{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE UndecidableInstances #-}

module Language.Haskell.Tools.AST.SemaInfoClasses
  (module Language.Haskell.Tools.AST.SemaInfoClasses, getInstances, UsageSpec(..)) where

import GHC
import Id as GHC (Id, idName)

import Control.Reference

import Language.Haskell.Tools.AST.Ann as AST
import Language.Haskell.Tools.AST.Representation.Exprs as AST (UFieldWildcard, UExpr)
import Language.Haskell.Tools.AST.Representation.Modules as AST (UImportDecl, UModule)
import Language.Haskell.Tools.AST.Representation.Names as AST (UName(..), UQualifiedName)
import Language.Haskell.Tools.AST.Representation.Literals as AST (ULiteral)
import Language.Haskell.Tools.AST.SemaInfoTypes as AST

semanticsLitType :: Ann ULiteral IdDom st -> GHC.Type
semanticsLitType lit = lit ^. annotation & semanticInfo & literalType

-- * Information about names

-- | Domains that have semantic information for names
type HasNameInfo dom = (Domain dom, HasNameInfo' (SemanticInfo dom UQualifiedName))

-- | Infos that may have a name that can be extracted
class HasNameInfo' si where
  semanticsName :: si -> Maybe GHC.Name

instance HasNameInfo' (NameInfo GhcRn) where
  semanticsName = (^? nameInfo)

instance HasNameInfo' CNameInfo where
  semanticsName = fmap idName . (^? cnameInfo)

instance HasNameInfo dom => HasNameInfo' (Ann UQualifiedName dom st) where
  semanticsName = semanticsName . (^. annotation&semanticInfo)

instance HasNameInfo dom => HasNameInfo' (Ann UName dom st) where
  semanticsName = semanticsName . _simpleName . _element

-- | Domains that have semantic information for literals
type HasLiteralInfo dom = (Domain dom, HasLiteralInfo' (SemanticInfo dom ULiteral))

-- | Info of types
class HasLiteralInfo' si where
  semanticsLiteralType :: si -> GHC.Type

instance HasLiteralInfo' LiteralInfo where
  semanticsLiteralType = (^. literalType)

instance HasLiteralInfo dom => HasLiteralInfo' (Ann ULiteral dom st) where
  semanticsLiteralType = semanticsLiteralType . (^. annotation&semanticInfo)

-- * Information about typed names

type HasIdInfo dom = (Domain dom, HasIdInfo' (SemanticInfo dom UQualifiedName))

-- | Infos that may have a typed name that can be extracted
class HasNameInfo' si => HasIdInfo' si where
  semanticsId :: si -> Id

instance HasIdInfo' CNameInfo where
  semanticsId = (^. cnameInfo)

instance HasIdInfo dom => HasIdInfo' (Ann UQualifiedName dom st) where
  semanticsId = semanticsId . (^. annotation&semanticInfo)

instance HasIdInfo dom => HasIdInfo' (Ann UName dom st) where
  semanticsId = semanticsId . _simpleName . _element

-- * Fixity information

type HasFixityInfo dom = (Domain dom, HasFixityInfo' (SemanticInfo dom UQualifiedName))

-- | Infos that may have a fixity information
class HasFixityInfo' si where
  semanticsFixity :: si -> Maybe GHC.Fixity

instance HasFixityInfo' CNameInfo where
  semanticsFixity = (^. cnameFixity)

instance HasFixityInfo dom => HasFixityInfo' (Ann UQualifiedName dom st) where
  semanticsFixity = semanticsFixity . (^. annotation&semanticInfo)

-- * Scope information

type HasScopeInfo dom = (Domain dom, HasScopeInfo' (SemanticInfo dom UQualifiedName), HasScopeInfo' (SemanticInfo dom UExpr))

-- | Infos that contain the names that are available in theirs scope
class HasScopeInfo' si where
  semanticsScope :: si -> Scope

instance HasScopeInfo' (NameInfo n) where
  semanticsScope = (^. nameScopedLocals)

instance HasScopeInfo' CNameInfo where
  semanticsScope = (^. cnameScopedLocals)

instance HasScopeInfo' ScopeInfo where
  semanticsScope = (^. exprScopedLocals)

instance HasScopeInfo dom => HasScopeInfo' (Ann UExpr dom st) where
  semanticsScope = semanticsScope . (^. annotation&semanticInfo)

instance HasScopeInfo dom => HasScopeInfo' (Ann UQualifiedName dom st) where
  semanticsScope = semanticsScope . (^. annotation&semanticInfo)

-- * Information about names being defined

type HasDefiningInfo dom = (Domain dom, HasDefiningInfo' (SemanticInfo dom UQualifiedName))

-- | Infos that store if they were used to define a name
class HasDefiningInfo' si where
  semanticsDefining :: si -> Bool

instance HasDefiningInfo' (NameInfo n) where
  semanticsDefining = (^. nameIsDefined)

instance HasDefiningInfo' CNameInfo where
  semanticsDefining = (^. cnameIsDefined)

instance HasDefiningInfo dom => HasDefiningInfo' (Ann UQualifiedName dom st) where
  semanticsDefining = semanticsDefining . (^. annotation&semanticInfo)

-- * Information about source info in sema

class HasSourceInfoInSema' si where
  semanticsSourceInfo :: si -> Maybe SrcSpan

instance HasSourceInfoInSema' (NameInfo n) where
  semanticsSourceInfo = (^? nameLocation)

-- * Information about modules

type HasModuleInfo dom = (Domain dom, HasModuleInfo' (SemanticInfo dom AST.UModule))

class HasModuleInfo' si where
  semanticsModule :: si -> GHC.Module
  semanticsDynFlags :: si -> GHC.DynFlags
  isBootModule :: si -> Bool
  semanticsImplicitImports :: si -> [GHC.Name]
  semanticsPrelTransMods :: si -> [Module]

instance HasModuleInfo' (AST.ModuleInfo GhcRn) where
  semanticsModule = (^. defModuleName)
  semanticsDynFlags = (^. defDynFlags)
  isBootModule = (^. defIsBootModule)
  semanticsImplicitImports = (^? implicitNames&traversal&pName)
  semanticsPrelTransMods = (^. prelTransMods)

instance HasModuleInfo' (AST.ModuleInfo GhcTc) where
  semanticsModule = (^. defModuleName)
  semanticsDynFlags = (^. defDynFlags)
  isBootModule = (^. defIsBootModule)
  semanticsImplicitImports = map idName . (^? implicitNames&traversal&pName)
  semanticsPrelTransMods = (^. prelTransMods)

instance HasModuleInfo dom => HasModuleInfo' (Ann UModule dom st) where
  semanticsModule = semanticsModule . (^. annotation&semanticInfo)
  semanticsDynFlags = semanticsDynFlags . (^. annotation&semanticInfo)
  isBootModule = isBootModule . (^. annotation&semanticInfo)
  semanticsImplicitImports = semanticsImplicitImports . (^. annotation&semanticInfo)
  semanticsPrelTransMods = semanticsPrelTransMods . (^. annotation&semanticInfo)

-- * Information about imports

type HasImportInfo dom = (Domain dom, HasImportInfo' (SemanticInfo dom AST.UImportDecl))

class HasImportInfo' si where
  semanticsImportedModule :: si -> GHC.Module
  semanticsAvailable :: si -> [GHC.Name]
  semanticsImported :: si -> [GHC.Name]
  semanticsTransMods :: si -> [Module]

instance HasImportInfo' (AST.ImportInfo GhcRn) where
  semanticsImportedModule = (^. importedModule)
  semanticsAvailable = (^. availableNames)
  semanticsImported = (^? importedNames&traversal&pName)
  semanticsTransMods = (^. importTransMods)

instance HasImportInfo' (AST.ImportInfo GhcTc) where
  semanticsImportedModule = (^. importedModule)
  semanticsAvailable = map idName . (^. availableNames)
  semanticsImported = map idName . (^? importedNames&traversal&pName)
  semanticsTransMods = (^. importTransMods)

instance HasImportInfo dom => HasImportInfo' (Ann UImportDecl dom st) where
  semanticsImportedModule = semanticsImportedModule . (^. annotation&semanticInfo)
  semanticsAvailable = semanticsAvailable . (^. annotation&semanticInfo)
  semanticsImported = semanticsImported . (^. annotation&semanticInfo)
  semanticsTransMods = semanticsTransMods . (^. annotation&semanticInfo)

-- * Information about implicitly bounded fields

type HasImplicitFieldsInfo dom = (Domain dom, HasImplicitFieldsInfo' (SemanticInfo dom AST.UFieldWildcard))

class HasImplicitFieldsInfo' si where
  semanticsImplicitFlds :: si -> [(GHC.Name, GHC.Name)]

instance HasImplicitFieldsInfo' ImplicitFieldInfo where
  semanticsImplicitFlds = (^. implicitFieldBindings)

instance HasImplicitFieldsInfo dom => HasImplicitFieldsInfo' (Ann UFieldWildcard dom st) where
  semanticsImplicitFlds = semanticsImplicitFlds . (^. annotation&semanticInfo)

-- * AST elements with no information

type HasNoSemanticInfo dom si = SemanticInfo dom si ~ NoSemanticInfo
