module CSPM.Evaluator (
    evaluateExp, evaluateDecl, evaluateFile,
    addToEnvironment, maybeProcessNameToProcess,
    
    initEvaluator, runFromStateToState,
    EvaluatorOptions(..), defaultEvaluatorOptions,
    ProfilerOptions(..), defaultProfilerOptions,
    EvaluationMonad, runEvaluator, EvaluationState,
    module CSPM.Evaluator.ProcessValues,
    module CSPM.Evaluator.Values,
    module CSPM.Evaluator.ValueSet,

    ProfilingData(..), profilingData,
) where

import CSPM.DataStructures.Names
import CSPM.DataStructures.Syntax
import CSPM.Evaluator.BuiltInFunctions
import CSPM.Evaluator.DeclBind
import CSPM.Evaluator.Environment
import CSPM.Evaluator.Expr
import CSPM.Evaluator.File
import CSPM.Evaluator.Monad
import CSPM.Evaluator.ProcessValues
import CSPM.Evaluator.Profiler
import CSPM.Evaluator.Values
import CSPM.Evaluator.ValueSet
import qualified Data.Foldable as F
import Util.Annotated

data EvaluatorOptions = EvaluatorOptions {
        runtimeRangeChecks :: Bool,
        profilerOptions :: ProfilerOptions
    }

defaultEvaluatorOptions :: EvaluatorOptions
defaultEvaluatorOptions = EvaluatorOptions {
        runtimeRangeChecks = True,
        profilerOptions = defaultProfilerOptions
    }

runFromStateToState :: EvaluationState -> EvaluationMonad a -> 
    (a, EvaluationState)
runFromStateToState st prog = runEvaluator st $ do
    r <- prog
    s <- getState
    return (r, s)

-- | The environment to use initially. This uses the IO monad as 
-- the EvaluationMonad cannot be used without a valid environment.
initEvaluator :: EvaluatorOptions -> IO EvaluationState
initEvaluator options = do
    profilerState <- initialProfilerState (profilerOptions options)
    let initialState = EvaluationState {
                environment = new,
                CSPM.Evaluator.Monad.parentScopeIdentifier = Nothing,
                currentExpressionLocation = Unknown,
                timedSection = Nothing,
                profilerState = profilerState,
                doRuntimeRangeChecks = runtimeRangeChecks options
            }
    return $! runEvaluator initialState (injectBuiltInFunctions getState)

evaluateExp :: TCExp -> EvaluationMonad Value
evaluateExp e = eval e

-- | Evaluates the declaration but doesn't add it to the current environment.
evaluateDecl :: TCDecl -> EvaluationMonad [(Name, EvaluationMonad Value)]
evaluateDecl d = bindDecls [d]

-- | Evaluates the declaration but doesn't add it to the current environment.
evaluateFile :: TCCSPMFile -> EvaluationMonad [(Name, EvaluationMonad Value)]
evaluateFile ms = bindFile ms

addToEnvironment :: [(Name, EvaluationMonad Value)] ->
    EvaluationMonad EvaluationState
addToEnvironment bs = addScopeAndBindM bs getState

-- | Attempts to convert a process name to a process, if possible.
maybeProcessNameToProcess :: ProcName -> EvaluationMonad (Maybe UProc)
maybeProcessNameToProcess (pn@(ProcName (SFunctionBind fn [args] Nothing))) = do
    -- Evaluate the function again
    VFunction _ func <- lookupVar fn
    let checkArgument (VInt i) = True
        checkArgument (VChar c) = True
        checkArgument (VBool b) = True
        checkArgument (VTuple vs) = F.and $ fmap checkArgument vs
        checkArgument (VList vs) = F.and $ fmap checkArgument vs
        checkArgument (VSet s) = F.and $ fmap checkArgument (toList s)
        checkArgument (VDot vs) = F.and $ fmap checkArgument vs
        checkArgument (VChannel n) = True
        checkArgument (VDataType n) = True
        checkArgument (VFunction id _) = False
        checkArgument (VProc p) = False
    if and (map checkArgument args) then do
        v <- func args
        return $ Just $ PProcCall pn (let VProc p = v in p)
    else return Nothing
maybeProcessNameToProcess _ = return Nothing

profilingData :: EvaluationMonad ProfilingData
profilingData = getProfilingData