module AST.Module
    ( Interfaces
    , Types, Aliases, ADTs
    , AdtInfo, CanonicalAdt
    , SourceModule, ValidModule, CanonicalModule, Optimized
    , Module(..), Body(..)
    , Header(..)
    , Interface(..), toInterface
    , UserImport, DefaultImport, ImportMethod(..)
    ) where

import Data.Binary
import qualified Data.Map as Map

import qualified AST.Declaration as Decl
import qualified AST.Expression.Canonical as Canonical
import qualified AST.Expression.Optimized as Optimized
import qualified AST.Module.Name as Name
import qualified AST.Type as Type
import qualified AST.Variable as Var
import qualified Docs.AST as Docs
import qualified Elm.Package as Package
import qualified Elm.Compiler.Version as Compiler
import qualified Reporting.Annotation as A

import qualified Type.Effect.Common as Effect


-- HELPFUL TYPE ALIASES

type Interfaces = Map.Map Name.Canonical Interface

type Types   = Map.Map String Type.Canonical
type Aliases = Map.Map String ([String], Type.Canonical)
type ADTs    = Map.Map String (AdtInfo String)

type AdtInfo v = ( [String], [(v, [Type.Canonical])] )
type CanonicalAdt = (Var.Canonical, AdtInfo Var.Canonical)


-- MODULES

type SourceModule =
    Module
      String
      [UserImport]
      (Var.Listing (A.Located Var.Value))
      [Decl.SourceDecl]


type ValidModule =
    Module
      String
      ([DefaultImport], [UserImport])
      (Var.Listing (A.Located Var.Value))
      [Decl.ValidDecl]


type CanonicalModule =
    Module Docs.Centralized [Name.Raw] [Var.Value] (Body Canonical.Expr)


type Optimized =
    Module Docs.Centralized [Name.Raw] [Var.Value] (Body [Optimized.Def])


data Module docs imports exports body = Module
    { name    :: Name.Canonical
    , path    :: FilePath
    , docs    :: A.Located (Maybe docs)
    , exports :: exports
    , imports :: imports
    , body    :: body
    }


data Body expr = Body
    { program   :: expr
    , types     :: Types
    , annots    :: Map.Map String (Effect.CanonicalAnnot, [Int], [Effect.CanonicalConstr])
    , fixities  :: [(Decl.Assoc, Int, String)]
    , aliases   :: Aliases
    , datatypes :: ADTs
    , ports     :: [String]
    }


-- HEADERS

{-| Basic info needed to identify modules and determine dependencies. -}
data Header imports = Header
    { _name :: Name.Raw
    , _docs :: A.Located (Maybe String)
    , _exports :: Var.Listing (A.Located Var.Value)
    , _imports :: imports
    }


-- IMPORTs

type UserImport = A.Located (Name.Raw, ImportMethod)


type DefaultImport = (Name.Raw, ImportMethod)


data ImportMethod = ImportMethod
    { alias :: Maybe String
    , exposedVars :: !(Var.Listing Var.Value)
    }


-- INTERFACES

{-| Key facts about a module, used when reading info from .elmi files. -}
data Interface = Interface
    { iVersion  :: Package.Version
    , iPackage  :: Package.Name
    , iExports  :: [Var.Value]
    , iTypes    :: Types
    , iAnnots   :: Map.Map String (Effect.CanonicalAnnot, [Int], [Effect.CanonicalConstr])
    , iImports  :: [Name.Raw]
    , iAdts     :: ADTs
    , iAliases  :: Aliases
    , iFixities :: [(Decl.Assoc, Int, String)]
    , iPorts    :: [String]
    }


toInterface :: Package.Name -> Optimized -> Interface
toInterface pkgName modul =
    let body' = body modul in
    Interface
    { iVersion  = Compiler.version
    , iPackage  = pkgName
    , iExports  = exports modul
    , iTypes    = types body'
    , iAnnots   = annots body'
    , iImports  = imports modul
    , iAdts     = datatypes body'
    , iAliases  = aliases body'
    , iFixities = fixities body'
    , iPorts    = ports body'
    }


instance Binary Interface where
  get = Interface <$> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get
  put modul = do
      put (iVersion modul)
      put (iPackage modul)
      put (iExports modul)
      put (iTypes modul)
      put (iAnnots modul)
      put (iImports modul)
      put (iAdts modul)
      put (iAliases modul)
      put (iFixities modul)
      put (iPorts modul)
