module Lang.LamIf.Semantics where

import FP
import MAAM
import Lang.LamIf.Values
import Lang.LamIf.Stamp
import Lang.LamIf.Syntax
import Lang.LamIf.Time

data LamIfState val = LamIfState
  { env  Env
  , κAddr  Maybe ExpAddr
  , time  Time
  , store  Store val
  , κStore  KStore val
  }
  deriving (Eq,Ord)
makeLenses ''LamIfState
makePrettyRecord ''LamIfState
instance (POrd val)  POrd (LamIfState val) where
  () = poCompareFromLte $ \ (LamIfState ρ₁ κl₁ τ₁ σ₁ κσ₁) (LamIfState ρ₂ κl₂ τ₂ σ₂ κσ₂)  
    meets [ρ₁  ρ₂,κl₁  κl₂,τ₁  τ₂,σ₁  σ₂,κσ₁  κσ₂]

data LamIfEnv = LamIfEnv
  { currentExp  Exp
  , timeParam  TimeParam
  }
makeLenses ''LamIfEnv

class 
  ( POrd val
  , JoinLattice val
  , Val val
  , Monad m
  , MonadState (LamIfState val) m
  , MonadJoinLattice m
  )  MonadLamIf val m | m  val

type ParamsM m = ReaderT LamIfEnv m

atom  (MonadLamIf val m)  Atom  ParamsM m AtomVal
atom (AInteger i) = return $ AtomValInt i
atom (AVar x) = do
  ρ  getL envL
  case ρ # x of
    Nothing  mbot
    Just l  return $ AtomValAddr l
atom (ALam x e) = do
  ρ  getL envL
  ce  askL currentExpL
  τ  getL (lexicalL  timeL)
  return $ AtomValClo $ Closure ce x e ρ τ

atomVal  (MonadLamIf val m)  AtomVal  ParamsM m val
atomVal (AtomValInt i) = return $ intI i
atomVal (AtomValAddr l) = do
  σ  getL storeL
  case σ # l of
    Nothing  mbot
    Just v  return v
atomVal (AtomValClo clo) = return $ cloI clo
atomVal (AtomValOp o av₁ av₂) = do
  v₁  atomVal av₁
  v₂  atomVal av₂
  return $ δ o v₁ v₂

