module CSPM.Evaluator.Values (
    Value(..), UProc, UProcOperator, Proc(..), CSPOperator(..),
    ProcOperator(..), Event(..), EventSet,
    ScopeIdentifier(..), FunctionIdentifier(..),
    compareValues,
    procName, scopeId, annonymousScopeId,
    valueEventToEvent,
    noSave, maybeSave, removeThunk, lookupVar,
    tupleFromList,
    trimValueForProcessName,
    module Data.Array,
) where

import CSPM.DataStructures.Names
import CSPM.DataStructures.Syntax
import CSPM.DataStructures.Types
import CSPM.Evaluator.Monad
import CSPM.Evaluator.ProcessValues
import {-# SOURCE #-} qualified CSPM.Evaluator.ValueSet as S
import Data.Array
import qualified Data.Foldable as F
import Data.Hashable
import qualified Data.Map as M
import Prelude hiding (lookup)
import Util.Exception
import Util.Prelude

type UProc = UnCompiledProc
type UProcOperator = UnCompiledProcOperator

data Value =
    VInt Int
    | VChar Char
    | VBool Bool
    | VTuple (Array Int Value)
    -- | If A is a datatype clause that has 3 fields a b c then a runtime
    -- instantiation of this would be VDot [VDataType "A", a, b, c] where a,b
    -- and c can contain other VDots.
    | VDot [Value]
    -- The following two never appear on their own, they are always part of a 
    -- VDot (even if the VDot has no values).
    | VChannel Name
    | VDataType Name
    | VList [Value]
    | VMap (M.Map Value Value)
    | VSet S.ValueSet
    | VFunction FunctionIdentifier ([Value] -> EvaluationMonad Value)
    | VProc UProc
    | VThunk (EvaluationMonad Value)

-- | A disambiguator between different occurences of either processes or
-- functions. This works by storing the values that are bound (i.e. the free
-- variables the inner `thing` may depend on). This is used as a 'ProcName' and
-- for 'FunctionIdentifier's.
data ScopeIdentifier =
    SFunctionBind {
        scopeFunctionName :: Name,
        scopeFunctionArguments :: [[Value]],
        parentScopeIdentifier :: Maybe ScopeIdentifier
    }
    | SVariableBind {
        variablesBound :: [Value],
        parentScopeIdentifier :: Maybe ScopeIdentifier
    }

instance Eq ScopeIdentifier where
    SFunctionBind n1 vss1 p1 == SFunctionBind n2 vss2 p2 =
        n1 == n2 && vss1 == vss2 && p1 == p2
    SVariableBind vs1 p1 == SVariableBind vs2 p2 =
        vs1 == vs2 && p1 == p2

instance Hashable ScopeIdentifier where
    hash (SFunctionBind n1 args1 p1) =
        combine 1 (combine (hash n1) (combine (hash args1) (hash p1)))
    hash (SVariableBind vs1 p1) = combine 2 (combine (hash vs1) (hash p1))

instance Ord ScopeIdentifier where
    compare (SFunctionBind n1 vss1 p1) (SFunctionBind n2 vss2 p2) =
        compare n1 n2 `thenCmp` compare vss1 vss2 `thenCmp` compare p1 p2
    compare (SFunctionBind _ _ _) _ = LT
    compare _ (SFunctionBind _ _ _) = GT
    compare (SVariableBind vs1 p1) (SVariableBind vs2 p2) =
        compare vs1 vs2 `thenCmp` compare p1 p2

data FunctionIdentifier = 
    FBuiltInFunction {
        functionName :: Name,
        arguments :: [Value]
    }
    | FLambda {
        lambdaExpression :: Exp Name,
        parentFunctionIdentifier :: Maybe ScopeIdentifier
    }
    | FMatchBind {
        functionName :: Name,
        argumentGroups :: [[Value]],
        -- | The free variables this is bound in
        scopeIdentifier :: Maybe ScopeIdentifier
    }

instance Eq FunctionIdentifier where
    FBuiltInFunction n1 vs1 == FBuiltInFunction n2 vs2 =
        n1 == n2 && vs1 == vs2
    FLambda e1 parent1 == FLambda e2 parent2 =
        e1 == e2 && parent1 == parent2
    FMatchBind n1 args1 parent1 == FMatchBind n2 args2 parent2 =
        n1 == n2 && args1 == args2 && parent1 == parent2
    _ == _ = False

instance Hashable FunctionIdentifier where
    hash (FBuiltInFunction n1 vs) = combine 2 (combine (hash n1) (hash vs))
    hash (FLambda expr parent) =
        combine 3 (combine (hash (show expr)) (hash parent))
    hash (FMatchBind n vs parent) =
        combine 4 (combine (hash n) (combine (hash vs) (hash parent)))

instance Ord FunctionIdentifier where
    compare (FBuiltInFunction n1 args1) (FBuiltInFunction n2 args2) =
        compare n1 n2 `thenCmp` compare args1 args2
    compare (FBuiltInFunction _ _) _ = LT
    compare _ (FBuiltInFunction _ _) = GT
    compare (FLambda e1 parent1) (FLambda e2 parent2) =
        compare parent1 parent2 `thenCmp` compare e1 e2
    compare (FLambda _ _) _ = LT
    compare _ (FLambda _ _) = GT
    compare (FMatchBind n1 vs1 parent1) (FMatchBind n2 vs2 parent2) =
        compare n1 n2 `thenCmp` compare parent1 parent2 `thenCmp` compare vs1 vs2

tupleFromList :: [Value] -> Value
tupleFromList vs = VTuple $! listArray (0, length vs - 1) vs

-- | Given a program that yields a value, returns a second program that can be
-- inserted into the environment, but will cause the environment not to save
-- the actual value, but to recompute it everytime. This is useful for cheap,
-- to compute, but high cost in terms of memory, computations (like named
-- processes).
noSave :: EvaluationMonad Value -> EvaluationMonad Value
noSave prog = do
    pn <- getParentScopeIdentifier
    tok <- gets timedSection
    return $ VThunk $ modify (\ st -> st {
            CSPM.Evaluator.Monad.parentScopeIdentifier = pn,
            timedSection = tok }) prog

maybeSave :: Type -> EvaluationMonad Value -> EvaluationMonad Value
maybeSave TProc prog = noSave prog
maybeSave _ prog = prog

removeThunk :: Value -> EvaluationMonad Value
removeThunk (VThunk p) = p
removeThunk v = return v

lookupVar :: Name -> EvaluationMonad Value
lookupVar n = lookupVarMaybeThunk n >>= removeThunk

instance (Ix i, Hashable a) => Hashable (Array i a) where
    hash arr = F.foldr combine 0 (fmap hash arr)

instance Hashable Value where
    hash (VInt i) = combine 1 (hash i)
    hash (VBool b) = combine 2 (hash b)
    hash (VChar c) = combine 3 (hash c)
    hash (VTuple vs) = combine 5 (hash vs)
    hash (VDot vs) = combine 6 (hash vs)
    hash (VChannel n) = combine 7 (hash n)
    hash (VDataType n) = combine 8 (hash n)
    hash (VList vs) = combine 9 (hash vs)
    hash (VSet vset) = combine 10 (hash vset)
    -- We identify all functions (for process names) - see comment below in Eq.
    hash (VFunction id _) = combine 11 (hash id)
    hash (VProc p) = combine 12 (hash p)
    hash (VMap m) = combine 13 (hash (M.toList m))

instance Eq Value where
    VInt i1 == VInt i2 = i1 == i2
    VBool b1 == VBool b2 = b1 == b2
    VChar c1 == VChar c2 = c1 == c2
    VTuple vs1 == VTuple vs2 = vs1 == vs2
    VDot vs1 == VDot vs2 = vs1 == vs2
    VChannel n1 == VChannel n2 = n1 == n2
    VDataType n1 == VDataType n2 = n1 == n2
    VList vs1 == VList vs2 = vs1 == vs2
    VSet s1 == VSet s2 = s1 == s2
    VProc p1 == VProc p2 = p1 == p2
    VFunction id1 _ == VFunction id2 _ = id1 == id2
    VMap m1 == VMap m2 = m1 == m2
    
    v1 == v2 = False
    
-- | Implements CSPM comparisons (note that Ord Value does not).
compareValues :: Value -> Value -> Maybe Ordering
-- The following are all orderable and comparable
compareValues (VInt i1) (VInt i2) = Just (compare i1 i2)
compareValues (VChar c1) (VChar c2) = Just (compare c1 c2)
compareValues (VBool b1) (VBool b2) = Just (compare b1 b2)
compareValues (VTuple vs1) (VTuple vs2) =
    -- Tuples must be same length by type checking
    -- Tuples are ordered lexiographically
    let
        (l, u) = bounds vs1
        cmp ix | ix > u = EQ
        cmp ix = compare (vs1!ix) (vs2!ix) `thenCmp` cmp (ix+1)
    in Just (cmp 0)
compareValues (VList vs1) (VList vs2) =
    let
        -- for lists comparing means comparing prefixes
        cmp [] [] = Just EQ
        cmp [] (y:ys) = Just LT
        cmp (x:xs) [] = Just GT
        cmp (x:xs) (y:ys) | x == y = cmp xs ys
        cmp (x:xs) (y:ys) = 
            -- x != y, hence neither can be a prefix of the other
            Nothing
    in cmp vs1 vs2
compareValues (VSet s1) (VSet s2) = S.compareValueSets s1 s2
compareValues (VMap m1) (VMap m2) =
    let cmp v1 v2 =
            case compareValues v1 v2 of
                Just LT -> True
                Just EQ -> True
                _ -> False
    in if m1 == m2 then Just EQ
    else if M.isSubmapOfBy cmp m1 m2 then Just LT
    else if M.isSubmapOfBy cmp m2 m1 then Just GT
    else Nothing

-- The following can only be compared for equality, hence if they are not
-- equal we return Nothing.
compareValues (VChannel n1) (VChannel n2) = 
    if n1 == n2 then Just EQ else Nothing
compareValues (VDataType n1) (VDataType n2) = 
    if n1 == n2 then Just EQ else Nothing
compareValues (VDot vs1) (VDot vs2) =
    if vs1 == vs2 then Just EQ else Nothing

-- Every other possibility is invalid
compareValues v1 v2 = panic $ "Cannot compare two values"

instance Ord Value where
    -- This implementation is used for various internal measures, but not
    -- for implementing actual comparisons in CSPM.
    compare (VInt i1) (VInt i2) = compare i1 i2
    compare (VChar c1) (VChar c2) = compare c1 c2
    compare (VBool b1) (VBool b2) = compare b1 b2
    compare (VTuple vs1) (VTuple vs2) = compare vs1 vs2
    compare (VList vs1) (VList vs2) = compare vs1 vs2
    compare (VSet s1) (VSet s2) = compare s1 s2
    compare (VMap m1) (VMap m2) = compare m1 m2
    -- These are only ever used for the internal set implementation
    compare (VDot vs1) (VDot vs2) = compare vs1 vs2
    compare (VChannel n) (VChannel n') = compare n n'
    compare (VDataType n) (VDataType n') = compare n n'
    compare (VProc p1) (VProc p2) = compare p1 p2
    compare (VFunction id1 _) (VFunction id2 _) = compare id1 id2

    compare v1 v2 = panic $
        -- Must be as a result of a mixed set of values, which cannot happen
        -- as a result of type checking.
        "Internal sets - cannot order "
      
procName :: ScopeIdentifier -> ProcName
procName = ProcName

scopeId :: Name -> [[Value]] -> Maybe ScopeIdentifier -> ScopeIdentifier
scopeId n vss pn = SFunctionBind n (map (map trimValueForProcessName) vss) pn

annonymousScopeId :: [Value] -> Maybe ScopeIdentifier -> ScopeIdentifier
annonymousScopeId vss pn = SVariableBind (map trimValueForProcessName vss) pn

-- | This assumes that the value is a VDot with the left is a VChannel
valueEventToEvent :: Value -> Event
valueEventToEvent = UserEvent

errorThunk = panic "Trimmed value function evaluated"

trimFunctionIdentifier :: FunctionIdentifier -> FunctionIdentifier
trimFunctionIdentifier (FBuiltInFunction n args) =
    FBuiltInFunction n (map trimValueForProcessName args)
trimFunctionIdentifier (FLambda e p) = FLambda e p
trimFunctionIdentifier (FMatchBind n args p) =
    FMatchBind n (map (map trimValueForProcessName) args) p

trimValueForProcessName :: Value -> Value
trimValueForProcessName (VInt i) = VInt i
trimValueForProcessName (VChar c) = VChar c
trimValueForProcessName (VBool b) = VBool b
trimValueForProcessName (VTuple vs) = VTuple (fmap trimValueForProcessName vs)
trimValueForProcessName (VList vs) = VList (map trimValueForProcessName vs)
trimValueForProcessName (VSet s) =
    VSet $ S.fromList $ map trimValueForProcessName $ S.toList s
trimValueForProcessName (VMap m) = VMap $ M.fromList $
    map (\ (v1, v2) -> (trimValueForProcessName v1, trimValueForProcessName v2))
        (M.toList m)
trimValueForProcessName (VDot vs) = VDot $ map trimValueForProcessName vs
trimValueForProcessName (VChannel n) = VChannel n
trimValueForProcessName (VDataType n) = VDataType n
trimValueForProcessName (VFunction id _) =
    VFunction (trimFunctionIdentifier id) errorThunk
trimValueForProcessName (VProc p) = VProc (trimProcess p)