module Blockchain.VM.Opcodes where

import Prelude hiding (EQ, GT, LT)

import Data.Binary
import qualified Data.ByteString as B
import qualified Data.Map as M
import Data.Maybe
import Legacy.Haskoin.V0102.Network.Haskoin.Crypto.BigWord
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))

import Blockchain.Util

--import Debug.Trace
data Operation
  = STOP
  | ADD
  | MUL
  | SUB
  | DIV
  | SDIV
  | MOD
  | SMOD
  | ADDMOD
  | MULMOD
  | EXP
  | SIGNEXTEND
  | NEG
  | LT
  | GT
  | SLT
  | SGT
  | EQ
  | ISZERO
  | NOT
  | AND
  | OR
  | XOR
  | BYTE
  | SHA3
  | ADDRESS
  | BALANCE
  | ORIGIN
  | CALLER
  | CALLVALUE
  | CALLDATALOAD
  | CALLDATASIZE
  | CALLDATACOPY
  | CODESIZE
  | CODECOPY
  | GASPRICE
  | EXTCODESIZE
  | EXTCODECOPY
  | BLOCKHASH
  | COINBASE
  | TIMESTAMP
  | NUMBER
  | DIFFICULTY
  | GASLIMIT
  | POP
  | MLOAD
  | MSTORE
  | MSTORE8
  | SLOAD
  | SSTORE
  | JUMP
  | JUMPI
  | PC
  | MSIZE
  | GAS
  | JUMPDEST
  | PUSH [Word8]
  | DUP1
  | DUP2
  | DUP3
  | DUP4
  | DUP5
  | DUP6
  | DUP7
  | DUP8
  | DUP9
  | DUP10
  | DUP11
  | DUP12
  | DUP13
  | DUP14
  | DUP15
  | DUP16
  | SWAP1
  | SWAP2
  | SWAP3
  | SWAP4
  | SWAP5
  | SWAP6
  | SWAP7
  | SWAP8
  | SWAP9
  | SWAP10
  | SWAP11
  | SWAP12
  | SWAP13
  | SWAP14
  | SWAP15
  | SWAP16
  | LOG0
  | LOG1
  | LOG2
  | LOG3
  | LOG4
  | CREATE
  | CALL
  | CALLCODE
  | RETURN
  | DELEGATECALL
  | REVERT
  | INVALID
  | SUICIDE
    --Pseudo Opcodes
  | LABEL String
  | PUSHLABEL String
  | PUSHDIFF String
             String
  | DATA B.ByteString
  | MalformedOpcode Word8
  deriving (Show, Eq, Ord)

instance Pretty Operation where
  pretty x@JUMPDEST = text $ "------" ++ show x
  pretty x@(PUSH vals) = text $ show x ++ " --" ++ show (bytes2Integer vals)
  pretty x = text $ show x

data OPData =
  OPData Word8
         Operation
         Int
         Int
         String

type EthCode = [Operation]

singleOp :: Operation -> ([Word8] -> Operation, Int)
singleOp o = (const o, 1)

