/*
 * Copyright (c) [2016] [ <ether.camp> ]
 * This file is part of the ethereumJ library.
 *
 * The ethereumJ library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * The ethereumJ library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
 */
package org.ethereum.vm;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import static org.ethereum.vm.OpCode.Tier.*;


/**
 * Instruction set for the Ethereum Virtual Machine
 * See Yellow Paper: http://www.gavwood.com/Paper.pdf
 * - Appendix G. Virtual Machine Specification
 */
public enum OpCode {
    // TODO #POC9 Need to make tiers more accurate
    /**
     * Halts execution (0x00)
     */
    STOP(0x00, 0, 0, ZeroTier),

    /*  Arithmetic Operations   */

    /**
     * (0x01) Addition operation
     */
    ADD(0x01, 2, 1, VeryLowTier),
    /**
     * (0x02) Multiplication operation
     */
    MUL(0x02, 2, 1, LowTier),
    /**
     * (0x03) Subtraction operations
     */
    SUB(0x03, 2, 1, VeryLowTier),
    /**
     * (0x04) Integer division operation
     */
    DIV(0x04, 2, 1, LowTier),
    /**
     * (0x05) Signed integer division operation
     */
    SDIV(0x05, 2, 1, LowTier),
    /**
     * (0x06) Modulo remainder operation
     */
    MOD(0x06, 2, 1, LowTier),
    /**
     * (0x07) Signed modulo remainder operation
     */
    SMOD(0x07, 2, 1, LowTier),
    /**
     * (0x08) Addition combined with modulo
     * remainder operation
     */
    ADDMOD(0x08, 3, 1, MidTier),
    /**
     * (0x09) Multiplication combined with modulo
     * remainder operation
     */
    MULMOD(0x09, 3, 1, MidTier),
    /**
     * (0x0a) Exponential operation
     */
    EXP(0x0a, 2, 1, SpecialTier),
    /**
     * (0x0b) Extend length of signed integer
     */
    SIGNEXTEND(0x0b, 2, 1, LowTier),

    /*  Bitwise Logic & Comparison Operations   */

    /**
     * (0x10) Less-than comparison
     */
    LT(0X10, 2, 1, VeryLowTier),
    /**
     * (0x11) Greater-than comparison
     */
    GT(0X11, 2, 1, VeryLowTier),
    /**
     * (0x12) Signed less-than comparison
     */
    SLT(0X12, 2, 1, VeryLowTier),
    /**
     * (0x13) Signed greater-than comparison
     */
    SGT(0X13, 2, 1, VeryLowTier),
    /**
     * (0x14) Equality comparison
     */
    EQ(0X14, 2, 1, VeryLowTier),
    /**
     * (0x15) Negation operation
     */
    ISZERO(0x15, 1, 1, VeryLowTier),
    /**
     * (0x16) Bitwise AND operation
     */
    AND(0x16, 2, 1, VeryLowTier),
    /**
     * (0x17) Bitwise OR operation
     */
    OR(0x17, 2, 1, VeryLowTier),
    /**
     * (0x18) Bitwise XOR operation
     */
    XOR(0x18, 2, 1, VeryLowTier),
    /**
     * (0x19) Bitwise NOT operationr
     */
    NOT(0x19, 1, 1, VeryLowTier),
    /**
     * (0x1a) Retrieve single byte from word
     */
    BYTE(0x1a, 2, 1, VeryLowTier),
    /**
     * (0x1b) Shift left
     */
    SHL(0x1b, 2, 1, VeryLowTier),
    /**
     * (0x1c) Logical shift right
     */
    SHR(0x1c, 2, 1, VeryLowTier),
    /**
     * (0x1d) Arithmetic shift right
     */
    SAR(0x1d, 2, 1, VeryLowTier),

    /*  Cryptographic Operations    */

    /**
     * (0x20) Compute SHA3-256 hash
     */
    SHA3(0x20, 2, 1, SpecialTier),

