{-# LANGUAGE LambdaCase #-}

module Funcons.Substitution (
    Env(..), subsAndRewritesInEnv, subsAndRewritesToValueInEnv, subsAndRewritesToValue, subsAndRewritesToValuesInEnv, subsAndRewritesToValues,
    envInsert, emptyEnv,
    Levelled(..), substitute_signal, substitute,rewriteTermTo, stepTermTo,
    envRewrite, envStore, lifted_envRewrite, lifted_envStore,
    fLevel, fsLevel, vLevel, vsLevel, envLookup
    ) where

import Funcons.Types
import Funcons.MSOS

import Control.Applicative
import Control.Monad
import Data.Monoid
import qualified Data.Map as M

-- | An environment mapping meta-variables to funcon terms.
-- This environment is used by a substitution procedure to transform
-- funcon terms from 'FTerm' representation to 'Funcons'.
type Env = M.Map MetaVar Levelled
data Levelled   = TypeTerm ComputationTypes
                | TypesTerm [ComputationTypes]
                | ValueTerm Values
                | ValuesTerm [Values]
                | FunconTerm Funcons (Maybe (MSOS StepRes)) {- cached step -}
                | FunconsTerm [Funcons]

-- | The empty substitution environment.
-- Bindings are inserted by pattern-matching.
emptyEnv = M.empty

envLookup :: Env -> MetaVar -> Rewrite Levelled
envLookup env k = case M.lookup k env of
                    Nothing -> internal ("no substitution for variable: " <> k)
                    Just lf -> return lf

envInsert :: MetaVar -> Levelled -> Env -> Env
envInsert = M.insertWith op
  where op new old = new --TODO somehow unify values
  {- 
        isWildCard (ValueTerm VAny) = True
        isWildCard (ValuesTerm [VAny]) = True
        isWildCard (FunconTerm (FValue VAny) _) = True
        isWildCard (FunconsTerm [FValue VAny]) = True
        isWildCard _ = False
  -}


{-
tsLevel :: Levelled -> Rewrite [ComputationTypes]
tsLevel = \case TypesTerm ts  -> return ts
                TypeTerm t    -> return [t]
                ValuesTerm vs 
                  | all isSort_ vs -> return $ map downcastSort (map FValue vs)
                  | otherwise     -> internal "not bound to types"
                ValueTerm v | isSort_ v-> return $ [downcastSort v]
-}

vsLevel :: Levelled -> Rewrite [Values]
vsLevel = \case FunconsTerm fs
                  | all (not.hasStep) fs  -> return (map downcastValue fs)
                  | otherwise           -> internal "not bound to values"
                FunconTerm (FValue v) _ -> return [v]
                FunconTerm f         _  -> internal ("not bound to values")
                ValuesTerm vs           -> return vs
                ValueTerm v             -> return [v]
                TypeTerm t              -> return [ComputationType t]
                TypesTerm ts            -> return (map ComputationType ts)

vLevel :: Levelled -> Rewrite Values
vLevel =  \case FunconsTerm [FValue v]  -> return v
                FunconsTerm fs          -> internal "not bound to values"
                FunconTerm (FValue v) _ -> return v
                FunconTerm f         _  -> internal ("not bound to value")
                ValuesTerm [v]          -> return v
                ValuesTerm vs           -> internal ("not bound to value")
                ValueTerm v             -> return v
                TypeTerm t              -> return (ComputationType t)
                TypesTerm [t]           -> return (ComputationType t)
                TypesTerm _             -> internal "not bound to a value"

fsLevel :: Levelled -> Rewrite [Funcons]
fsLevel = \case FunconsTerm f       -> return f
                FunconTerm f _      -> return [f]
                ValuesTerm vs       -> return (map FValue vs)
                ValueTerm v         -> return [FValue v]
                TypesTerm ts        -> return (map sort_ ts)
                TypeTerm t          -> return [sort_ t]

fLevel k = \case
  FunconsTerm [f]   -> return f
  FunconsTerm fs    -> internal "not bound to a funcon"
  FunconTerm f _    -> return f
  ValuesTerm [v]    -> return (FValue v)
  ValuesTerm vs     -> internal "not bound to a funcon"
  ValueTerm v       -> return (FValue v)
  TypeTerm t        -> return (sort_ t)
  TypesTerm [t]     -> return (sort_ t)
  TypesTerm ts      -> internal "not bound to a funcon"

-- substitution

substitute_signal :: FTerm -> Env -> Rewrite Values
substitute_signal t env = case t of
  TVar k        -> vLevel $ envMaybeLookup env k
  TName nm      -> return (ADTVal nm [])
  TApp nm terms -> ADTVal nm . map FValue  <$> subs_flatten terms env
  TSeq ts       -> internal "top level sequence during substitution (signal)"
--  TList ts      -> List <$> subs_flatten ts env
  TSet ts       -> setval_ <$> subs_flatten ts env
  TMap ts       -> mapval_ <$> subs_flatten ts env
  TBinding x y  -> tuple <$> subs_flatten [x,y] env
  TFuncon (FValue v) -> return v
  TAny          -> return VAny
  _             -> internal "missing case in substitute_signal"
  where subs_flatten :: [FTerm] -> Env -> Rewrite [Values]
        subs_flatten terms env = (concat <$>) $ forM terms $ \case
              TVar k  -> vsLevel $ envMaybeLookup env k
              TSeq ts -> mapM (flip substitute_signal env) ts
              term    -> (:[]) <$> substitute_signal term env

        envMaybeLookup :: Env -> MetaVar -> Levelled
        envMaybeLookup env k = case M.lookup k env of
                            Nothing -> ValueTerm VAny
                            Just lf -> lf


substitute :: FTerm ->  Env -> Rewrite Funcons
substitute (TVar k) env = envLookup env k >>= fLevel k
substitute (TName nm) env = return $ FName nm
substitute (TApp nm terms) env = FApp nm <$> subsFlatten terms env
substitute (TSeq terms) env = internal ("top level sequence during substitution: " ++ show terms)
-- e.g. print(V+:values+) -- standard-out![V+] -> ()
--substitute (TList terms) env = FList <$> subsFlatten terms env 
substitute (TSet terms)  env = FSet <$> subsFlatten terms env
substitute (TMap terms)  env = FMap <$> subsFlatten terms env
substitute (TBinding x y) env = FBinding <$> substitute x env <*> subsFlatten [y] env
substitute (TFuncon f) env = return f
substitute (TSortUnion t1 t2) env = FSortUnion <$> substitute t1 env <*> substitute t2 env
substitute (TSortInter t1 t2) env = FSortInter <$> substitute t1 env <*> substitute t2 env
substitute (TSortComplement t1) env = FSortComplement <$> substitute t1 env
substitute (TSortSeq t1 op) env = flip FSortSeq op <$> substitute t1 env
substitute (TSortPower t1 t2) env = FSortPower <$> substitute t1 env <*> substitute t2 env
substitute (TSortComputes t1) env = FSortComputes <$> substitute t1 env
-- special case for thunks/continuations
substitute (TSortComputesFrom (TSeq []) t2) env =
    FSortComputesFrom nulltype_ <$> substitute t2 env
substitute (TSortComputesFrom t1 t2) env =
    FSortComputesFrom <$> substitute t1 env <*> substitute t2 env
substitute TAny env = internal "missing substitution (wildcard)"

-- flatten out sequence-variables
subsFlatten :: [FTerm] -> Env -> Rewrite [Funcons]
subsFlatten terms env = concat <$> (forM terms $ \case
                            TVar k  -> envLookup env k >>= fsLevel
                            TSeq ts -> subsFlatten ts env
                            term    -> (:[]) <$> substitute term env)

-- Version of subsAndRewritesToValue that 'caches' computational steps:
-- If after rewriting a computational step is produced the
--  step and associated funcon replace the term in the environment
--  (if the term consists of just a meta-variable)
-- This function immediately returns a computational step when
--  a given term has a cached step
-- If the term is not just a meta-variable, this function behaves normally
subsAndRewritesInEnv :: FTerm -> Env -> Rewrite (Rewritten, Env)
subsAndRewritesInEnv (TVar x) env
  | Just (FunconTerm f (Just step)) <- M.lookup x env =
      return $ (CompTerm f step, env)
subsAndRewritesInEnv term env  = do
  f <- substitute term env
  res <- rewriteFunconsWcount f
  case term of
    TVar x -> case res of
     ValTerm vs       -> return (res, envInsert x (ValuesTerm vs) env)
     CompTerm f step  -> return (res, envInsert x (FunconTerm f (Just step)) env)
    _      -> return (res, env)

subsAndRewritesToValue :: FTerm -> Env -> Rewrite Values
subsAndRewritesToValue f env = fst <$> subsAndRewritesToValueInEnv f env

-- Important optimisation:
-- If the given term is a var, 
--    update the env to store the rewritten value.
subsAndRewritesToValueInEnv :: FTerm -> Env -> Rewrite (Values, Env)
subsAndRewritesToValueInEnv (TVar x) env
  -- assumed to be already rewritten (because cached step is present)
  | Just (FunconTerm f (Just step)) <- M.lookup x env = rewriteToValErr
subsAndRewritesToValueInEnv term env = do
  f <- substitute term env
  v <- rewritesToValue f
  case term of TVar var  -> return (v, envInsert var (ValueTerm v) env)
               _         -> return (v, env)

subsAndRewritesToValues :: FTerm -> Env -> Rewrite [Values]
subsAndRewritesToValues f env = fst <$> subsAndRewritesToValuesInEnv f env

subsAndRewritesToValuesInEnv :: FTerm -> Env -> Rewrite ([Values], Env)
subsAndRewritesToValuesInEnv (TVar x) env
  -- assumed to be already rewritten (because cached step is present)
  | Just (FunconTerm f (Just step)) <- M.lookup x env = rewriteToValErr
subsAndRewritesToValuesInEnv term env = do
  fs  <- subsFlatten [term] env
  vs  <- concat <$> mapM rewritesToValues fs
  case term of TVar var  -> return (vs, envInsert var (ValuesTerm vs) env)
               _         -> return (vs, env)


-- | Variant of 'rewriteTo' that applies substitution.
rewriteTermTo :: FTerm -> Env -> Rewrite Rewritten
rewriteTermTo fterm env = subsFlatten [fterm] env >>= \case
  [f] -> rewriteTo f
  fs  -> rewriteSeqTo fs

-- | Variant of 'stepTo' that applies substitution.
stepTermTo :: FTerm -> Env -> MSOS StepRes
stepTermTo fterm env = liftRewrite (subsFlatten [fterm] env) >>= \case
  [f] -> stepTo f
  fs  -> stepSeqTo fs

lifted_envStore :: MetaVar -> FTerm -> Env -> MSOS Env
lifted_envStore m t e = liftRewrite (envStore m t e)

envStore :: MetaVar -> FTerm -> Env -> Rewrite Env
envStore var term env = substitute term env >>= \case
    (FValue v)    -> return $ envInsert var (ValueTerm v) env
    fct           -> return $ envInsert var (FunconTerm fct Nothing) env

lifted_envRewrite :: MetaVar -> Env -> MSOS Env
lifted_envRewrite m e = liftRewrite (envRewrite m e)

-- | Apply as many rewrites as possible to the term bound to the
-- given variable in the meta-environment
envRewrite :: MetaVar -> Env -> Rewrite Env
envRewrite var env = do
    envLookup env var >>= \case
      FunconTerm fct Nothing -> rewriteFunconsWcount fct >>= \case
        ValTerm vs    -> return $ envInsert var (ValuesTerm vs) env
        CompTerm f fs -> return $ envInsert var (FunconTerm f (Just fs)) env
      _ -> return env