-- | Parsing of Michelson instructions.

module Michelson.Parser.Instr
  ( primInstr
  , ops'
  -- * These are handled separately to have better error messages
  , mapOp
  , pairOp
  , cmpOp
  ) where

import Prelude hiding (EQ, GT, LT, many, note, some, try)

import Text.Megaparsec (choice, sepEndBy, try)

import Michelson.Let (LetValue(..))
import Michelson.Macro (ParsedInstr, ParsedOp(..))
import Michelson.Parser.Annotations
import Michelson.Parser.Lexer
import Michelson.Parser.Type
import Michelson.Parser.Types (Parser, letValues)
import Michelson.Parser.Value
import Michelson.Untyped

-- | Parser for primitive Michelson instruction (no macros and extensions).
primInstr :: Parser (Contract' ParsedOp) -> Parser ParsedOp -> Parser ParsedInstr
primInstr contractParser opParser = choice
  [ dropOp, dupOp, swapOp, pushOp opParser, someOp, noneOp, unitOp
  , ifNoneOp opParser, carOp, cdrOp, leftOp, rightOp, ifLeftOp opParser, nilOp
  , consOp, ifConsOp opParser, sizeOp, emptySetOp, emptyMapOp, iterOp opParser
  , memOp, getOp, updateOp, loopLOp opParser, loopOp opParser
  , lambdaOp opParser, execOp, dipOp opParser, failWithOp, castOp, renameOp
  , concatOp, packOp, unpackOp, sliceOp, isNatOp, addressOp, addOp, subOp
  , mulOp, edivOp, absOp, negOp, lslOp, lsrOp, orOp, andOp, xorOp, notOp
  , compareOp, eqOp, neqOp, ltOp, leOp, gtOp, geOp, intOp, selfOp, contractOp
  , transferTokensOp, setDelegateOp, createAccountOp
  , createContractOp contractParser, implicitAccountOp, nowOp, amountOp
  , balanceOp, checkSigOp, sha256Op, sha512Op, blake2BOp, hashKeyOp
  , stepsToQuotaOp, sourceOp, senderOp
  ]

-- | Parse a sequence of instructions.
ops' :: Parser ParsedOp -> Parser [ParsedOp]
ops' opParser = braces $ sepEndBy opParser semicolon

-- Control Structures

failWithOp :: Parser ParsedInstr
failWithOp = do symbol' "FAILWITH"; return FAILWITH

loopOp :: Parser ParsedOp -> Parser ParsedInstr
loopOp opParser = do void $ symbol' "LOOP"; LOOP <$> ops' opParser

loopLOp :: Parser ParsedOp -> Parser ParsedInstr
loopLOp opParser = do void $ symbol' "LOOP_LEFT"; LOOP_LEFT <$> ops' opParser

execOp :: Parser ParsedInstr
execOp = do void $ symbol' "EXEC"; EXEC <$> noteVDef

dipOp :: Parser ParsedOp -> Parser ParsedInstr
dipOp opParser = do void $ symbol' "DIP"; DIP <$> ops' opParser

-- Stack Operations

dropOp :: Parser ParsedInstr
dropOp = do symbol' "DROP"; return DROP;

dupOp :: Parser ParsedInstr
dupOp = do void $ symbol' "DUP"; DUP <$> noteVDef

swapOp :: Parser ParsedInstr
swapOp = do symbol' "SWAP"; return SWAP;

pushOp :: Parser ParsedOp -> Parser ParsedInstr
pushOp opParser = do
  symbol' "PUSH"
  v <- noteVDef
  (try $ pushLet v) <|> (push' v)
  where
    pushLet v = do
      lvs <- asks letValues
      lv <- mkLetVal lvs
      return $ PUSH v (lvSig lv) (lvVal lv)
    push' v = PUSH v <$> type_ <*> value' opParser

unitOp :: Parser ParsedInstr
unitOp = do symbol' "UNIT"; (t, v) <- notesTV; return $ UNIT t v

lambdaOp :: Parser ParsedOp -> Parser ParsedInstr
lambdaOp opParser =
  symbol' "LAMBDA" *>
  (LAMBDA <$> noteVDef <*> type_ <*> type_ <*> ops' opParser)

-- Generic comparison

cmpOp :: Parser ParsedInstr
cmpOp = eqOp <|> neqOp <|> ltOp <|> gtOp <|> leOp <|> gtOp <|> geOp

eqOp :: Parser ParsedInstr
eqOp = do void $ symbol' "EQ"; EQ <$> noteVDef

neqOp :: Parser ParsedInstr
neqOp = do void $ symbol' "NEQ"; NEQ <$> noteVDef

ltOp :: Parser ParsedInstr
ltOp = do void $ symbol' "LT"; LT <$> noteVDef

gtOp :: Parser ParsedInstr
gtOp = do void $ symbol' "GT"; GT <$> noteVDef

leOp :: Parser ParsedInstr
leOp = do void $ symbol' "LE"; LE <$> noteVDef

geOp :: Parser ParsedInstr
geOp = do void $ symbol' "GE"; GE <$> noteVDef

-- ad-hoc comparison

compareOp :: Parser ParsedInstr
compareOp = do void $ symbol' "COMPARE"; COMPARE <$> noteVDef

-- Operations on booleans

orOp :: Parser ParsedInstr
orOp = do void $ symbol' "OR";  OR <$> noteVDef

andOp :: Parser ParsedInstr
andOp = do void $ symbol' "AND"; AND <$> noteVDef

xorOp :: Parser ParsedInstr
xorOp = do void $ symbol' "XOR"; XOR <$> noteVDef

notOp :: Parser ParsedInstr
notOp = do void $ symbol' "NOT"; NOT <$> noteVDef

-- Operations on integers and natural numbers

addOp :: Parser ParsedInstr
addOp = do void $ symbol' "ADD"; ADD <$> noteVDef

subOp :: Parser ParsedInstr
subOp = do void $ symbol' "SUB"; SUB <$> noteVDef

mulOp :: Parser ParsedInstr
mulOp = do void $ symbol' "MUL"; MUL <$> noteVDef

edivOp :: Parser ParsedInstr
edivOp = do void $ symbol' "EDIV";EDIV <$> noteVDef

absOp :: Parser ParsedInstr
absOp = do void $ symbol' "ABS"; ABS <$> noteVDef

negOp :: Parser ParsedInstr
negOp = do symbol' "NEG"; return NEG;

-- Bitwise logical operators

lslOp :: Parser ParsedInstr
lslOp = do void $ symbol' "LSL"; LSL <$> noteVDef

lsrOp :: Parser ParsedInstr
lsrOp = do void $ symbol' "LSR"; LSR <$> noteVDef

-- Operations on string's

concatOp :: Parser ParsedInstr
concatOp = do void $ symbol' "CONCAT"; CONCAT <$> noteVDef

sliceOp :: Parser ParsedInstr
sliceOp = do void $ symbol' "SLICE"; SLICE <$> noteVDef

-- Operations on pairs
pairOp :: Parser ParsedInstr
pairOp = do symbol' "PAIR"; (t, v, (p, q)) <- notesTVF2; return $ PAIR t v p q

carOp :: Parser ParsedInstr
carOp = do symbol' "CAR"; (v, f) <- notesVF; return $ CAR v f

cdrOp :: Parser ParsedInstr
cdrOp = do symbol' "CDR"; (v, f) <- notesVF; return $ CDR v f

-- Operations on collections (sets, maps, lists)

emptySetOp :: Parser ParsedInstr
emptySetOp = do symbol' "EMPTY_SET"; (t, v) <- notesTV;
                EMPTY_SET t v <$> comparable

emptyMapOp :: Parser ParsedInstr
emptyMapOp = do symbol' "EMPTY_MAP"; (t, v) <- notesTV; a <- comparable;
                EMPTY_MAP t v a <$> type_

memOp :: Parser ParsedInstr
memOp = do void $ symbol' "MEM"; MEM <$> noteVDef

updateOp :: Parser ParsedInstr
updateOp = do symbol' "UPDATE"; return UPDATE

iterOp :: Parser ParsedOp -> Parser ParsedInstr
iterOp opParser = do void $ symbol' "ITER"; ITER <$> ops' opParser

sizeOp :: Parser ParsedInstr
sizeOp = do void $ symbol' "SIZE"; SIZE <$> noteVDef

mapOp :: Parser ParsedOp -> Parser ParsedInstr
mapOp opParser = do symbol' "MAP"; v <- noteVDef; MAP v <$> ops' opParser

getOp :: Parser ParsedInstr
getOp = do void $ symbol' "GET"; GET <$> noteVDef

nilOp :: Parser ParsedInstr
nilOp = do symbol' "NIL"; (t, v) <- notesTV; NIL t v <$> type_

consOp :: Parser ParsedInstr
consOp = do void $ symbol' "CONS"; CONS <$> noteVDef

ifConsOp :: Parser ParsedOp -> Parser ParsedInstr
ifConsOp opParser =
  symbol' "IF_CONS" *>
  (IF_CONS <$> ops' opParser <*> ops' opParser)

-- Operations on options

someOp :: Parser ParsedInstr
someOp = do symbol' "SOME"; (t, v, f) <- notesTVF; return $ SOME t v f

noneOp :: Parser ParsedInstr
noneOp = do symbol' "NONE"; (t, v, f) <- notesTVF; NONE t v f <$> type_

ifNoneOp :: Parser ParsedOp -> Parser ParsedInstr
ifNoneOp opParser =
  symbol' "IF_NONE" *>
  (IF_NONE <$> ops' opParser <*> ops' opParser)

-- Operations on unions

leftOp :: Parser ParsedInstr
leftOp = do symbol' "LEFT"; (t, v, (f, f')) <- notesTVF2;
               LEFT t v f f' <$> type_

rightOp :: Parser ParsedInstr
rightOp = do symbol' "RIGHT"; (t, v, (f, f')) <- notesTVF2;
               RIGHT t v f f' <$> type_

