module Types.Types where

import Context
import Data.Char (isDigit)
import Data.List (intercalate,isPrefixOf)
import qualified Data.Set as Set
import qualified Data.Map as Map

type X = Int

data Type = LambdaT Type Type
          | VarT X
          | ADT String [Type]
          | EmptyRecord
          | RecordT (Map.Map String [Type]) Type
          | Super (Set.Set Type)
            deriving (Eq, Ord)

data Scheme = Forall [X] [Context Constraint] Type deriving (Eq, Ord, Show)

data Constraint = Type :=: Type
                | Type :<: Type
                | X :<<: Scheme
                  deriving (Eq, Ord, Show)

recordT :: [(String,Type)] -> Map.Map String [Type]
recordT fields =
    foldl (\r (x,t) -> Map.insertWith (++) x [t] r) Map.empty fields

recordOf :: [(String,Type)] -> Type
recordOf fields = RecordT (recordT fields) EmptyRecord

tipe t = ADT t []

int = tipe "Int"
float = tipe "Float"
time = tipe "Time"
date = tipe "Date"

char = tipe "Char"
bool = tipe "Bool"
text = tipe "Text"
order = tipe "Order"
string = tipe "String"

number = Super $ Set.fromList [ int, float, time ]
appendable t = Super $ Set.fromList [ string, text, listOf (VarT t) ]
comparable t = Super $ Set.fromList [ int, float, char, string, time, date ]

element   = tipe "Element"

listOf t   = ADT "List" [t]
signalOf t = ADT "Signal" [t]
tupleOf ts = ADT ("Tuple" ++ show (length ts)) ts
maybeOf t  = ADT "Maybe" [t]
eitherOf a b = ADT "Either" [a,b]
pairOf t = tupleOf [t,t]
point = pairOf int

infixr ==>
t1 ==> t2 = LambdaT t1 t2

infix 8 -:
name -: tipe = (,) name $ Forall [] [] tipe

parens = ("("++) . (++")")

instance Show Type where
  show t =
   let addParens (c:cs) =
           if notElem ' ' cs || c == '(' then c:cs else parens (c:cs)
   in case t of
      LambdaT t1@(LambdaT _ _) t2 -> parens (show t1) ++ " -> " ++ show t2
      LambdaT t1 t2 -> show t1 ++ " -> " ++ show t2
      VarT x -> 't' : show x
      ADT "List" [ADT "Char" []] -> "String"
      ADT "List" [tipe] -> "[" ++ show tipe ++ "]"
      ADT name cs ->
          if isTupleString name
              then parens . intercalate "," $ map show cs
              else name ++ concatMap ((' ':) . addParens . show) cs
      Super ts -> "{" ++ (intercalate "," . map show $ Set.toList ts) ++ "}"
      EmptyRecord -> "{}"
      RecordT fs t ->
        start ++ intercalate ", " (concatMap fields $ Map.toList fs) ++ " }"
           where field n s = n ++ " : " ++ show s
                 fields (n,ss) = map (field n) ss
                 start = case t of
                           EmptyRecord -> "{ "
                           _ -> "{ " ++ show t ++ " | "


isTupleString str = "Tuple" `isPrefixOf` str && all isDigit (drop 5 str)