-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

-- | 'StatementF' functor datatype for Freer monad

module Indigo.Frontend.Statement
  ( StatementF (..)

  , IfConstraint
  , IndigoMCaseClauseL (..)
  ) where

import qualified Data.Kind as Kind
import Util.TypeLits (AppendSymbol)

import Lorentz.Entrypoints.Helpers (RequireSumType)
import qualified Lorentz.Run as L (Contract)
import Michelson.Typed.Haskell.Instr.Sum (CaseClauseParam(..), CtorField(..))

import Indigo.Prelude
import Indigo.Lorentz
import Indigo.Internal
import Indigo.Backend

-- | Analogous datatype as IndigoCaseClauseL from Indigo.Backend.Case
data IndigoMCaseClauseL freer ret (param :: CaseClauseParam) where
    OneFieldIndigoMCaseClauseL
      :: ( CaseArrow name
                     (Var x -> IndigoAnyOut x ret)
                     (IndigoCaseClauseL ret ('CaseClauseParam ctor ('OneField x)))
         , name ~ (AppendSymbol "c" ctor)
         , KnownValue x
         , ScopeCodeGen retBr
         , ret ~ RetExprs retBr
         , RetOutStack ret ~ RetOutStack retBr
         )
      => Label name
      -> (Var x -> freer retBr)
      -> IndigoMCaseClauseL freer ret ('CaseClauseParam ctor ('OneField x))

-- | StatementF functor for Freer monad.
--
-- The constructors correspond to every Indigo statement that has expressions
-- (@'Expr' x@) in its signature.
--
-- The ones that don't take expressions are compiled directly to 'IndigoState'
-- (and kept in 'LiftIndigoState'), because they won't be taken into consideration
-- by an optimizer anyway.
--
-- One more detail about 'StatementF' is that it takes a @cont@ type parameter,
-- which is basically 'IndigoM' (freer monad), to avoid cyclic dependencies.
-- @cont@ is needed to support statements which have recursive structure
-- (like: @if@, @while@, @case@, etc).
data StatementF (freer :: Kind.Type -> Kind.Type) a where
  -- | Direct injection of IndigoState of statements
  -- which are not going to be analyzed by optimizer.
  LiftIndigoState :: (forall inp . SomeIndigoState inp a) -> StatementF freer a

  NewVar :: (ex :~> x) => ex -> StatementF freer (Var x)
  SetVar :: (ex :~> x, IsObject x) => Var x -> ex -> StatementF freer ()
  VarModification
    :: (ey :~> y, IsObject x)
    => [y, x] :-> '[x]
    -> Var x
    -> ey
    -> StatementF freer ()
  SetField ::
    ( ex :~> ftype
    , IsObject dt
    , IsObject ftype
    , HasField dt fname ftype
    )
    => Var dt -> Label fname -> ex -> StatementF cont ()

  -- | Pure lambda
  LambdaPure1Call
    :: (ExecuteLambdaPure1C arg res, CreateLambdaPure1C arg res, Typeable res)
    => String
    -> (Var arg -> freer res)
    -> Expr arg
    -> StatementF freer (RetVars res)

  -- | "Default" lambda which can modify storage
  Lambda1Call
    :: (ExecuteLambda1C st arg res, CreateLambda1C st arg res, Typeable res)
    => Proxy st
    -> String
    -> (Var arg -> freer res)
    -> Expr arg
    -> StatementF freer (RetVars res)

  -- | Lambda which can modify storage and emit operations
  LambdaEff1Call
    :: (ExecuteLambdaEff1C st arg res, CreateLambdaEff1C st arg res, Typeable res)
    => Proxy st
    -> String
    -> (Var arg -> freer res)
    -> Expr arg
    -> StatementF freer (RetVars res)

  Scope :: ScopeCodeGen a => freer a -> StatementF freer (RetVars a)
  If :: (IfConstraint a b, ex :~> Bool)
     => ex
     -> freer a
     -> freer b
     -> StatementF freer (RetVars a)
  IfSome :: (IfConstraint a b, KnownValue x, ex :~> Maybe x)
     => ex
     -> (Var x -> freer a)
     -> freer b
     -> StatementF freer (RetVars a)
  IfRight
     :: (IfConstraint a b, KnownValue x, KnownValue y, ex :~> Either y x)
     => ex
     -> (Var x -> freer a)
     -> (Var y -> freer b)
     -> StatementF freer (RetVars a)
  IfCons
     :: (IfConstraint a b, KnownValue x, ex :~> (List x) )
     => ex
     -> (Var x -> Var (List x) -> freer a)
     -> freer b
     -> StatementF freer (RetVars a)
  Case :: CaseCommonF (IndigoMCaseClauseL freer) dt guard ret clauses
       => guard -> clauses
       -> StatementF freer (RetVars ret)
  EntryCase ::
    ( CaseCommonF (IndigoMCaseClauseL freer) dt guard ret clauses
    , DocumentEntrypoints entrypointKind dt
    )
    => Proxy entrypointKind
    -> guard
    -> clauses
    -> StatementF freer (RetVars ret)
  EntryCaseSimple ::
         ( CaseCommonF (IndigoMCaseClauseL freer) cp guard ret clauses
         , DocumentEntrypoints PlainEntrypointsKind cp
         , NiceParameterFull cp
         , RequireFlatParamEps cp
         )
      => guard
      -> clauses
      -> StatementF freer (RetVars ret)

  While :: ex :~> Bool => ex -> freer () -> StatementF freer ()
  WhileLeft
    :: (KnownValue x, KnownValue y, ex :~> Either y x)
    => ex
    -> (Var y -> freer ())
    -> StatementF freer (Var x)
  ForEach :: (IterOpHs a, KnownValue (IterOpElHs a), e :~> a)
          => e
          -> (Var (IterOpElHs a) -> freer ())
          -> StatementF freer ()

  ContractName :: Text        -> freer () -> StatementF freer ()
  DocGroup     :: DocGrouping -> freer () -> StatementF freer ()
  ContractGeneral :: freer () -> StatementF freer ()
  FinalizeParamCallingDoc
    :: (ToExpr param, NiceParameterFull (ExprType param), RequireSumType (ExprType param), HasCallStack)
    => (Var (ExprType param) -> freer x) -> param -> StatementF freer x

  TransferTokens
    :: (exp :~> p, exm :~> Mutez, exc :~> ContractRef p, NiceParameter p, HasSideEffects)
    => exp -> exm -> exc -> StatementF freer ()
  SetDelegate :: (HasSideEffects, ex :~> Maybe KeyHash) => ex -> StatementF freer ()

  CreateContract ::
    ( IsObject st
    , exk :~> Maybe KeyHash, exm :~> Mutez, exs :~> st
    , NiceStorage st, NiceParameterFull param
    , HasSideEffects
    )
    => L.Contract param st
    -> exk
    -> exm
    -> exs
    -> StatementF freer (Var Address)
  ContractCalling ::
     ( HasEntrypointArg cp epRef epArg
     , ToTAddress cp addr
     , ToT addr ~ ToT Address
     , exAddr :~> addr
     , KnownValue epArg
     )
     => Proxy cp -> epRef -> exAddr -> StatementF freer (Var (Maybe (ContractRef epArg)))

  FailWith ::
    ( ex :~> a
    )
    => ex -> StatementF freer r
  Assert ::
    ( IsError x
    , ex :~> Bool
    )
    => x -> ex -> StatementF freer ()
  FailCustom ::
     ( err ~ ErrorArg tag
     , CustomErrorHasDoc tag
     , NiceConstant err
     , ex :~> err
     )
     => Label tag -> ex -> StatementF freer r