opDatas :: [OPData]
opDatas =
  [ OPData 0x00 STOP 0 0 "Halts execution."
  , OPData 0x01 ADD 2 1 "Addition operation."
  , OPData 0x02 MUL 2 1 "Multiplication operation."
  , OPData 0x03 SUB 2 1 "Subtraction operation."
  , OPData 0x04 DIV 2 1 "Integer division operation."
  , OPData 0x05 SDIV 2 1 "Signed integer division operation."
  , OPData 0x06 MOD 2 1 "Modulo remainder operation."
  , OPData 0x07 SMOD 2 1 "Signed modulo remainder operation."
  , OPData 0x08 ADDMOD 2 1 "unsigned modular addition"
  , OPData 0x09 MULMOD 2 1 "unsigned modular multiplication"
  , OPData 0x0a EXP 2 1 "Exponential operation."
  , OPData
      0x0b
      SIGNEXTEND
      2
      1
      "Extend length of two’s complement signed integer."
  , OPData 0x10 LT 2 1 "Less-than comparision."
  , OPData 0x11 GT 2 1 "Greater-than comparision."
  , OPData 0x12 SLT 2 1 "Signed less-than comparision."
  , OPData 0x13 SGT 2 1 "Signed greater-than comparision."
  , OPData 0x14 EQ 2 1 "Equality comparision."
  , OPData 0x15 ISZERO 1 1 "Simple not operator."
  , OPData 0x16 AND 2 1 "Bitwise AND operation."
  , OPData 0x17 OR 2 1 "Bitwise OR operation."
  , OPData 0x18 XOR 2 1 "Bitwise XOR operation."
  , OPData 0x19 NOT 1 1 "Bitwise not operator."
  , OPData 0x1a BYTE 2 1 "Retrieve single byte from word."
  , OPData 0x20 SHA3 2 1 "Compute SHA3-256 hash."
  , OPData 0x30 ADDRESS 0 1 "Get address of currently executing account."
  , OPData 0x31 BALANCE 1 1 "Get balance of the given account."
  , OPData 0x32 ORIGIN 0 1 "Get execution origination address."
  , OPData 0x33 CALLER 0 1 "Get caller address."
  , OPData
      0x34
      CALLVALUE
      0
      1
      "Get deposited value by the instruction/transaction responsible for this execution."
  , OPData 0x35 CALLDATALOAD 1 1 "Get input data of current environment."
  , OPData
      0x36
      CALLDATASIZE
      0
      1
      "Get size of input data in current environment."
  , OPData
      0x37
      CALLDATACOPY
      3
      0
      "Copy input data in current environment to memory."
  , OPData 0x38 CODESIZE 0 1 "Get size of code running in current environment."
  , OPData
      0x39
      CODECOPY
      3
      0
      "Copy code running in current environment to memory."
  , OPData 0x3a GASPRICE 0 1 "Get price of gas in current environment."
  , OPData 0x3b EXTCODESIZE 0 1 "Get size of an account's code."
  , OPData 0x3c EXTCODECOPY 0 4 "Copy an account’s code to memory"
  , OPData 0x40 BLOCKHASH 0 1 "Get hash of most recent complete block."
  , OPData 0x41 COINBASE 0 1 "Get the block’s coinbase address."
  , OPData 0x42 TIMESTAMP 0 1 "Get the block’s timestamp."
  , OPData 0x43 NUMBER 0 1 "Get the block’s number."
  , OPData 0x44 DIFFICULTY 0 1 "Get the block’s difficulty."
  , OPData 0x45 GASLIMIT 0 1 "Get the block’s gas limit."
  , OPData 0x50 POP 1 0 "Remove item from stack."
  , OPData 0x51 MLOAD 1 1 "Load word from memory."
  , OPData 0x52 MSTORE 2 0 "Save word to memory."
  , OPData 0x53 MSTORE8 2 0 "Save byte to memory."
  , OPData 0x54 SLOAD 1 1 "Load word from storage."
  , OPData 0x55 SSTORE 2 0 "Save word to storage."
  , OPData 0x56 JUMP 1 0 "Alter the program counter."
  , OPData 0x57 JUMPI 2 0 "Conditionally alter the program counter."
  , OPData 0x58 PC 0 1 "Get the program counter."
  , OPData 0x59 MSIZE 0 1 "Get the size of active memory in bytes."
  , OPData 0x5a GAS 0 1 "Get the amount of available gas."
  , OPData 0x5b JUMPDEST 0 0 "set a potential jump destination"
  , OPData 0x80 DUP1 1 2 "Duplicate 1st stack item."
  , OPData 0x81 DUP2 2 3 "Duplicate 2nd stack item."
  , OPData 0x82 DUP3 3 4 "Duplicate 3rd stack item."
  , OPData 0x83 DUP4 4 5 "Duplicate 4th stack item."
  , OPData 0x84 DUP5 5 6 "Duplicate 5th stack item."
  , OPData 0x85 DUP6 6 7 "Duplicate 6th stack item."
  , OPData 0x86 DUP7 7 8 "Duplicate 7th stack item."
  , OPData 0x87 DUP8 8 9 "Duplicate 8th stack item."
  , OPData 0x88 DUP9 9 10 "Duplicate 9th stack item."
  , OPData 0x89 DUP10 10 11 "Duplicate 10th stack item."
  , OPData 0x8a DUP11 11 12 "Duplicate 11th stack item."
  , OPData 0x8b DUP12 12 13 "Duplicate 12th stack item."
  , OPData 0x8c DUP13 13 14 "Duplicate 13th stack item."
  , OPData 0x8d DUP14 14 15 "Duplicate 14th stack item."
  , OPData 0x8e DUP15 15 16 "Duplicate 15th stack item."
  , OPData 0x8f DUP16 16 17 "Duplicate 16th stack item."
  , OPData 0x90 SWAP1 2 2 "Exchange 1st and 2nd stack items."
  , OPData 0x91 SWAP2 3 3 "Exchange 1st and 3nd stack items."
  , OPData 0x92 SWAP3 4 4 "Exchange 1st and 4nd stack items."
  , OPData 0x93 SWAP4 5 5 "Exchange 1st and 5nd stack items."
  , OPData 0x94 SWAP5 6 6 "Exchange 1st and 6nd stack items."
  , OPData 0x95 SWAP6 7 7 "Exchange 1st and 7nd stack items."
  , OPData 0x96 SWAP7 8 8 "Exchange 1st and 8nd stack items."
  , OPData 0x97 SWAP8 9 9 "Exchange 1st and 9nd stack items."
  , OPData 0x98 SWAP9 10 10 "Exchange 1st and 10nd stack items."
  , OPData 0x99 SWAP10 11 11 "Exchange 1st and 11nd stack items."
  , OPData 0x9a SWAP11 12 12 "Exchange 1st and 12nd stack items."
  , OPData 0x9b SWAP12 13 13 "Exchange 1st and 13nd stack items."
  , OPData 0x9c SWAP13 14 14 "Exchange 1st and 14nd stack items."
  , OPData 0x9d SWAP14 15 15 "Exchange 1st and 15nd stack items."
  , OPData 0x9e SWAP15 16 16 "Exchange 1st and 16nd stack items."
  , OPData 0x9f SWAP16 17 17 "Exchange 1st and 17nd stack items."
  , OPData 0xa0 LOG0 2 0 "Append log record with no topics."
  , OPData 0xa1 LOG1 3 0 "Append log record with one topic."
  , OPData 0xa2 LOG2 4 0 "Append log record with two topics."
  , OPData 0xa3 LOG3 5 0 "Append log record with three topics."
  , OPData 0xa4 LOG4 6 0 "Append log record with four topics."
  , OPData 0xf0 CREATE 3 1 "Create a new account with associated code."
  , OPData 0xf1 CALL 7 1 "Message-call into an account."
  , OPData
      0xf2
      CALLCODE
      7
      1
      "Message-call into this account with alternate account's code."
  , OPData 0xf3 RETURN 2 0 "Halt execution returning output data."
  , OPData
      0xf4
      DELEGATECALL
      7
      1
      "Message-call into this account with an alternative account’s code, but persisting the current values for sender and value."
  , OPData 0xfd REVERT 0 0 "Stop execution and revert state changes (EIP140)."
  , OPData 0xfe INVALID 0 0 "Designated invalid instruction."
  , OPData
      0xff
      SUICIDE
      1
      0
      "Halt execution and register account for later deletion."
  ]

