{-# LANGUAGE UndecidableInstances, RankNTypes, FlexibleInstances, MultiParamTypeClasses #-}
{-| Module      :  TypeConstraints
    License     :  GPL

    Maintainer  :  helium@cs.uu.nl
    Stability   :  experimental
    Portability :  portable

    The type constraints used by the Helium compiler (all derived from the
	basic constraints that are supplied by the Top framework). Some constraints
	are lifted to work on finite maps as well.
-}

module Helium.StaticAnalysis.Miscellaneous.TypeConstraints where

import Top.Constraint
import Top.Constraint.Equality hiding ((.==.))
import Top.Constraint.Qualifier
import Top.Constraint.Polymorphism hiding ((.::.))
import Top.Constraint.Information
import Top.Interface.Basic
import Top.Interface.Substitution
import Top.Interface.TypeInference
import Top.Interface.Qualification
import Top.Types
import qualified Data.Map as M

type TypeConstraints info = [TypeConstraint info]
data TypeConstraint  info
   = TC1 (EqualityConstraint info)
   | TC2 (ExtraConstraint info)
   | TC3 (PolymorphismConstraint info)
   | TCOper String (forall m . HasSubst m info => m ())

instance (HasBasic m info, HasTI m info, HasSubst m info, HasQual m info, PolyTypeConstraintInfo info)
            => Solvable (TypeConstraint info) m where
   solveConstraint (TC1 c)      = solveConstraint c
   solveConstraint (TC2 c)      = solveConstraint c
   solveConstraint (TC3 c)      = solveConstraint c
   solveConstraint (TCOper _ f) = f
   checkCondition  (TC1 c)      = checkCondition c
   checkCondition  (TC2 c)      = checkCondition c
   checkCondition  (TC3 c)      = checkCondition c
   checkCondition  (TCOper _ _) = return True

instance Show info => Show (TypeConstraint info) where
   show (TC1 c)      = show c
   show (TC2 c)      = show c
   show (TC3 c)      = show c
   show (TCOper s _) = s

instance Substitutable (TypeConstraint info) where
   sub |-> (TC1 c) = TC1 (sub |-> c)
   sub |-> (TC2 c) = TC2 (sub |-> c)
   sub |-> (TC3 c) = TC3 (sub |-> c)
   _   |-> tc     = tc
   ftv (TC1 c)    = ftv c
   ftv (TC2 c)    = ftv c
   ftv (TC3 c)    = ftv c
   ftv _          = []

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

polySubst :: M.Map Int (Scheme Predicates) -> TypeConstraint info -> TypeConstraint info
polySubst schemeMap tc =
   case tc of
      TC3 (Instantiate tp sigma info)        -> TC3 (Instantiate tp (f sigma) info)
      TC3 (Skolemize tp (monos, sigma) info) -> TC3 (Skolemize tp (monos, f sigma) info)
      _                                      -> tc

 where
   f :: Sigma Predicates -> Sigma Predicates
   f sigma =
      case sigma of
         SigmaVar i -> maybe sigma SigmaScheme (M.lookup i schemeMap)
         _          -> sigma

spreadFunction :: TypeConstraint info -> Maybe Int
spreadFunction tc =
   case tc of
      TC1 (Equality _ t2 _)             -> spreadFromType t2
      TC3 (Instantiate tp _ _)          -> spreadFromType tp
      TC3 (Skolemize tp _ _)            -> spreadFromType tp
      TC3 (Implicit t1 (_, _) _)        -> spreadFromType t1
      _                                 -> Nothing

spreadFromType :: Tp -> Maybe Int
spreadFromType (TVar i) = Just i
spreadFromType _        = Nothing

------------------------------------------------------------------------------
-- Lifted constructors

infix 3 .==., .===., .::., .:::., !::!, !:::!, .<=., .<==., !<=!, !<==!

lift :: Ord k => (a1 -> t1 -> t2 -> a) -> M.Map k a1
                 -> M.Map k [(t, t1)] -> (t -> t2) -> ([a], M.Map k [(t, t1)])
lift combinator as bs cf =
       let constraints = concat (M.elems (M.intersectionWith f as bs))
           rest        = bs M.\\ as
           f a list    = [ (a `combinator` b) (cf name) | (name,b) <- list ]
       in (constraints, rest)

(.==.) :: Show info => Tp -> Tp -> info -> TypeConstraint info
(t1 .==. t2) info = TC1 (Equality t1 t2 info)

(.===.) :: (Show info, Ord key) => M.Map key Tp -> M.Map key [(key,Tp)] -> (key -> info) -> ([TypeConstraint info], M.Map key [(key,Tp)])
(.===.) = lift (.==.)

(.::.) :: Show info => Tp -> TpScheme -> info -> TypeConstraint info
tp .::. ts = tp .<=. SigmaScheme ts

(.:::.) :: (Show info, Ord key) => M.Map key TpScheme -> M.Map key [(key,Tp)] -> (key -> info) -> ([TypeConstraint info], M.Map key [(key,Tp)])
(.:::.) = lift (flip (.::.))

(!::!) :: Tp -> TpScheme -> Tps -> info -> TypeConstraint info
(tp !::! ts) monos info = TC3 (Skolemize tp (monos, SigmaScheme ts) info)

(!:::!) :: (Show info, Ord key) => M.Map key TpScheme -> M.Map key Tp -> Tps -> (Tps -> key -> key -> info) -> ([TypeConstraint info], M.Map key Tp)
(as !:::! bs) monos info =
   let op key tp (cs, fm) =
          case M.lookup key as of
             Just ts ->
                let -- the key of the type scheme (equal, but may have a different range).
                    key' = head (filter (==key) (M.keys as)) {- this is the other name -}
                in ((tp !::! ts) monos (info monos key key') : cs, fm)
             Nothing -> (cs, M.insert key tp fm)
   in M.foldrWithKey op ([], M.empty) bs

(.<=.) :: Show info => Tp -> Sigma Predicates -> info -> TypeConstraint info
(tp .<=. ts) info = TC3 (Instantiate tp ts info)

(.<==.) :: (Show info, Ord key) => M.Map key (Sigma Predicates) -> M.Map key [(key,Tp)] -> (key -> info) -> ([TypeConstraint info], M.Map key [(key,Tp)])
(.<==.) = lift (flip (.<=.))

-- the old implicit instance constraint
(!<=!) :: Show info => Tps -> Tp -> Tp -> info -> TypeConstraint info
(!<=!) ms t1 t2 info = TC3 (Implicit t1 (ms, t2) info)

(!<==!) :: (Show info, Ord key) => Tps -> M.Map key Tp -> M.Map key [(key,Tp)] -> (key -> info) -> ([TypeConstraint info], M.Map key [(key,Tp)])
(!<==!) ms = lift (ms !<=!)

genConstraints :: Tps -> (key -> info) -> [(Int, (key, Tp))] -> TypeConstraints info
genConstraints monos infoF =
   let f (sv, (key, tp)) = TC3 (Generalize sv (monos, tp) (infoF key))
   in map f

predicate :: Predicate -> info -> TypeConstraint info
predicate p cinfo = TC2 (Prove p cinfo)