module Language.MSH.CodeGen.Constructors (
    genConstructors
) where

import Control.Applicative ((<$>))
import Control.Monad (replicateM)

import qualified Data.Map as M

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

import Language.MSH.Constructor 
import Language.MSH.StateDecl
import Language.MSH.StateEnv
import Language.MSH.CodeGen.Shared
import Language.MSH.CodeGen.Interop

{-
    Constructors
-}

genCtrParams :: StateDecl -> Q [(String, Name)]
genCtrParams (StateDecl { 
    stateName = name,
    stateData = ds 
}) = mapM (\(n,_) -> newName n >>= \v -> return (n,v)) (getFields ds)

-- | This is a hack to change the names of type variables in imported types from
--   unique names to unqualified names
unqualifyName :: Name -> Name 
unqualifyName (Name occ flavour) = case flavour of
    NameU _ -> Name occ NameS 
    _       -> Name occ flavour

unqualifyBndr :: TyVarBndr -> TyVarBndr
unqualifyBndr (PlainTV n)    = PlainTV (unqualifyName n)
unqualifyBndr (KindedTV n k) = KindedTV (unqualifyName n) k

--unqualifyPred :: Pred -> Pred
--unqualifyPred (AppT (AppT EqualityT a) b) = AppT (AppT EqualityT (normaliseType a)) (normaliseType b)
--unqualifyPred (ClassP n ts) = foldl AppT (ConT n) (map normaliseType ts)

normaliseType :: Type -> Type
normaliseType (ForallT bs ctx t) = ForallT (map unqualifyBndr bs) (map normaliseType ctx) (normaliseType t)
normaliseType (AppT f a) = AppT (normaliseType f) (normaliseType a)
normaliseType (SigT t k) = SigT (normaliseType t) k
normaliseType (VarT n) = VarT (unqualifyName n)
normaliseType t = t

genPCtrParams :: StateEnv -> String -> Q [(Type,Name)]
genPCtrParams env pn = case M.lookup pn env of
    (Just s) -> do
        ts <- getFieldTypes $ stateData s
        mapM (\(n,t) -> newName n >>= \n' -> return (t,n')) ts
    Nothing  -> do
        mn <- lookupValueName $ "_mk" ++ pn
        case mn of
            Nothing  -> fail $ "Constructor for `" ++ pn ++ "' is not in scope."
            (Just n) -> do
                (VarI _ t _ _) <- reify n
                mapM (\t -> newName "arg" >>= \n -> return (t,n)) (typeArgs $ normaliseType t)

genStateExpr :: StateDecl -> [(String, Name)] -> Exp
genStateExpr (StateDecl { 
    stateName = name,
    stateData = ds 
}) vs = RecConE (mkName $ "Mk" ++ name ++ "State") baseFs
    where
        baseFs = [(mkName $ "_" ++ name ++ "_" ++ n, VarE v) | (n,v) <- vs]

-- | Generates the internal constructor `_mkS' for a class `S'.
genBaseConstructor :: StateEnv -> StateDecl -> Q StateCtr
genBaseConstructor env s@(StateDecl { stateName = name, stateParentN = mp, stateData = ds }) = do
    vs <- genCtrParams s 
    ts <- map snd <$> getFieldTypes ds 
    let
        baseName = mkName $ "_mk" ++ name
        stateExp = genStateExpr s vs
        ps       = map (VarP . snd) vs
    case mp of 
        Nothing  -> do
            let
                cn  = mkName $ name ++ "Data"
                con = RecConE cn [(mkName $ "_" ++ name ++ "_data", stateExp)]
            return $ SCtr {
                sctrDec   = FunD baseName [Clause ps (NormalB con) []],
                sctrTypes = ts 
            }
        (Just p) -> do
            let
                cn        = mkName $ name ++ "End"
                Name pn _ = parentName $ parseType p
                pctr      = "_mk" ++ occString pn
            pps <- genPCtrParams env (occString pn)
            let
                pvs       = map snd pps
                supExp    = VarE $ mkName pctr
                appSup    = appEs supExp (map VarE pvs)
                con       = RecConE cn [(mkName $ "_" ++ name ++ "_data",stateExp), (mkName $ "_" ++ name ++ "_sup",appSup)]
            return $ SCtr {
                sctrDec   = FunD baseName [Clause (map VarP pvs ++ ps) (NormalB con) []],
                sctrTypes = map fst pps ++ ts
            }

{-genSuperConstructor :: StateDecl -> Q Dec
genSuperConstructor (StateDecl m name vars p decls) = do
    let
        supName = mkName $ "_mk" ++ name ++ "_super"
        supFs   = [(mkName $ "_" ++ n, parseExp e) | (n,e) <- getFields decls]
        supExp  = RecConE (mkName $ name ++ "Start")
    return $ FunD supName [Clause [] (NormalB supExp) []]-}

genConstructors :: StateEnv -> StateDecl -> Q StateCtr
genConstructors env s = 
    genBaseConstructor env s