    /*  Environmental Information   */

    /**
     * (0x30)  Get address of currently
     * executing account
     */
    ADDRESS(0x30, 0, 1, BaseTier),
    /**
     * (0x31) Get balance of the given account
     */
    BALANCE(0x31, 1, 1, ExtTier),
    /**
     * (0x32) Get execution origination address
     */
    ORIGIN(0x32, 0, 1, BaseTier),
    /**
     * (0x33) Get caller address
     */
    CALLER(0x33, 0, 1, BaseTier),
    /**
     * (0x34) Get deposited value by the
     * instruction/transaction responsible
     * for this execution
     */
    CALLVALUE(0x34, 0, 1, BaseTier),
    /**
     * (0x35) Get input data of current
     * environment
     */
    CALLDATALOAD(0x35, 1, 1, VeryLowTier),
    /**
     * (0x36) Get size of input data in current
     * environment
     */
    CALLDATASIZE(0x36, 0, 1, BaseTier),
    /**
     * (0x37) Copy input data in current
     * environment to memory
     */
    CALLDATACOPY(0x37, 3, 0, VeryLowTier),
    /**
     * (0x38) Get size of code running in
     * current environment
     */
    CODESIZE(0x38, 0, 1, BaseTier),
    /**
     * (0x39) Copy code running in current
     * environment to memory
     */
    CODECOPY(0x39, 3, 0, VeryLowTier), // [len code_start mem_start CODECOPY]

    RETURNDATASIZE(0x3d, 0, 1, BaseTier),

    RETURNDATACOPY(0x3e, 3, 0, VeryLowTier),
    /**
     * (0x3a) Get price of gas in current
     * environment
     */
    GASPRICE(0x3a, 0, 1, BaseTier),
    /**
     * (0x3b) Get size of code running in
     * current environment with given offset
     */
    EXTCODESIZE(0x3b, 1, 1, ExtTier),
    /**
     * (0x3c) Copy code running in current
     * environment to memory with given offset
     */
    EXTCODECOPY(0x3c, 4, 0, ExtTier),
    /**
     * (0x3f) Returns the keccak256 hash of
     * a contract’s code
     */
    EXTCODEHASH(0x3f, 1,1 , ExtTier),

    /*  Block Information   */

    /**
     * (0x40) Get hash of most recent
     * complete block
     */
    BLOCKHASH(0x40, 1, 1, ExtTier),
    /**
     * (0x41) Get the block’s coinbase address
     */
    COINBASE(0x41, 0, 1, BaseTier),
    /**
     * (x042) Get the block’s timestamp
     */
    TIMESTAMP(0x42, 0, 1, BaseTier),
    /**
     * (0x43) Get the block’s number
     */
    NUMBER(0x43, 0, 1, BaseTier),
    /**
     * (0x44) Get the block’s difficulty
     */
    DIFFICULTY(0x44, 0, 1, BaseTier),
    /**
     * (0x45) Get the block’s gas limit
     */
    GASLIMIT(0x45, 0, 1, BaseTier),

    /*  Memory, Storage and Flow Operations */

    /**
     * (0x50) Remove item from stack
     */
    POP(0x50, 1, 0, BaseTier),
    /**
     * (0x51) Load word from memory
     */
    MLOAD(0x51, 1, 1, VeryLowTier),
    /**
     * (0x52) Save word to memory
     */
    MSTORE(0x52, 2, 0, VeryLowTier),
    /**
     * (0x53) Save byte to memory
     */
    MSTORE8(0x53, 2, 0, VeryLowTier),
    /**
     * (0x54) Load word from storage
     */
    SLOAD(0x54, 1, 1, SpecialTier),
    /**
     * (0x55) Save word to storage
     */
    SSTORE(0x55, 2, 0, SpecialTier),
    /**
     * (0x56) Alter the program counter
     */
    JUMP(0x56, 1, 0, MidTier),
    /**
     * (0x57) Conditionally alter the program
     * counter
     */
    JUMPI(0x57, 2, 0, HighTier),
    /**
     * (0x58) Get the program counter
     */
    PC(0x58, 0, 1, BaseTier),
    /**
     * (0x59) Get the size of active memory
     */
    MSIZE(0x59, 0, 1, BaseTier),
    /**
     * (0x5a) Get the amount of available gas
     */
    GAS(0x5a, 0, 1, BaseTier),
    /**
     * (0x5b)
     */
    JUMPDEST(0x5b, 0, 0, SpecialTier),

