-- 
-- (C) Susumu Katayama
--
-- Finite trie indexed by Expr, used for fast pattern match
module MagicHaskeller.Analytical.FMExpr where

import qualified Data.IntMap as IntMap

import qualified MagicHaskeller.Types as Types

import MagicHaskeller.Analytical.Syntax


iopsToVisFME :: TBS -> [IOPair] -> FMExpr [IOPair]
iopsToVisFME tbs = iopsToFME . map (visIOP tbs)
iopsToFME :: [IOPair] -> FMExpr [IOPair]
iopsToFME = assocsToFME . map iop2Assoc

visIOP :: TBS -> IOPair -> IOPair
visIOP tbs iop = iop {inputs = visibles tbs $ inputs iop}

iop2Assoc :: IOPair -> (Expr, IOPair)
iop2Assoc iop = (output iop, iop)



-- | @FMExpr a@ is a finite trie representing @Expr -> a@
data FMExpr a = EmptyFME -- use of emptyFME in place of EmptyFME should not be as efficient, because there are many EmptyFME's.
              | FME { existentialFME :: IntMap.IntMap a, universalFME :: [a], conApFME :: IntMap.IntMap (FMExprs a) } deriving Show
data FMExprs a = EmptyFMEs -- and there are many EmptyFMEs's, too.
               | FMEs { nilFMEs :: a, consFMEs :: FMExpr (FMExprs a) } deriving Show
instance Functor FMExpr where
  fmap _ EmptyFME = EmptyFME
  fmap f (FME {existentialFME=e, universalFME=u, conApFME=c}) = FME{existentialFME = fmap f e, universalFME = fmap f u, conApFME = fmap (fmap f) c}
instance Functor FMExprs where
  fmap f EmptyFMEs = EmptyFMEs
  fmap f (FMEs {nilFMEs = n, consFMEs = c}) = FMEs {nilFMEs = f n, consFMEs = fmap (fmap f) c}
assocsToFME :: [(Expr, a)] -> FMExpr [a]
assocsToFME = foldr (\(k,v) -> updateFME (v:) [] k) emptyFME
updateFME :: (a->a) -> a -> Expr -> FMExpr a -> FMExpr a
updateFME f x t                EmptyFME = updateFME f x t emptyFME
updateFME f x (E i)            fme      = fme { existentialFME = IntMap.insertWith (\_ -> f) i (f x) $ existentialFME fme }
updateFME f x (U i)            fme      = fme { universalFME   = insertNth f x i $ universalFME fme }
updateFME f x (C _ (c Types.:::_) fs) fme      = fme { conApFME       = IntMap.insertWith (\_ -> updateFMEs f x fs) c (updateFMEs f x fs EmptyFMEs) $ conApFME fme }
updateFMEs f x es         EmptyFMEs = updateFMEs f x es FMEs{nilFMEs = x, consFMEs = EmptyFME} 
updateFMEs f x []              fmes = fmes { nilFMEs  = f $ nilFMEs fmes }
updateFMEs f x (e:es)          fmes = fmes { consFMEs = updateFME (updateFMEs f x es) EmptyFMEs e $ consFMEs fmes }
emptyFME = FME{ existentialFME = IntMap.empty, universalFME = [], conApFME = IntMap.empty }


insertNth upd zero n []     = replicate n zero ++ [upd zero]
insertNth upd zero 0 (x:xs) = upd x : xs
insertNth upd zero n (x:xs) = x : insertNth upd zero (n-1) xs



-- returns the set of possible substitutions. Should the name be matchFME?
unifyFME :: Expr -> FMExpr a -> [(a,Subst)]
unifyFME x fme = unifyFME' x fme emptySubst
--unifyFME = matchFME
unifyFME' :: Expr -> FMExpr a -> Subst -> [(a,Subst)]
unifyFME' x         EmptyFME s = []
unifyFME' x@(E i)        fme s = error "cannot happen for now"
  {-
unifyFME' x@(E i)        fme s o = case lookup i s of Nothing -> [ (x, [(i,e')] `plusSubst` s) | (e,x) <- assocsFME fme, let e' = fresh (o+) e ] -- assocsFME :: FMExpr a -> [(Expr,a)]だけど，FMExprに情報を残しておけば無駄な計算がなくなるか．てゆーか，Typed ConstrのTypeの部分のためにそうする必要がある．
	       		       	   	       	      Just e  -> unifyFME' e fme s o
