-- 
-- (C) Susumu Katayama
--
module MagicHaskeller.Analytical.Parser where

import Data.List(sort, group, genericLength)
import Control.Monad -- hiding (guard)
import Control.Monad.State -- hiding (guard)
import Data.Char(ord)
import Data.Array
import qualified Data.Map as Map
import qualified Data.IntMap as IntMap
import Language.Haskell.TH hiding (match)

import MagicHaskeller.CoreLang(VarLib, Var)
import qualified MagicHaskeller.Types as T
import MagicHaskeller.PriorSubsts hiding (unify)
import MagicHaskeller.TyConLib
import MagicHaskeller.ReadTHType(thTypeToType)
import qualified MagicHaskeller.PolyDynamic as PD

import MagicHaskeller.Analytical.Syntax

import Data.Word

data XVarLib = XVL {varLib :: VarLib, invVarLib :: Map.Map String Var, zeroID :: Var, succID :: Var, negateID :: Var} deriving Show

-- We compare nameBase ignoring the module name, instead of using equivalence over Name's.
mkXVarLib :: VarLib -> XVarLib
mkXVarLib vl = let
                   (_,mx) = bounds vl
               in XVL {varLib = vl
                      , invVarLib = Map.fromListWith (\_ a -> a) ([ (nameBase name, num) | (num, PD.Dynamic{PD.dynExp=thexpr}) <- assocs vl, name <- extractName thexpr ])
                      , zeroID = mx-2   -- These are dependent on the order in CoreLang.defaultPrimitives
                      , succID = mx-1
                      , negateID = mx
                      }
extractName (ConE name) = [name]
extractName (VarE name) = [name]
extractName _           = []
parseTypedIOPairss :: (Functor m, MonadPlus m) => TyConLib -> XVarLib -> [Dec] -> PriorSubsts m [(Name, T.Typed [IOPair T.Type])]
parseTypedIOPairss tcl xvl ds = inferTypedIOPairss =<< parseTypedIOPairss' tcl xvl ds
inferTypedIOPairss :: (Functor m, MonadPlus m) => [(Name,(Maybe T.Type, Maybe (T.Typed [IOPair T.Type])))] -> PriorSubsts m [(Name, T.Typed [IOPair T.Type])]
inferTypedIOPairss ((name, (Just ty, Just (iops T.::: infty))):ts)
    = do apinfty <- applyPS infty
         mguPS apinfty $ T.quantify ty
--         updateSubstPS (return . unquantifySubst)

         s <- getSubst
         let hd = (name, map (tapplyIOP s) iops T.:::ty)
         tl <- inferTypedIOPairss ts
         return (hd:tl)
inferTypedIOPairss ((name, (Nothing, Just (iops T.::: infty))):ts)
    = do s <- getSubst
         let hd = (name, map (tapplyIOP s) iops T.::: T.apply s infty)
         tl <- inferTypedIOPairss ts
         return (hd:tl)
inferTypedIOPairss ((_nam, (Just _t, Nothing)):ts) = inferTypedIOPairss ts -- pattern including only a type signature. This is still useful when incorporating with MagicHaskeller, but MagH has its own parser, so let's ignore the pattern silently.
inferTypedIOPairss ((_,    (Nothing, Nothing)):_)  = error "MagicHaskeller.TypedIOPairs.inferTypedIOPairss: impossible"
inferTypedIOPairss [] = return []

parseTypedIOPairss' :: (Functor m,MonadPlus m) => TyConLib -> XVarLib -> [Dec] -> PriorSubsts m [(Name, (Maybe T.Type, Maybe (T.Typed [IOPair T.Type])))]
parseTypedIOPairss' tcl xvl ds
    = do tups <- parseIOPairss xvl ds
         return $ Map.toList $ Map.fromListWith plus
                    ([(name, (Just t,  Nothing))   | (name, t)    <- parseTypes tcl ds] ++
                     [(name, (Nothing, Just tiops)) | (name, tiops) <- tups])
(a,b) `plus` (c,d) = (a `mplus` c, b `mplus` d)

parseTypes :: TyConLib -> [Dec] -> [(Name,T.Type)]
parseTypes tcl ds = [ (name, thTypeToType tcl ty) | SigD name ty <- ds ]


parseIOPairss :: (Functor m, MonadPlus m) => XVarLib -> [Dec] -> PriorSubsts m [(Name, T.Typed [IOPair T.Type])]
parseIOPairss xvl (FunD funname clauses : decs) 
    = do tiops <- mapM (clauseToIOPair xvl) clauses
         let (iops,t:ts) = unzipTyped tiops
         ty <- foldM mgtPS t ts
         s <- getSubst
         let hd = (funname, map (tapplyIOP s) iops T.::: ty)
         tl <- parseIOPairss xvl decs
         return $ hd:tl