    /*  Push Operations */

    /**
     * (0x60) Place 1-byte item on stack
     */
    PUSH1(0x60, 0, 1, VeryLowTier),
    /**
     * (0x61) Place 2-byte item on stack
     */
    PUSH2(0x61, 0, 1, VeryLowTier),
    /**
     * (0x62) Place 3-byte item on stack
     */
    PUSH3(0x62, 0, 1, VeryLowTier),
    /**
     * (0x63) Place 4-byte item on stack
     */
    PUSH4(0x63, 0, 1, VeryLowTier),
    /**
     * (0x64) Place 5-byte item on stack
     */
    PUSH5(0x64, 0, 1, VeryLowTier),
    /**
     * (0x65) Place 6-byte item on stack
     */
    PUSH6(0x65, 0, 1, VeryLowTier),
    /**
     * (0x66) Place 7-byte item on stack
     */
    PUSH7(0x66, 0, 1, VeryLowTier),
    /**
     * (0x67) Place 8-byte item on stack
     */
    PUSH8(0x67, 0, 1, VeryLowTier),
    /**
     * (0x68) Place 9-byte item on stack
     */
    PUSH9(0x68, 0, 1, VeryLowTier),
    /**
     * (0x69) Place 10-byte item on stack
     */
    PUSH10(0x69, 0, 1, VeryLowTier),
    /**
     * (0x6a) Place 11-byte item on stack
     */
    PUSH11(0x6a, 0, 1, VeryLowTier),
    /**
     * (0x6b) Place 12-byte item on stack
     */
    PUSH12(0x6b, 0, 1, VeryLowTier),
    /**
     * (0x6c) Place 13-byte item on stack
     */
    PUSH13(0x6c, 0, 1, VeryLowTier),
    /**
     * (0x6d) Place 14-byte item on stack
     */
    PUSH14(0x6d, 0, 1, VeryLowTier),
    /**
     * (0x6e) Place 15-byte item on stack
     */
    PUSH15(0x6e, 0, 1, VeryLowTier),
    /**
     * (0x6f) Place 16-byte item on stack
     */
    PUSH16(0x6f, 0, 1, VeryLowTier),
    /**
     * (0x70) Place 17-byte item on stack
     */
    PUSH17(0x70, 0, 1, VeryLowTier),
    /**
     * (0x71) Place 18-byte item on stack
     */
    PUSH18(0x71, 0, 1, VeryLowTier),
    /**
     * (0x72) Place 19-byte item on stack
     */
    PUSH19(0x72, 0, 1, VeryLowTier),
    /**
     * (0x73) Place 20-byte item on stack
     */
    PUSH20(0x73, 0, 1, VeryLowTier),
    /**
     * (0x74) Place 21-byte item on stack
     */
    PUSH21(0x74, 0, 1, VeryLowTier),
    /**
     * (0x75) Place 22-byte item on stack
     */
    PUSH22(0x75, 0, 1, VeryLowTier),
    /**
     * (0x76) Place 23-byte item on stack
     */
    PUSH23(0x76, 0, 1, VeryLowTier),
    /**
     * (0x77) Place 24-byte item on stack
     */
    PUSH24(0x77, 0, 1, VeryLowTier),
    /**
     * (0x78) Place 25-byte item on stack
     */
    PUSH25(0x78, 0, 1, VeryLowTier),
    /**
     * (0x79) Place 26-byte item on stack
     */
    PUSH26(0x79, 0, 1, VeryLowTier),
    /**
     * (0x7a) Place 27-byte item on stack
     */
    PUSH27(0x7a, 0, 1, VeryLowTier),
    /**
     * (0x7b) Place 28-byte item on stack
     */
    PUSH28(0x7b, 0, 1, VeryLowTier),
    /**
     * (0x7c) Place 29-byte item on stack
     */
    PUSH29(0x7c, 0, 1, VeryLowTier),
    /**
     * (0x7d) Place 30-byte item on stack
     */
    PUSH30(0x7d, 0, 1, VeryLowTier),
    /**
     * (0x7e) Place 31-byte item on stack
     */
    PUSH31(0x7e, 0, 1, VeryLowTier),
    /**
     * (0x7f) Place 32-byte (full word)
     * item on stack
     */
    PUSH32(0x7f, 0, 1, VeryLowTier),

