{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Funcons.Entities (
    -- * Accessing entities
        -- ** mutables
        getMut, putMut, getMutPatt, putMutTerm,
        -- ** inherited
        getInh, withInh, getInhPatt, withInhTerm,
        -- ** control
        raiseSignal, receiveSignals, raiseTerm, receiveSignalPatt,
        withControlTerm, getControlPatt,
        -- ** output
        writeOut, readOut, writeOutTerm, readOutPatt,
        -- ** input
        matchInput, withExtraInput,withExactInput,
            withExtraInputTerms, withExactInputTerms,
    -- * Default entity values
        EntityDefaults, EntityDefault(..), setEntityDefaults
    )where

import Funcons.Types
import Funcons.MSOS
import Funcons.Substitution
import Funcons.Exceptions
import Funcons.Patterns

import Control.Applicative
import Control.Arrow
import qualified Data.Map as M
import Data.Text

-- defaults
-- | A list of 'EntityDefault's is used to declare (and possibly initialise)
-- entities.
type EntityDefaults = [EntityDefault]
-- | Default values of entities can be specified for /inherited/ 
-- and /mutable/ entities. 
data EntityDefault  = DefMutable Name Funcons
                    | DefInherited Name Funcons
                    -- | For the purpose of unit-testing it is advised to notify an interpreter of the existence of control, output and input entities as well.
                    | DefOutput Name
                    | DefControl Name
                    | DefInput Name

setEntityDefaults :: EntityDefaults -> MSOS StepRes -> MSOS StepRes
setEntityDefaults [] msos = msos
setEntityDefaults ((DefMutable nm f):rest) msos =
    liftRewrite (rewriteFuncons f) >>= \case
        ValTerm [v] -> putMut nm v >> setEntityDefaults rest msos
        ValTerm vs  -> liftRewrite $ exception f "default value evaluates to a sequence"
        _           -> liftRewrite $ exception f "default value requires steps to evaluate"
setEntityDefaults ((DefInherited nm f):rest) msos =
    liftRewrite (rewriteFuncons f) >>= \case
        ValTerm vs  -> withInh nm vs (setEntityDefaults rest msos)
        _           -> liftRewrite $ exception f "default value requires steps to evaluate"
setEntityDefaults ((DefControl nm):rest) msos =
    withControl nm Nothing (setEntityDefaults rest msos)
setEntityDefaults (_:rest) msos = setEntityDefaults rest msos

----------------------------------------------------
--- accessing entities

-- mutables

emptyMUT :: Mutable
emptyMUT = M.empty

giveMUT :: MSOS Mutable
giveMUT = MSOS $ \ctxt mut -> return (Right (mut_entities mut), mut, mempty)

-- | Get the value of some mutable entity.
getMut :: Name -> MSOS Values
getMut key = do  rw <- giveMUT
                 case M.lookup key rw of
                    Nothing -> return null__
                    Just v  -> return v

-- | Variant of 'getMut' that performs pattern-matching.
getMutPatt :: Name -> VPattern -> Env -> MSOS Env
getMutPatt nm pat env = do
    val <- getMut nm
    liftRewrite (vMatch val pat env)

modifyMUT :: Name -> (Values -> Values) -> MSOS ()
modifyMUT key f = do    rw <- giveMUT
                        newMUT (M.alter up key rw)
 where  up Nothing  = Just (f null__)
        up (Just x) = Just (f x)

-- | Set the value of some mutable entity.
putMut :: Name -> Values -> MSOS ()
putMut key v = do rw <- giveMUT
                  newMUT (M.insert key v rw)

-- | Variant of 'putMut' that applies substitution.
putMutTerm :: Name -> FTerm -> Env -> MSOS ()
putMutTerm nm term env = liftRewrite (subsAndRewritesToValue term env) >>= putMut nm

newMUT :: Mutable -> MSOS ()
newMUT rw = MSOS $ \ctxt mut-> return (Right(), mut {mut_entities = rw}, mempty)


-- input
-- | Consume a single value from the input stream.
-- | Throws an 'unsufficient input' exception, if not enough input is available.
consumeInput :: Name -> MSOS Funcons
consumeInput nm = MSOS $ \ctxt mut ->
    case M.lookup nm (inp_es mut) of
      Just (vss, mreadM) -> wrapAttempt ctxt mut vss mreadM
      Nothing            -> wrapAttempt ctxt mut [] (Just (def_fread ctxt nm))
  where
    wrapAttempt ctxt mut vss mreadM = case attemptConsume vss of
      Just (v,vss') -> return
        (Right (FValue v), mut {inp_es = M.insert nm (vss',mreadM) (inp_es mut)},mempty)
      Nothing       -> case mreadM of
        Nothing -> return
          (Left (ctxt2exception (InsufficientInput nm) ctxt), mut, mempty)
        Just readM -> do    v <- readM
                            return (Right v, mut, mempty)

    attemptConsume :: [[a]] -> Maybe (a,[[a]])
    attemptConsume []           = Nothing
    attemptConsume ((v:vs):vss) = Just (v,vs:vss)
    attemptConsume ([]:vss)     = second ([]:) <$> attemptConsume vss

-- | Provides /extra/ values to a certain input entity, available
-- to be consumed by the given 'MSOS' computation argument.
withExtraInput :: Name -> [Values] -> MSOS a -> MSOS a
withExtraInput = withInput False

-- | Provides an /exact/ amount of input for some input entity, 
-- that is to be /completely/ consumed by the given 'MSOS' computation.
-- If less output is consumed a 'insufficient input consumed' exception
-- is thrown.
withExactInput :: Name -> [Values] -> MSOS a -> MSOS a
withExactInput = withInput True

withInput :: Bool -> Name -> [Values] -> MSOS a -> MSOS a
withInput isExactInput nm vs (MSOS f) = MSOS $ \ctxt mut -> do
  let provideInput newInp mreadM = do
        (a,mut',wr') <- f ctxt mut{ inp_es = M.insert nm newInp (inp_es mut)}
        let (res,vss'') = case (inp_es mut') M.! nm of
               ([]:vss',_) -> (a, vss')
               _           -> (Left(ctxt2exception(InsufficientInputConsumed nm) ctxt), vss'')
        return (res, mut' {inp_es = M.insert nm (vss'',mreadM) (inp_es mut')}, wr')
  case M.lookup nm (inp_es mut) of
    Just (vss, mreadM) ->
      provideInput (vs:vss, if isExactInput then Nothing else mreadM) mreadM
    Nothing -> provideInput ([vs], Nothing) Nothing

-- | Variant of 'consumeInput' that matches the given `VPattern` to the consumed
-- value in the given 'Env'. 
matchInput :: Name -> VPattern -> Env -> MSOS Env
matchInput nm pat env = do
    fs <- consumeInput nm
    vs <- liftRewrite (rewritesToValues fs)
    liftRewrite (vsMatch vs [pat] env)

-- | Variant of 'withExtraInput' that performs substitution.
withExtraInputTerms = withInputTerms False
-- | Variant of 'withExactInput' that performs substitution.
withExactInputTerms = withInputTerms True

withInputTerms :: Bool -> Name -> [FTerm] -> Env -> MSOS a -> MSOS a
withInputTerms b nm fs env msos = do
    vs <- liftRewrite (mapM (flip subsAndRewritesToValue env) fs)
    withInput b nm vs msos

-- control
-- | Receive the value of a control entity from a given 'MSOS' computation.
receiveSignals :: [Name] -> MSOS a -> MSOS (a, [Maybe Values])
receiveSignals keys (MSOS f) = MSOS (\ctxt mut -> do
    (e_a, mut1, wr1) <- f ctxt mut
    case e_a of
        Left err -> return (Left err, mut1, wr1)
        Right a  -> return $
          (Right (a, Prelude.map (find (ctrl_entities wr1)) keys)
                 , mut1, wr1 {ctrl_entities = Prelude.foldr M.delete (ctrl_entities wr1) keys}))
  where find m key = maybe Nothing id $ M.lookup key m

-- | Variant of 'receiveSignal' that performs pattern-matching.
receiveSignalPatt :: Maybe Values -> Maybe VPattern -> Env -> MSOS Env
receiveSignalPatt mval mpat env = liftRewrite (vMaybeMatch mval mpat env)

-- | Signal a value of some control entity.
raiseSignal :: Name -> Values -> MSOS ()
raiseSignal nm v = MSOS (\ctxt mut -> return
                        (Right (), mut, mempty { ctrl_entities = singleCTRL nm v}))

-- | Variant of 'raiseSignal' that applies substitution.
raiseTerm :: Name -> FTerm -> Env -> MSOS ()
raiseTerm nm term env = liftRewrite (subsAndRewritesToValue term env) >>= raiseSignal nm

-- downwards control
-- | Set the value of an downwards control entity. 
-- The new value is /only/ set for 'MSOS' computation given as a third argument.
withControl :: Name -> Maybe Values -> MSOS a -> MSOS a
withControl key mfct (MSOS f) = MSOS (\ctxt mut  ->
    let ctxt' = ctxt { dctrl_entities = M.insert key mfct (dctrl_entities ctxt) }
    in f ctxt' mut)

withControlTerm :: Name -> Maybe FTerm -> Env -> MSOS a -> MSOS a
withControlTerm nm mterm env msos = do
    mfct <- case mterm of
              Nothing   -> return Nothing
              Just term -> Just <$> liftRewrite (substitute_signal term env)
    withControl nm mfct msos

-- | Get the value of an down control entity.
getControl :: Name -> MSOS (Maybe Values)
getControl key = do
  ro <- giveCTRL
  case M.lookup key ro of
    Nothing   -> return Nothing
    Just mv   -> return mv

-- | Version of 'getControl' that applies pattern-matching.
getControlPatt :: Name -> Maybe VPattern -> Env -> MSOS Env
getControlPatt nm mpat env = do
    mpat' <- liftRewrite $ maybe (return Nothing) (fmap Just . flip substitute_patt_signal env) mpat
    mfct <- getControl nm
    liftRewrite (eval_catch (vMaybeMatch mfct mpat' env) >>= \case
      Left (_,_,PatternMismatch _)  -> return env --TODO suboptimal 
      Left exc                      -> rewrite_rethrow exc
      Right env'                    -> return env')


-- inherited

-- | Get the value of an inherited entity.
getInh :: Name -> MSOS [Values]
getInh key = do  ro <- giveINH
                 case M.lookup key ro of
                    Nothing -> return [null__]
                    Just vs -> return vs

-- | Version of 'getInh' that applies pattern-matching.
getInhPatt :: Name -> [VPattern] -> Env -> MSOS Env
getInhPatt nm pats env = do
    vals <- getInh nm
    liftRewrite (vsMatch vals pats env)

-- | Set the value of an inherited entity. 
-- The new value is /only/ set for 'MSOS' computation given as a third argument.
withInh :: Name -> [Values] -> MSOS a -> MSOS a
withInh key v (MSOS f) = MSOS (\ctxt mut  ->
        let ctxt' = ctxt { inh_entities = M.insert key v (inh_entities ctxt) }
        in f ctxt' mut)

-- | Variant of 'withInh' that performs substitution.
withInhTerm :: Name -> FTerm -> Env -> MSOS a -> MSOS a
withInhTerm nm term env msos = do
    v <- liftRewrite $ (subsAndRewritesToValues term env)
    withInh nm v msos

-- output
-- | Add new values to a certain output entity.
writeOut :: Name -> [Values] -> MSOS ()
writeOut key vs = MSOS $ \ctxt mut -> return (Right (), mut
                            ,mempty { out_entities = M.singleton key vs })

-- | Variant of 'writeOut' that applies substitution.
writeOutTerm :: Name -> FTerm -> Env -> MSOS ()
writeOutTerm nm term env =
  liftRewrite (subsAndRewritesToValues term env) >>= writeOut nm

-- | Read the values of a certain output entity. The output is obtained
-- from the 'MSOS' computation given as a second argument.
readOut :: Name -> MSOS a -> MSOS (a,[Values])
readOut key msos = readOuts msos >>=
                        return . fmap (maybe [] id . M.lookup key)

-- | Variant of 'readOut' that performs pattern-matching.
readOutPatt :: Name -> VPattern -> MSOS Env -> MSOS Env
readOutPatt key pat msos = do
    (env, vals) <- readOut key msos
    liftRewrite (vMatch (ADTVal "list" (Prelude.map FValue vals)) pat env)