{-+
Functions for unifying types, matching types, and expanding type synonyms.
-}
module TiSolve(TypeConstraint(..),solve,isVar,matches,matchEqns,expandSynonyms) where
import Prelude hiding (lookup) -- for Hugs
import Monad(mplus)
import TiConstraints
import TiTypes
import TiNames
import TiKinds
import TiPrelude(funcon)
import Unification(Unifiable(..),unify,match)
import MUtils
import Syntax(HsTypeI(..),TI(..),HsIdentI(..),matchT,struct,mapT,base)
import TiKEnv(lookup)
import PrettyPrint(Printable,pp)

--------------------------------------------------------------------------------
data TypeConstraint i
  = Type i :=: Type i
  | Inst (Typing i (Pred i))
  | KInst (Kinded (Type i)) -- to keep track of the kinds of type variables
  deriving Show

instance TypeVar v => Types v (TypeConstraint v) where
  tmap f (t1:=:t2) = tmap f t1:=:tmap f t2
  tmap f (Inst i) = Inst (tmap f i)
  tmap f (KInst (t:>:k)) = KInst (tmap f t:>:k)
  tv (t1:=:t2) = tv (t1,t2)
  tv (Inst i) = tv i
  tv (KInst (t:>:k)) = tv t

--------------------------------------------------------------------------------

solve env constraints =
    do let (eqns,(preds0,kpreds0)) = separate (toList constraints)
       s <- S # unify (expandEqns env eqns)
       let preds = apply s preds0
           kpreds = [apply s t:>:k|t:>:k<-kpreds0]
       return (preds,kpreds,s)
  where
    separate = apSnd (mapPartition id) . mapPartition dist
    dist (t1:=:t2) = Left (t1,t2)
    dist (Inst i) = Right (Left i)
    dist (KInst ki) = Right (Right ki)

t1 `matches` t2 = matchEqns [(t2,t1)]
matchEqns eqns env =  S # match (expandEqns env eqns)

instance (Printable i,TypeId i) => Unifiable (Type i) i where
  topMatch (Typ t1,Typ t2) =
    matchT t1 t2 `mplus` matchT (tupleHack t1) (tupleHack t2)

  isVar = isVarT
  subst s t = apply (S s) t

  showTerm = pp

{-+
Function types (previously also tuple types) can be represented in two
different ways in the abstract syntax, so we convert one
representation to the other to avoid false unification/matching
errors...
-}
--tupleHack (HsTyTuple ts) = struct $ appT (tuplecon (length ts):ts)
tupleHack (HsTyFun t1 t2) = struct $ appT [funcon,t1,t2]
tupleHack t = t

expandEqns env = mapBoth (expandSynonyms env)

{-+
Expand type synonyms.
(Previously, this function also replaced data/newtype names with their
original names. This is now assumed to happen in a pass prior to type checking.)
-}

--expandSynonyms :: Env QId (Kind,TypeInfo) -> Type -> Type
expandSynonyms env (Typ t) =
  case t of
    HsTyApp t1 t2 -> expandApp t1 [t2]
    HsTyCon _     -> expandApp (base t) []
    _ -> base $ mapT (expandSynonyms env) t
 where
   --expandApp :: Type -> [Type] -> Type
   expandApp (Typ t) ts = 
     case t of
       HsTyApp t1 t2 -> expandApp t1 (t2:ts)
       HsTyCon c ->
         case TiKEnv.lookup env (HsCon c) of
	   --Just (_,Data orig) -> app (HsTyCon orig)
	   --Just (_,Newtype orig) -> app (HsTyCon orig)
           Just (_,Synonym vs rhs) | n<=length ts ->
	       expandSynonyms env (appT (apply (S (zip vs ts1)) rhs:ts2))
	     where
               n=length vs
	       (ts1,ts2) = splitAt n ts
	   _ -> keepApp
       _ -> keepApp
     where
       keepApp = app t
       app t =  appT (base t:map (expandSynonyms env) ts)