    /*  Duplicate Nth item from the stack   */

    /**
     * (0x80) Duplicate 1st item on stack
     */
    DUP1(0x80, 1, 2, VeryLowTier),
    /**
     * (0x81) Duplicate 2nd item on stack
     */
    DUP2(0x81, 2, 3, VeryLowTier),
    /**
     * (0x82) Duplicate 3rd item on stack
     */
    DUP3(0x82, 3, 4, VeryLowTier),
    /**
     * (0x83) Duplicate 4th item on stack
     */
    DUP4(0x83, 4, 5, VeryLowTier),
    /**
     * (0x84) Duplicate 5th item on stack
     */
    DUP5(0x84, 5, 6, VeryLowTier),
    /**
     * (0x85) Duplicate 6th item on stack
     */
    DUP6(0x85, 6, 7, VeryLowTier),
    /**
     * (0x86) Duplicate 7th item on stack
     */
    DUP7(0x86, 7, 8, VeryLowTier),
    /**
     * (0x87) Duplicate 8th item on stack
     */
    DUP8(0x87, 8, 9, VeryLowTier),
    /**
     * (0x88) Duplicate 9th item on stack
     */
    DUP9(0x88, 9, 10, VeryLowTier),
    /**
     * (0x89) Duplicate 10th item on stack
     */
    DUP10(0x89, 10, 11, VeryLowTier),
    /**
     * (0x8a) Duplicate 11th item on stack
     */
    DUP11(0x8a, 11, 12, VeryLowTier),
    /**
     * (0x8b) Duplicate 12th item on stack
     */
    DUP12(0x8b, 12, 13, VeryLowTier),
    /**
     * (0x8c) Duplicate 13th item on stack
     */
    DUP13(0x8c, 13, 14, VeryLowTier),
    /**
     * (0x8d) Duplicate 14th item on stack
     */
    DUP14(0x8d, 14, 15, VeryLowTier),
    /**
     * (0x8e) Duplicate 15th item on stack
     */
    DUP15(0x8e, 15, 16, VeryLowTier),
    /**
     * (0x8f) Duplicate 16th item on stack
     */
    DUP16(0x8f, 16, 17, VeryLowTier),

    /*  Swap the Nth item from the stack with the top   */