parseIOPairss xvl (ValD (VarP name) (NormalB ex) [] : decs)
    = do (vout, _intmap) <- runStateT (inferType (thExpToExpr xvl ex)) IntMap.empty
         let hd = (name,    [IOP 0 [] vout] T.::: ann vout)
         tl <- parseIOPairss xvl decs
         return $ hd:tl
parseIOPairss xvl (_:decs) = parseIOPairss xvl decs
parseIOPairss _   []       = return []

-- 型宣言がある場合,そのforallなやつにマッチして終了.
-- ない場合,そのまま関数にして終了.
clauseToIOPair :: (Functor m, MonadPlus m) => XVarLib -> Clause -> PriorSubsts m (T.Typed (IOPair T.Type))
clauseToIOPair ivl cl = fmap fst $ runStateT (clauseToIOPair' ivl cl) IntMap.empty
clauseToIOPair' ivl (Clause inpats (NormalB ex) []) =do ins <- mapM inferT (reverse $ map (patToExp ivl) inpats)
                                                        vout <- inferT (thExpToExpr ivl ex)
                                                        ty <- lift $ applyPS (T.popArgs (map ann ins) $ ann vout)
                                                        return $ normalizeMkIOP ins vout T.::: ty
clauseToIOPair' _ _ = error "Neither _guards_ nor _where_clauses_ are permitted in clauses representing I/O pairs." 
-- In future where-clauses might be supported.


matchType :: (Functor m, MonadPlus m) => [T.Type] -> T.Type -> T.Type -> PriorSubsts m ()
matchType argtys retty ty = mguType argtys retty (T.quantify ty) >> updateSubstPS (return . unquantifySubst)
unquantifySubst = map (\(v,t) -> (v, T.unquantify t))
mguType (t:ts) r (u T.:->v) = do mguPS t u
                                 s <- getSubst
                                 mguType (map (T.apply s) ts) (T.apply s r) v
mguType []     r v       = T.mgu r v
mguType (_:_)  _ _       = error "Not enough arguments supplied."


inferType, inferT :: (Functor m, MonadPlus m) => Expr a -> StateT (IntMap.IntMap T.Type) (PriorSubsts m) (Expr T.Type)
inferType e = do e' <- inferT e
                 s <- lift getSubst
                 return $ tapplyExpr s e'
inferT v@(U _ i) = do 
                    tenv <- get
                    case IntMap.lookup i tenv of
                        Nothing -> do tvid <- lift newTVar
                                      let ty = T.TV tvid
                                      put $ IntMap.insert i ty tenv
                                      return (U ty i)
                        Just ty -> do apty <- lift $ applyPS ty
                                      return (U apty i)
inferT (C _ sz (i T.:::ty) es)
                       = do es' <- mapM inferT es
                            lift $ do let tvs = map head $ group $ sort $ T.tyvars ty
                                      tvid <- reserveTVars $ genericLength tvs
                                      let apty = T.apply (zip tvs $ map T.TV [tvid..]) ty
                                      rty <- foldM funApM apty $ map ann es'
                                      rapty <- applyPS rty
                                      return $ C rapty sz (i T.:::apty) es'
funApM :: (Functor m, MonadPlus m) => T.Type -> T.Type -> PriorSubsts m T.Type
funApM (a T.:-> r) t = fAM a r t
funApM (a T.:>  r) t = fAM a r t
funApM (T.TV i)    t = do tvid <- newTVar
                          updatePS [(i,t T.:->T.TV tvid)]
                          return $ T.TV tvid
funApM _         _ = fail "too many arguments applied."
fAM apa r t = do apt <- applyPS t
                 mguPS apa apt
                 applyPS r
tapplyIOP :: T.Subst -> IOPair T.Type -> IOPair T.Type
tapplyIOP s (IOP bvs ins out) = IOP bvs (map (tapplyExpr s) ins) (tapplyExpr s out)

tapplyExpr :: T.Subst -> Expr T.Type -> Expr T.Type
tapplyExpr sub (C t sz (i T.:::cty) es) = C (T.apply sub t) sz (i T.:::T.apply sub cty) (map (tapplyExpr sub) es)
tapplyExpr _   v               = v
{-
substitutionを一度getしたら,それを全体に波及させる必要がある?
てゆーか,各コンストラクタのforallでfreshVarしたやつだけすればよい?
考えるの面倒くさいし,律速ではないので2パスで.
-}

-- MagicHaskeller.Typesに置くべきという気がしないでもない.
unzipTyped [] = ([],[])
unzipTyped ((e T.:::t):ets) = let (es,ts) = unzipTyped ets in (e:es,t:ts)


getMbTypedConstr :: XVarLib -> Name -> Maybe (T.Typed Constr)
getMbTypedConstr xvl name = fmap (mkTypedConstr xvl) $ Map.lookup (nameBase name) (invVarLib xvl)
getTypedConstr :: XVarLib -> Name -> T.Typed Constr
getTypedConstr xvl name = case Map.lookup (nameBase name) $ invVarLib xvl of Just c -> mkTypedConstr xvl c
                                                                             Nothing -> error ("could not find "++show name)
mkTypedConstr  xvl c   = c T.::: PD.dynType (varLib xvl!c)
patToExp ivl (LitP (IntegerL i)) | i>=0      = natToConExp ivl i
                                 | otherwise = cap () (mkTypedConstr ivl (negateID ivl)) [natToConExp ivl (-i)]
-- patToExp tcl (LitP (CharL c))     = C (Ctor (ord c) (cに相当する奴. ある訳ない?)) []
-- patToExp tcl (LitP (StringL str)) = strToConExp tcl str
patToExp ivl (VarP name)   = U () (strToInt $ nameBase name)
patToExp ivl (TupP pats)         = cap () (getTypedConstr ivl (tupleDataName (length pats))) (map (patToExp ivl) pats)
patToExp ivl (ConP name pats)    = cap () (getTypedConstr ivl name)                          (map (patToExp ivl) pats)
patToExp ivl (InfixP p1 name p2) = cap () (getTypedConstr ivl name)                          (map (patToExp ivl) [p1,p2])
patToExp ivl (TildeP p)    = patToExp ivl p
patToExp ivl (AsP _ _)     = error "As (@) patterns not supported."
patToExp ivl WildP         = U () (strToInt "_") -- will not work correctly if there are more than one wildcards in one I/O pair, I think.
patToExp ivl (RecP _ _)    = error "Record patterns not supported."
patToExp ivl (ListP pats)  = foldr cons nil $ map (patToExp ivl) pats 
    where nil        = C () 1 (getTypedConstr ivl '[]) []
          cons e1 e2 = cap () (getTypedConstr ivl '(:)) [e1,e2]
patToExp ivl (SigP pat _t) = patToExp ivl pat -- Or should this cause an error?

-- Is this encoding really quicker than raw String (or maybe PackedString)?
strToInt [] = 1
strToInt (x:xs) = ord x + 256 * strToInt xs

natLimit = 32
natToConExp ivl i -- x | i > natLimit = C (Ctor i  (iに相当する奴. ある訳ない?)) []
                  | otherwise    = smallNat ivl i
smallNat ivl 0 = C () 1 (mkTypedConstr ivl (zeroID ivl)) []
smallNat ivl i = cap () (mkTypedConstr ivl (succID ivl)) [smallNat ivl (i-1)]
-- strToConExp tcl "" = C (Ctor 0 ([]に相当する奴)) []

thExpToExpr :: XVarLib -> Exp -> Expr ()
thExpToExpr ivl (VarE name) = case getMbTypedConstr ivl name of Nothing -> U () (strToInt $ nameBase name)
                                                                Just x  -> C () 1 x []
thExpToExpr ivl (ConE name) = C () 1 (getTypedConstr ivl name) []
thExpToExpr ivl (LitE (IntegerL i)) | i >= 0    = natToConExp ivl i
                                    | otherwise = cap () (mkTypedConstr ivl (negateID ivl)) [natToConExp ivl (-i)]
thExpToExpr ivl (AppE f x)  = case thExpToExpr ivl f of C () sz c xs -> let thx = thExpToExpr ivl x
                                                                        in C () (sz + size thx) c (xs ++ [thx])  -- O(n^2)
                                                        U () _    -> error "Only constructor applications are permitted in IO examples."
thExpToExpr ivl (InfixE (Just x) (ConE name) (Just y)) = cap () (getTypedConstr ivl name) [thExpToExpr ivl x, thExpToExpr ivl y]
thExpToExpr ivl (InfixE (Just x) (VarE name) (Just y)) = cap () (getTypedConstr ivl name) [thExpToExpr ivl x, thExpToExpr ivl y]
thExpToExpr ivl (TupE es) = cap () (getTypedConstr ivl (tupleDataName (length es))) (map (thExpToExpr ivl) es)
thExpToExpr ivl (ListE es) = foldr cons nil $ map (thExpToExpr ivl) es 
    where nil        = cap () (getTypedConstr ivl '[]) []
          cons e1 e2 = cap () (getTypedConstr ivl '(:)) [e1,e2]
thExpToExpr ivl (SigE e _t) = thExpToExpr ivl e
thExpToExpr _ _ = error "Unsupported expression in IO examples."
{-
caseの場合,既にあるprimitive componentに合わせるのは結構ややこしい.(たとえば,コンストラクタの順序づけとかを合わせるのはってことね.)
コンストラクタの順序づけはreifyでゲットした順にすることにして,caseは直接THを生成することにする.
これが可能なのは,まずanalyticalやってそれからsystematicをやるから.

そうなると,clauseToIOPairとかにVarLibはいらなくなるし,ConstrはCoreExprの代わりにTH.Exp(かTH.Name)を持つことになる.
-}