-}
unifyFME' x@(U i)        fme s = [ (v, subst `plusSubst` s)
                                 | (k,v) <- zip [0..] (universalFME fme)
                                 , subst <- case lookup k s of Nothing    -> [[(k,x)]]
                                                               Just (E j) -> [[(j,x)]]
                                                               Just (U j) | i==j -> [[]]
                                                               _          -> []
                                 ]
unifyFME' x@(C _sz (c Types.::: _) xs) fme s = matchExistential ++ matchConstr
    where matchExistential = [ (v, subst `plusSubst` s)
                             | (k,v) <- zip [0..] (universalFME fme)
                             , subst <- case lookup k s of Nothing -> [[(k,x)]]
                                                           Just e  -> unify e x
                             ]
          matchConstr      = case IntMap.lookup c (conApFME fme) of Nothing   -> []
                                                                    Just fmes -> unifyFMEs xs fmes s
unifyFMEs :: [Expr] -> FMExprs a -> Subst -> [(a,Subst)]
unifyFMEs _ EmptyFMEs _ = []
unifyFMEs []     fmes s = [ (nilFMEs fmes, s) ]
unifyFMEs (x:xs) fmes s = [ t | (fmes', s') <- unifyFME' x (consFMEs fmes) s, t <- unifyFMEs xs fmes' s' ]

assocsFME :: FMExpr a -> [(Expr,a)]
assocsFME EmptyFME = []
assocsFME fme = [ (E i, v) | (i,v) <- IntMap.toList (existentialFME fme) ] ++ [ (U i, v) | (i,v) <- zip [0..] (universalFME fme) ]
	   ++ [ (C (sum $ map size xs) (c Types.::: error "Not implemented yet!") xs, v)  | (c,fmes) <- IntMap.toList (conApFME fme), (xs, v) <- assocsFMEs fmes ]
assocsFMEs :: FMExprs a -> [([Expr],a)]
assocsFMEs EmptyFMEs = []
assocsFMEs fmes = ([], nilFMEs fmes) : [ (x:xs, v) | (x,fmes') <- assocsFME (consFMEs fmes), (xs, v) <- assocsFMEs fmes ]


-- 短くした物．効率は確認してない．
matchFME :: Expr -> FMExpr a -> [(a,Subst)]
matchFME x fme = matchFME' x fme emptySubst
matchFME' :: Expr -> FMExpr a -> Subst -> [(a,Subst)]
matchFME' x         EmptyFME s = []
matchFME' x@(E i) fme s = error "cannot happen"
-- Universal variables only match to existentials
matchFME' x@(U _) fme s = matchExistential x fme s
-- Constractor applications can match to both existentials and constructor applications with the same constructor.
matchFME' x@(C _sz (c Types.::: _) xs) fme s = matchExistential x fme s ++ matchConstr
    where matchConstr = case IntMap.lookup c (conApFME fme) of Nothing   -> []
                                                               Just fmes -> matchFMEs xs fmes s
matchExistential x fme s = [ (v, subst `plusSubst` s)
                           | (k,v) <- zip [0..] (universalFME fme)
                           , subst <- case lookup k s of Nothing -> [[(k,x)]]
                                                         Just e  -> unify e x
                           ]
-- matchFMEs matches corresponding constructor fields
matchFMEs :: [Expr] -> FMExprs a -> Subst -> [(a,Subst)]
matchFMEs _ EmptyFMEs _ = []
matchFMEs []     fmes s = [ (nilFMEs fmes, s) ]
matchFMEs (x:xs) fmes s = [ t | (fmes', s') <- matchFME' x (consFMEs fmes) s, t <- matchFMEs xs fmes' s' ]