    /**
     * (0x90) Exchange 2nd item from stack with the top
     */
    SWAP1(0x90, 2, 2, VeryLowTier),
    /**
     * (0x91) Exchange 3rd item from stack with the top
     */
    SWAP2(0x91, 3, 3, VeryLowTier),
    /**
     * (0x92) Exchange 4th item from stack with the top
     */
    SWAP3(0x92, 4, 4, VeryLowTier),
    /**
     * (0x93) Exchange 5th item from stack with the top
     */
    SWAP4(0x93, 5, 5, VeryLowTier),
    /**
     * (0x94) Exchange 6th item from stack with the top
     */
    SWAP5(0x94, 6, 6, VeryLowTier),
    /**
     * (0x95) Exchange 7th item from stack with the top
     */
    SWAP6(0x95, 7, 7, VeryLowTier),
    /**
     * (0x96) Exchange 8th item from stack with the top
     */
    SWAP7(0x96, 8, 8, VeryLowTier),
    /**
     * (0x97) Exchange 9th item from stack with the top
     */
    SWAP8(0x97, 9, 9, VeryLowTier),
    /**
     * (0x98) Exchange 10th item from stack with the top
     */
    SWAP9(0x98, 10, 10, VeryLowTier),
    /**
     * (0x99) Exchange 11th item from stack with the top
     */
    SWAP10(0x99, 11, 11,VeryLowTier),
    /**
     * (0x9a) Exchange 12th item from stack with the top
     */
    SWAP11(0x9a, 12, 12, VeryLowTier),
    /**
     * (0x9b) Exchange 13th item from stack with the top
     */
    SWAP12(0x9b, 13, 13, VeryLowTier),
    /**
     * (0x9c) Exchange 14th item from stack with the top
     */
    SWAP13(0x9c, 14, 14, VeryLowTier),
    /**
     * (0x9d) Exchange 15th item from stack with the top
     */
    SWAP14(0x9d, 15, 15, VeryLowTier),
    /**
     * (0x9e) Exchange 16th item from stack with the top
     */
    SWAP15(0x9e, 16, 16, VeryLowTier),
    /**
     * (0x9f) Exchange 17th item from stack with the top
     */
    SWAP16(0x9f, 17, 17, VeryLowTier),

    /**
     * (0xa[n]) log some data for some addres with 0..n tags [addr [tag0..tagn] data]
     */
    LOG0(0xa0, 2, 0, SpecialTier),
    LOG1(0xa1, 3, 0, SpecialTier),
    LOG2(0xa2, 4, 0, SpecialTier),
    LOG3(0xa3, 5, 0, SpecialTier),
    LOG4(0xa4, 6, 0, SpecialTier),

    /*  System operations   */

    /**
     * (0xf0) Create a new account with associated code
     */
    CREATE(0xf0, 3, 1, SpecialTier),   //       [in_size] [in_offs] [gas_val] CREATE
    /**
     * (cxf1) Message-call into an account
     */
    CALL(0xf1, 7, 1, SpecialTier, CallFlags.Call, CallFlags.HasValue),
    //       [out_data_size] [out_data_start] [in_data_size] [in_data_start] [value] [to_addr]
    // [gas] CALL
    /**
     * (0xf2) Calls self, but grabbing the code from the
     * TO argument instead of from one's own address
     */
    CALLCODE(0xf2, 7, 1, SpecialTier, CallFlags.Call, CallFlags.HasValue, CallFlags.Stateless),
    /**
     * (0xf3) Halt execution returning output data
     */
    RETURN(0xf3, 2, 0, ZeroTier),

    /**
     * (0xf4)  similar in idea to CALLCODE, except that it propagates the sender and value
     *  from the parent scope to the child scope, ie. the call created has the same sender
     *  and value as the original call.
     *  also the Value parameter is omitted for this opCode
     */
    DELEGATECALL(0xf4, 6, 1, SpecialTier, CallFlags.Call, CallFlags.Stateless, CallFlags.Delegate),

    /**
     * (0xf5) Skinny CREATE2, same as CREATE but with deterministic address
     */
    CREATE2(0xf5, 4, 1, SpecialTier),

    /**
     *  opcode that can be used to call another contract (or itself) while disallowing any
     *  modifications to the state during the call (and its subcalls, if present).
     *  Any opcode that attempts to perform such a modification (see below for details)
     *  will result in an exception instead of performing the modification.
     */
    STATICCALL(0xfa, 6, 1, SpecialTier, CallFlags.Call, CallFlags.Static),

