-- | Module, containing data types for Michelson value.

module Michelson.Typed.Instr
  ( Instr (..)
  , (#)
  , Contract
  , ExtT
  , InstrExtT
  ) where

import Data.Kind (Type)
import Data.Singletons (SingI)

import Michelson.Typed.Arith
import Michelson.Typed.Polymorphic
import Michelson.Typed.T (CT(..), T(..))
import Michelson.Typed.Value (ContractInp, ContractOut, Val(..))

-- | Infix version of @Seq@ constructor.
--
-- One can represent sequence of Michelson opertaions as follows:
-- @SWAP; DROP; DUP;@ -> @SWAP # DROP # DUP@.
(#) :: Typeable b => Instr a b -> Instr b c -> Instr a c
(#) = Seq

infixl 0 #

-- | ExtT is extension of Instr by Morley instructions
type family ExtT (instr :: [T] -> [T] -> Type) :: Type

type InstrExtT = ExtT Instr

-- | Representation of Michelson instruction or sequence
-- of instructions.
--
-- Each Michelson instruction is represented by exactly one
-- constructor of this data type. Sequence of instructions
-- is represented with use of @Seq@ constructor in following
-- way: @SWAP; DROP ; DUP;@ -> @SWAP `Seq` DROP `Seq` DUP@.
-- Special case where there are no instructions is represented
-- by constructor @Nop@, e.g. @IF_NONE {} { SWAP; DROP; }@ ->
-- @IF_NONE Nop (SWAP `Seq` DROP)@.
--
-- Type parameter @inp@ states for input stack type. That is,
-- type of the stack that is required for operation to execute.
--
-- Type parameter @out@ states for output stack type or type
-- of stack that will be left after instruction's execution.

-- pva701: Typeable constraints are added during TM-29.
-- Maybe it makes sense to think how to eliminate them
-- if they break something
data Instr (inp :: [T]) (out :: [T]) where
  Seq :: Typeable b => Instr a b -> Instr b c -> Instr a c
  -- | Nop operation. Missing in Michelson spec, added to parse construction
  -- like  `IF {} { SWAP; DROP; }`.
  Nop :: Instr s s
  Ext :: ExtT Instr -> Instr s s
  -- | Nested wrapper is going to wrap a sequence of instructions with { }.
  -- It is crucial because serialisation of a contract
  -- depends on precise structure of its code.
  Nested :: Instr inp out -> Instr inp out

  DROP :: Instr (a ': s) s
  DUP  :: Instr (a ': s) (a ': a ': s)
  SWAP :: Instr (a ': b ': s) (b ': a ': s)
  PUSH :: forall t s . SingI t => Val Instr t -> Instr s (t ': s)
  SOME :: Instr (a ': s) ('TOption a ': s)
  NONE :: forall a s . SingI a => Instr s ('TOption a ': s)
  UNIT :: Instr s ('TUnit ': s)
  IF_NONE
    :: (Typeable a, Typeable s)
    => Instr s s'
    -> Instr (a ': s) s'
    -> Instr ('TOption a ': s) s'
  PAIR :: Instr (a ': b ': s) ('TPair a b ': s)
  CAR :: Instr ('TPair a b ': s) (a ': s)
  CDR :: Instr ('TPair a b ': s) (b ': s)
  LEFT :: forall a b s . SingI b => Instr (a ': s) ('TOr a b ': s)
  RIGHT :: forall a b s . SingI a => Instr (b ': s) ('TOr a b ': s)
  IF_LEFT
    :: (Typeable s, Typeable a, Typeable b)
    => Instr (a ': s) s'
    -> Instr (b ': s) s'
    -> Instr ('TOr a b ': s) s'
  IF_RIGHT
    :: (Typeable s, Typeable b, Typeable a)
    => Instr (b ': s) s'
    -> Instr (a ': s) s'
    -> Instr ('TOr a b ': s) s'
  NIL :: SingI p => Instr s ('TList p ': s)
  CONS :: Instr (a ': 'TList a ': s) ('TList a ': s)
  IF_CONS
    :: (Typeable s, Typeable a)
    => Instr (a ': 'TList a ': s) s'
    -> Instr s s'
    -> Instr ('TList a ': s) s'
  SIZE :: SizeOp c => Instr (c ': s) ('Tc 'CNat ': s)
  EMPTY_SET :: SingI e => Instr s ('TSet e ': s)
  EMPTY_MAP :: (SingI a, SingI b) => Instr s ('TMap a b ': s)
  MAP :: (Typeable (MapOpInp c ': s), MapOp c b)
      => Instr (MapOpInp c ': s) (b ': s)
      -> Instr (c ': s) (MapOpRes c b ': s)
  ITER :: (Typeable (IterOpEl c ': s), IterOp c) => Instr (IterOpEl c ': s) s -> Instr (c ': s) s
  MEM :: MemOp c => Instr ('Tc (MemOpKey c) ': c ': s) ('Tc 'CBool ': s)
  GET
    :: GetOp c
    => Instr ('Tc (GetOpKey c) ': c ': s) ('TOption (GetOpVal c) ': s)
  UPDATE
    :: UpdOp c
    => Instr ('Tc (UpdOpKey c) ': UpdOpParams c ': c ': s) (c ': s)
  IF :: Typeable s
     => Instr s s'
     -> Instr s s'
     -> Instr ('Tc 'CBool ': s) s'
  LOOP :: Typeable s
       => Instr s ('Tc 'CBool ': s)
       -> Instr ('Tc 'CBool ': s) s
  LOOP_LEFT
    :: (Typeable a, Typeable s)
    => Instr (a ': s) ('TOr a b ': s)
    -> Instr ('TOr a b ': s) (b ': s)
  LAMBDA :: forall i o s . (SingI i, SingI o)
         => Val Instr ('TLambda i o) -> Instr s ('TLambda i o ': s)
  EXEC :: Typeable t1 => Instr (t1 ': 'TLambda t1 t2 ': s) (t2 ': s)
  DIP :: Typeable a => Instr a c -> Instr (b ': a) (b ': c)
  FAILWITH :: Instr (a ': s) t
  CAST :: forall a s . SingI a => Instr (a ': s) (a ': s)
  RENAME :: Instr (a ': s) (a ': s)
  PACK :: Instr (a ': s) ('Tc 'CBytes ': s)
  UNPACK :: SingI a => Instr ('Tc 'CBytes ': s) ('TOption a ': s)
  CONCAT :: ConcatOp c => Instr (c ': c ': s) (c ': s)
  CONCAT' :: ConcatOp c => Instr ('TList c ': s) (c ': s)
  SLICE
    :: SliceOp c
    => Instr ('Tc 'CNat ': 'Tc 'CNat ': c ': s) ('TOption c ': s)
  ISNAT :: Instr ('Tc 'CInt ': s) ('TOption ('Tc 'CNat) ': s)
  ADD
    :: ArithOp Add n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Add n m) ': s)
  SUB
    :: ArithOp Sub n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Sub n m) ': s)
  MUL
    :: ArithOp Mul n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Mul n m) ': s)
  EDIV
    :: EDivOp n m
    => Instr ('Tc n ': 'Tc m ': s)
                 (('TOption ('TPair ('Tc (EDivOpRes n m))
                                      ('Tc (EModOpRes n m)))) ': s)
  ABS
    :: UnaryArithOp Abs n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Abs n) ': s)
  NEG
    :: UnaryArithOp Neg n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Neg n) ': s)
  LSL
    :: ArithOp Lsl n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Lsl n m) ': s)
  LSR
    :: ArithOp Lsr n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Lsr n m) ': s)
  OR
    :: ArithOp Or n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Or n m) ': s)
  AND
    :: ArithOp And n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes And n m) ': s)
  XOR
    :: ArithOp Xor n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Xor n m) ': s)
  NOT
    :: UnaryArithOp Not n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Not n) ': s)
  COMPARE
    :: ArithOp Compare n m
    => Instr ('Tc n ': 'Tc m ': s) ('Tc (ArithRes Compare n m) ': s)
  EQ
    :: UnaryArithOp Eq' n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Eq' n) ': s)
  NEQ
    :: UnaryArithOp Neq n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Neq n) ': s)
  LT
    :: UnaryArithOp Lt n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Lt n) ': s)
  GT
    :: UnaryArithOp Gt n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Gt n) ': s)
  LE
    :: UnaryArithOp Le n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Le n) ': s)
  GE
    :: UnaryArithOp Ge n
    => Instr ('Tc n ': s) ('Tc (UnaryArithRes Ge n) ': s)
  INT :: Instr ('Tc 'CNat ': s) ('Tc 'CInt ': s)
  SELF :: forall (cp :: T) s . Instr s ('TContract cp ': s)
  CONTRACT
    :: SingI p => Instr ('Tc 'CAddress ': s) ('TOption ('TContract p) ': s)
  TRANSFER_TOKENS
    :: Typeable p => Instr (p ': 'Tc 'CMutez ': 'TContract p ': s)
                   ('TOperation ': s)
  SET_DELEGATE
    :: Instr ('TOption ('Tc 'CKeyHash) ': s) ('TOperation ': s)

  CREATE_ACCOUNT
    :: Instr
        ('Tc 'CKeyHash ': 'TOption ('Tc 'CKeyHash) ': 'Tc 'CBool
         ': 'Tc 'CMutez ': s) ('TOperation ': 'Tc 'CAddress ': s)

  CREATE_CONTRACT
    :: (SingI p, SingI g, Typeable p, Typeable g)
    => Instr
        ('Tc 'CKeyHash ': 'TOption ('Tc 'CKeyHash) ': 'Tc 'CBool
          ': 'Tc 'CBool ': 'Tc 'CMutez
          ': 'TLambda ('TPair p g)
                       ('TPair ('TList 'TOperation) g) ': g ': s)
        ('TOperation ': 'Tc 'CAddress ': s)
  CREATE_CONTRACT2
    :: (SingI p, SingI g, Typeable p, Typeable g)
    => Instr '[ 'TPair p g ] '[ 'TPair ('TList 'TOperation) g ]
    -> Instr ('Tc 'CKeyHash ':
              'TOption ('Tc 'CKeyHash) ':
              'Tc 'CBool ':
              'Tc 'CBool ':
              'Tc 'CMutez ':
               g ': s)
             ('TOperation ': 'Tc 'CAddress ': s)

  IMPLICIT_ACCOUNT
    :: Instr ('Tc 'CKeyHash ': s) ('TContract 'TUnit ': s)
  NOW :: Instr s ('Tc 'CTimestamp ': s)
  AMOUNT :: Instr s ('Tc 'CMutez ': s)
  BALANCE :: Instr s ('Tc 'CMutez ': s)
  CHECK_SIGNATURE
    :: Instr ('TKey ': 'TSignature ': 'Tc 'CBytes ': s)
                   ('Tc 'CBool ': s)
  SHA256 :: Instr ('Tc 'CBytes ': s) ('Tc 'CBytes ': s)
  SHA512 :: Instr ('Tc 'CBytes ': s) ('Tc 'CBytes ': s)
  BLAKE2B :: Instr ('Tc 'CBytes ': s) ('Tc 'CBytes ': s)
  HASH_KEY :: Instr ('TKey ': s) ('Tc 'CKeyHash ': s)
  STEPS_TO_QUOTA :: Instr s ('Tc 'CNat ': s)
  SOURCE :: Instr s ('Tc 'CAddress ': s)
  SENDER :: Instr s ('Tc 'CAddress ': s)
  ADDRESS :: Instr ('TContract a ': s) ('Tc 'CAddress ': s)

deriving instance Show (ExtT Instr) => Show (Instr inp out)

type Contract cp st = Instr (ContractInp cp st) (ContractOut st)