op2CodeMap :: M.Map Operation Word8
op2CodeMap = M.fromList $ (\(OPData code op _ _ _) -> (op, code)) <$> opDatas

code2OpMap :: M.Map Word8 Operation
code2OpMap =
  M.fromList $ (\(OPData opcode op _ _ _) -> (opcode, op)) <$> opDatas

op2OpCode :: Operation -> [Word8]
op2OpCode (PUSH theList)
  | length theList <= 32 && not (null theList) =
    0x5F + fromIntegral (length theList) : theList
op2OpCode (PUSH []) = error "PUSH needs at least one word"
op2OpCode (PUSH x) = error $ "PUSH can only take up to 32 words: " ++ show x
op2OpCode (DATA bytes) = B.unpack bytes
op2OpCode (MalformedOpcode byte) = [byte]
op2OpCode op =
  case M.lookup op op2CodeMap of
    Just x -> [x]
    Nothing -> error $ "op is missing in op2CodeMap: " ++ show op

opLen :: Operation -> Int
opLen (PUSH x) = 1 + length x
opLen _ = 1

opCode2Op :: B.ByteString -> (Operation, Word256)
opCode2Op rom
  | B.null rom = (STOP, 1) --according to the yellowpaper, should return STOP if outside of the code bytestring
opCode2Op rom =
  let opcode = B.head rom --head OK, null weeded out above
  in if opcode >= 0x60 && opcode <= 0x7f
       then ( PUSH $
              B.unpack $ safeTake (fromIntegral $ opcode - 0x5F) $ B.tail rom
            , fromIntegral $ opcode - 0x5E)
            --    let op = fromMaybe (error $ "code is missing in code2OpMap: 0x" ++ showHex (B.head rom) "")
       else let op =
                  fromMaybe (MalformedOpcode opcode) $
                  M.lookup opcode code2OpMap
            in (op, 1)