    /**
     * (0xfd) The `REVERT` instruction will stop execution, roll back all state changes done so far
     * and provide a pointer to a memory section, which can be interpreted as an error code or message.
     * While doing so, it will not consume all the remaining gas.
     */
    REVERT(0xfd, 2, 0, ZeroTier),
    /**
     * (0xff) Halt execution and register account for
     * later deletion
     */
    SUICIDE(0xff, 1, 0, ZeroTier);

    private final byte opcode;
    private final int require;
    private final Tier tier;
    private final int ret;
    private final EnumSet<CallFlags> callFlags;

    private static final OpCode[] intToTypeMap = new OpCode[256];
    private static final Map<String, Byte> stringToByteMap = new HashMap<>();

    static {
        for (OpCode type : OpCode.values()) {
            intToTypeMap[type.opcode & 0xFF] = type;
            stringToByteMap.put(type.name(), type.opcode);
        }
    }

    //require = required args
    //return = required return
    private OpCode(int op, int require, int ret, Tier tier, CallFlags ... callFlags) {
        this.opcode = (byte) op;
        this.require = require;
        this.tier = tier;
        this.ret = ret;
        this.callFlags = callFlags.length == 0 ? EnumSet.noneOf(CallFlags.class) :
                EnumSet.copyOf(Arrays.asList(callFlags));
    }


    public byte val() {
        return opcode;
    }

    /**
     * Returns the mininum amount of items required on the stack for this operation
     *
     * @return minimum amount of expected items on the stack
     */
    public int require() {
        return require;
    }

    public int ret() {
        return ret;
    }

    public int asInt() {
        return opcode;
    }

    public static boolean contains(String code) {
        return stringToByteMap.containsKey(code.trim());
    }

    public static byte byteVal(String code) {
        return stringToByteMap.get(code);
    }

    public static OpCode code(byte code) {
        return intToTypeMap[code & 0xFF];
    }

    private EnumSet<CallFlags> getCallFlags() {
        return callFlags;
    }

    /**
     * Indicates that opcode is a call
     */
    public boolean isCall() {
        return getCallFlags().contains(CallFlags.Call);
    }

    private void checkCall() {
        if (!isCall()) throw new RuntimeException("Opcode is not a call: " + this);
    }

    /**
     *  Indicates that the code is executed in the context of the caller
     */
    public boolean callIsStateless() {
        checkCall();
        return getCallFlags().contains(CallFlags.Stateless);
    }

    /**
     *  Indicates that the opcode has value parameter (3rd on stack)
     */
    public boolean callHasValue() {
        checkCall();
        return getCallFlags().contains(CallFlags.HasValue);
    }

    /**
     *  Indicates that any state modifications are disallowed during the call
     */
    public boolean callIsStatic() {
        checkCall();
        return getCallFlags().contains(CallFlags.Static);
    }

    /**
     *  Indicates that value and message sender are propagated from parent to child scope
     */
    public boolean callIsDelegate() {
        checkCall();
        return getCallFlags().contains(CallFlags.Delegate);
    }

    public Tier getTier() {
        return this.tier;
    }

    public enum Tier {
        ZeroTier(0),
        BaseTier(2),
        VeryLowTier(3),
        LowTier(5),
        MidTier(8),
        HighTier(10),
        ExtTier(20),
        SpecialTier(1), //TODO #POC9 is this correct?? "multiparam" from cpp
        InvalidTier(0);


        private final int level;

        private Tier(int level) {
            this.level = level;
        }

        public int asInt() {
            return level;
        }
    }

    private enum CallFlags {
        /**
         * Indicates that opcode is a call
         */
        Call,

        /**
         *  Indicates that the code is executed in the context of the caller
         */
        Stateless,

        /**
         *  Indicates that the opcode has value parameter (3rd on stack)
         */
        HasValue,

        /**
         *  Indicates that any state modifications are disallowed during the call
         */
        Static,

        /**
         *  Indicates that value and message sender are propagated from parent to child scope
         */
        Delegate
    }
}