push  (MonadLamIf val m)  Frame  ParamsM m ()
push fr = do
  τ  getL timeL
  e  askL currentExpL
  let κl = Just $ ExpAddr e τ
  κl'  getL κAddrL
  modifyL κStoreL $ \ κσ  κσ  dict [κl  frameI (fr,κl')]
  putL κAddrL κl

pop  (MonadLamIf val m)  ParamsM m Frame
pop = do
  κl  getL κAddrL
  κσ  getL κStoreL
  case κσ # κl of
    Nothing  mbot
    Just v  do
      (fr,κl')  mset $ frameE v
      putL κAddrL κl'
      return fr

bind  (MonadLamIf val m)  Name  val  ParamsM m ()
bind x v = do
  τ  getL timeL
  let l = VarAddr x τ
  modifyL envL $ \ ρ  insertDict x l ρ 
  modifyL storeL $ \ σ  σ  dict [l  v]

tickL  (MonadLamIf val m)  Exp  (Lens LamIfEnv (Maybe ))  (Lens (LamIfState val) [Exp])  ParamsM m ()
tickL ce kL τL = do
  k  askL kL
  modifyL τL $ \ τ  elimMaybe id firstN k $ ce:τ

tickO  (MonadLamIf val m)  Exp  ParamsM m ()
tickO ce = do
  tickL ce (lexicalObjDepthL  timeParamL) (objL  lexicalL  timeL)
  tickL ce (dynamicObjDepthL  timeParamL) (objL  dynamicL  timeL)

refine  (MonadLamIf val m)  Name  val  ParamsM m ()
refine x v = do
  ρ  getL envL
  σ  getL storeL
  case ρ # x of
    Nothing  mbot
    Just l  putL storeL $ insertDict l v σ

delZeroM  (MonadLamIf val m)  Name  ParamsM m ()
delZeroM x = do
  ρ  getL envL
  case ρ # x of
    Nothing  mbot
    Just l  modifyL storeL $ modifyDict delZero l

stackReturn  (MonadLamIf val m)  Maybe Atom  AtomVal  ParamsM m Exp
stackReturn aM v = do
  fr  pop
  case fr of
    IfH e₂ e₃ ρ   do
      putL envL ρ
      putL (lexicalL  timeL) 
      b  mset  isZeroE *$ atomVal v
      when b $
        whenMaybe (view (aVarL  justL) aM) $ \ x  refine x $ intI $ 𝕫 0
      when (not b) $
        whenMaybe (view (aVarL  justL) aM) $ \ x  delZeroM x
      return $ if b then e₂ else e₃
    LetH x e₂ ρ   do
      putL envL ρ
      putL (lexicalL  timeL) 
      bind x *$ atomVal v
      return e₂
    OpL o e₂ ρ   do
      putL envL ρ
      putL (lexicalL  timeL) 
      push $ OpR v o
      return e₂
    OpR v₁ o  do
      stackReturn Nothing $ AtomValOp o v₁ v
    AppL e₂ ρ   do
      putL envL ρ
      putL (lexicalL  timeL) 
      push $ AppR v
      return e₂
    AppR v₁  do
      Closure ce x e ρ   mset  cloE *$ atomVal v₁
      putL envL ρ
      putL (lexicalL  timeL) 
      tickO ce
      bind x *$ atomVal v
      return e

tickK  (MonadLamIf val m)  ParamsM m ()
tickK = do
  ce  askL currentExpL
  tickL ce (lexicalCallDepthL  timeParamL) (callL  lexicalL  timeL)
  tickL ce (dynamicCallDepthL  timeParamL)  (callL  dynamicL  timeL)

step  (MonadLamIf val m)  TimeParam  Exp  m Exp
step ps e = runReaderTWith (LamIfEnv e ps) $ do
  tickK
  case expRawExp e of
    EAtom a  do
      v  atom a
      stackReturn (Just a) v
    EIf e₁ e₂ e₃  do
      ρ  getL envL
        getL (lexicalL  timeL)
      push $ IfH e₂ e₃ ρ 
      return e₁
    ELet x e₁ e₂  do
      ρ  getL envL
        getL (lexicalL  timeL)
      push $ LetH x e₂ ρ 
      return e₁
    EOp o e₁ e₂  do
      ρ  getL envL
        getL (lexicalL  timeL)
      push $ OpL o e₂ ρ 
      return e₁
    EApp e₁ e₂  do
      ρ  getL envL
        getL (lexicalL  timeL)
      push $ AppL e₂ ρ 
      return e₁

gc  (MonadLamIf val m)  Exp  m ()
gc = undefined

class 
  ( MonadLamIf val m
  , Inject ι ς
  , GaloisTransformer ς m
  )  ExecutionLamIf val ι ς m | m  val,m  ς

type LFPLamIf ς = (POrd (ς Exp),JoinLattice (ς Exp),Difference (ς Exp))

ex  (ExecutionLamIf val ι ς' m,LFPLamIf ς)  TimeParam  ς Exp  ς' Exp  (Exp  m Exp)  ς Exp  ς Exp
ex ps iso f = collect (isoFrom iso  γGT (f * step ps)  isoTo iso)

exDiffs  (ExecutionLamIf val ι ς' m,LFPLamIf ς)  TimeParam  ς Exp  ς' Exp  (Exp  m Exp)  ς Exp  Stream (ς Exp)
exDiffs ps iso f = collectDiffs (isoFrom iso  γGT (f * step ps)  isoTo iso)