ifLeftOp :: Parser ParsedOp -> Parser ParsedInstr
ifLeftOp opParser = do
  symbol' "IF_LEFT"
  a <- ops' opParser
  IF_LEFT a <$> ops' opParser

-- Operations on contracts

createContractOp :: Parser (Contract' ParsedOp) -> Parser ParsedInstr
createContractOp contractParser =
  symbol' "CREATE_CONTRACT" *>
  (CREATE_CONTRACT <$> noteVDef <*> noteVDef <*> braces contractParser)

createAccountOp :: Parser ParsedInstr
createAccountOp = do symbol' "CREATE_ACCOUNT"; v <- noteVDef; v' <- noteVDef;
                       return $ CREATE_ACCOUNT v v'

transferTokensOp :: Parser ParsedInstr
transferTokensOp = do void $ symbol' "TRANSFER_TOKENS"; TRANSFER_TOKENS <$> noteVDef

setDelegateOp :: Parser ParsedInstr
setDelegateOp = do void $ symbol' "SET_DELEGATE"; SET_DELEGATE <$> noteVDef

balanceOp :: Parser ParsedInstr
balanceOp = do void $ symbol' "BALANCE"; BALANCE <$> noteVDef

contractOp :: Parser ParsedInstr
contractOp = do void $ symbol' "CONTRACT"; CONTRACT <$> noteVDef <*> type_

sourceOp :: Parser ParsedInstr
sourceOp = do void $ symbol' "SOURCE"; SOURCE <$> noteVDef

senderOp :: Parser ParsedInstr
senderOp = do void $ symbol' "SENDER"; SENDER <$> noteVDef

amountOp :: Parser ParsedInstr
amountOp = do void $ symbol' "AMOUNT"; AMOUNT <$> noteVDef

implicitAccountOp :: Parser ParsedInstr
implicitAccountOp = do void $ symbol' "IMPLICIT_ACCOUNT"; IMPLICIT_ACCOUNT <$> noteVDef

selfOp :: Parser ParsedInstr
selfOp = do void $ symbol' "SELF"; SELF <$> noteVDef

addressOp :: Parser ParsedInstr
addressOp = do void $ symbol' "ADDRESS"; ADDRESS <$> noteVDef

-- Special Operations

nowOp :: Parser ParsedInstr
nowOp = do void $ symbol' "NOW"; NOW <$> noteVDef

stepsToQuotaOp :: Parser ParsedInstr
stepsToQuotaOp = do void $ symbol' "STEPS_TO_QUOTA"; STEPS_TO_QUOTA <$> noteVDef

-- Operations on bytes

packOp :: Parser ParsedInstr
packOp = do void $ symbol' "PACK"; PACK <$> noteVDef

unpackOp :: Parser ParsedInstr
unpackOp = do symbol' "UNPACK"; v <- noteVDef; UNPACK v <$> type_

-- Cryptographic Primitives

checkSigOp :: Parser ParsedInstr
checkSigOp = do void $ symbol' "CHECK_SIGNATURE"; CHECK_SIGNATURE <$> noteVDef

blake2BOp :: Parser ParsedInstr
blake2BOp = do void $ symbol' "BLAKE2B"; BLAKE2B <$> noteVDef

sha256Op :: Parser ParsedInstr
sha256Op = do void $ symbol' "SHA256"; SHA256 <$> noteVDef

sha512Op :: Parser ParsedInstr
sha512Op = do void $ symbol' "SHA512"; SHA512 <$> noteVDef

hashKeyOp :: Parser ParsedInstr
hashKeyOp = do void $ symbol' "HASH_KEY"; HASH_KEY <$> noteVDef

-- Type operations

castOp :: Parser ParsedInstr
castOp = do void $ symbol' "CAST"; CAST <$> noteVDef <*> type_;

renameOp :: Parser ParsedInstr
renameOp = do void $ symbol' "RENAME"; RENAME <$> noteVDef

isNatOp :: Parser ParsedInstr
isNatOp = do void $ symbol' "ISNAT"; ISNAT <$> noteVDef

intOp :: Parser ParsedInstr
intOp = do void $ symbol' "INT"; INT <$> noteVDef