//! The function name.

use std::fmt;

use serde::Deserialize;
use serde::Serialize;

/// The function name.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Name {
    /// The user-defined function.
    UserDefined(String),

    /// `x + y`
    Add,
    /// `x - y`
    Sub,
    /// `x * y`
    Mul,
    /// `x / y` or `0` if `y == 0`
    Div,
    /// `x % y` or `0` if `y == 0`
    Mod,
    /// `x / y`, for signed numbers in two’s complement, `0` if `y == 0`
    Sdiv,
    /// `x % y`, for signed numbers in two’s complement, `0` if `y == 0`
    Smod,

    /// `1` if `x < y`, `0` otherwise
    Lt,
    /// `1` if `x > y`, `0` otherwise
    Gt,
    /// `1` if `x == y`, `0` otherwise
    Eq,
    /// `1` if `x == 0`, `0` otherwise
    IsZero,
    /// `1` if `x < y`, `0` otherwise, for signed numbers in two’s complement
    Slt,
    /// `1` if `x > y`, `0` otherwise, for signed numbers in two’s complement
    Sgt,

    /// bitwise "or" of `x` and `y`
    Or,
    /// bitwise "xor" of `x` and `y`
    Xor,
    /// bitwise "not" of `x` (every bit of `x` is negated)
    Not,
    /// bitwise "and" of `x` and `y`
    And,
    /// logical shift left `y` by `x` bits
    Shl,
    /// logical shift right `y` by `x` bits
    Shr,
    /// signed arithmetic shift right `y` by `x` bits
    Sar,
    /// number of leading zero bits of x, 256 if x == 0
    Clz,
    /// `n`th byte of `x`, where the most significant byte is the `0`th byte
    Byte,
    /// discard value x
    Pop,

    /// `(x + y) % m` with arbitrary precision arithmetic, `0` if `m == 0`
    AddMod,
    /// `(x * y) % m` with arbitrary precision arithmetic, `0` if `m == 0`
    MulMod,
    /// `x` to the power of `y`
    Exp,
    /// sign extend from `(i*8+7)`th bit counting from least significant
    SignExtend,

    /// `keccak(mem[p…(p+n)))`
    Keccak256,

    /// `mem[p…(p+32))`
    MLoad,
    /// `mem[p…(p+32)) := v`
    MStore,
    /// `mem[p] := v & 0xff` (only modifies a single byte)
    MStore8,
    /// heap memory copy
    MCopy,

    /// `storage[p]`
    SLoad,
    /// `storage[p] := v`
    SStore,
    /// transient `storage[p]`
    TLoad,
    /// transient `storage[p] := v`
    TStore,
    /// `loadimmutable` storage read
    LoadImmutable,
    /// `setimmutable` storage write
    SetImmutable,

    /// call data starting from position `p` (32 bytes)
    CallDataLoad,
    /// size of call data in bytes
    CallDataSize,
    /// copy `s` bytes from calldata at position `f` to memory at position `t`
    CallDataCopy,
    /// size of the code of the current contract / execution context
    CodeSize,
    /// copy `s` bytes from code at position `f` to mem at position `t`
    CodeCopy,
    /// size of the code at address `a`
    ExtCodeSize,
    /// code hash of address `a`
    ExtCodeHash,
    /// size of the last returndata
    ReturnDataSize,
    /// copy `s` bytes from returndata at position `f` to mem at position `t`
    ReturnDataCopy,

    /// end execution, return data `mem[p…(p+s))`
    Return,
    /// end execution, revert state changes, return data `mem[p…(p+s))`
    Revert,
    /// stop execution, identical to `return(0, 0)`
    Stop,
    /// end execution with invalid instruction
    Invalid,

    /// log without topics and data `mem[p…(p+s))`
    Log0,
    /// log with topic t1 and data `mem[p…(p+s))`
    Log1,
    /// log with topics t1, t2 and data `mem[p…(p+s))`
    Log2,
    /// log with topics t1, t2, t3 and data `mem[p…(p+s))`
    Log3,
    /// log with topics t1, t2, t3, t4 and data `mem[p…(p+s))`
    Log4,

    /// call contract at address a with input `mem[in…(in+insize))` providing `g` gas and `v` wei
    /// and output area `mem[out…(out+outsize))` returning 0 on error (e.g. out of gas)
    /// and 1 on success
    /// [See more](https://docs.soliditylang.org/en/v0.8.2/yul.html#yul-call-return-area)
    Call,
    /// identical to call but only use the code from a and stay in the context of the current
    /// contract otherwise
    CallCode,
    /// identical to `callcode` but also keeps `caller` and `callvalue`
    DelegateCall,
    /// identical to `call(g, a, 0, in, insize, out, outsize)` but do not allows state modifications
    StaticCall,

    /// create new contract with code `mem[p…(p+n))` and send `v` wei and return the new address
    /// Passes bytecode to the system contracts.
    Create,
    /// create new contract with code `mem[p…(p+n))` at address
    /// `keccak256(0xff . this . s . keccak256(mem[p…(p+n)))` and send `v` wei and return the
    /// new address, where `0xff` is a 1-byte value, this is the current contract’s address as a
    /// 20-byte value and `s` is a big-endian 256-bit value
    /// Passes bytecode to the system contracts.
    Create2,
    /// returns the size in the data area
    DataSize,
    /// is equivalent to `CodeCopy`
    DataCopy,
    /// returns the offset in the data area
    DataOffset,

    /// `linkersymbol` is a stub call
    LinkerSymbol,
    /// `memoryguard` is a stub call
    MemoryGuard,

    /// address of the current contract / execution context
    Address,
    /// call sender (excluding `delegatecall`)
    Caller,

    /// wei sent together with the current call
    CallValue,
    /// gas still available to execution
    Gas,
    /// wei balance at address `a`
    Balance,
    /// equivalent to `balance(address())`, but cheaper
    SelfBalance,

    /// block gas limit of the current block
    GasLimit,
    /// gas price of the transaction
    GasPrice,
    /// transaction sender
    Origin,
    /// ID of the executing chain (EIP 1344)
    ChainId,
    /// current block number
    Number,
    /// timestamp of the current block in seconds since the epoch
    Timestamp,
    /// hash of block nr b - only for last 256 blocks excluding current
    BlockHash,
    /// versioned hash of transaction’s i-th blob
    BlobHash,
    /// difficulty of the current block
    Difficulty,
    /// <https://eips.ethereum.org/EIPS/eip-4399>
    Prevrandao,
    /// current mining beneficiary
    CoinBase,
    /// size of memory, i.e. largest accessed memory index
    MSize,

    /// verbatim instruction with 0 inputs and 0 outputs only works in the Yul mode,
    /// so it is mostly used as a tool for extending Yul for PolkaVM
    Verbatim {
        /// the number of input arguments
        input_size: usize,
        /// the number of output arguments
        output_size: usize,
    },

    /// current block’s base fee (EIP-3198 and EIP-1559)
    BaseFee,
    /// current block’s blob base fee (EIP-7516 and EIP-4844)
    BlobBaseFee,
    /// current position in code
    Pc,
    /// like `codecopy(t, f, s)` but take code at address `a`
    ExtCodeCopy,
    /// end execution, destroy current contract and send funds to `a`
    SelfDestruct,
}

impl Name {
    /// Tries parsing the verbatim instruction.
    fn parse_verbatim(input: &str) -> Option<Self> {
        let verbatim = input.strip_prefix("verbatim")?;
        let regex = regex::Regex::new(r"_(\d+)i_(\d+)o").expect("Always valid");
        let captures = regex.captures(verbatim)?;
        let input_size: usize = captures.get(1)?.as_str().parse().ok()?;
        let output_size: usize = captures.get(2)?.as_str().parse().ok()?;
        Some(Self::Verbatim {
            input_size,
            output_size,
        })
    }
}

impl From<&str> for Name {
    fn from(input: &str) -> Self {
        if let Some(verbatim) = Self::parse_verbatim(input) {
            return verbatim;
        }

        match input {
            "add" => Self::Add,
            "sub" => Self::Sub,
            "mul" => Self::Mul,
            "div" => Self::Div,
            "mod" => Self::Mod,
            "sdiv" => Self::Sdiv,
            "smod" => Self::Smod,

            "lt" => Self::Lt,
            "gt" => Self::Gt,
            "eq" => Self::Eq,
            "iszero" => Self::IsZero,
            "slt" => Self::Slt,
            "sgt" => Self::Sgt,

            "or" => Self::Or,
            "xor" => Self::Xor,
            "not" => Self::Not,
            "and" => Self::And,
            "shl" => Self::Shl,
            "shr" => Self::Shr,
            "sar" => Self::Sar,
            "clz" => Self::Clz,
            "byte" => Self::Byte,
            "pop" => Self::Pop,

            "addmod" => Self::AddMod,
            "mulmod" => Self::MulMod,
            "exp" => Self::Exp,
            "signextend" => Self::SignExtend,

            "keccak256" => Self::Keccak256,

            "mload" => Self::MLoad,
            "mstore" => Self::MStore,
            "mstore8" => Self::MStore8,
            "mcopy" => Self::MCopy,

            "sload" => Self::SLoad,
            "sstore" => Self::SStore,
            "tload" => Self::TLoad,
            "tstore" => Self::TStore,
            "loadimmutable" => Self::LoadImmutable,
            "setimmutable" => Self::SetImmutable,

            "calldataload" => Self::CallDataLoad,
            "calldatasize" => Self::CallDataSize,
            "calldatacopy" => Self::CallDataCopy,
            "codesize" => Self::CodeSize,
            "codecopy" => Self::CodeCopy,
            "returndatasize" => Self::ReturnDataSize,
            "returndatacopy" => Self::ReturnDataCopy,
            "extcodesize" => Self::ExtCodeSize,
            "extcodehash" => Self::ExtCodeHash,

            "return" => Self::Return,
            "revert" => Self::Revert,

            "log0" => Self::Log0,
            "log1" => Self::Log1,
            "log2" => Self::Log2,
            "log3" => Self::Log3,
            "log4" => Self::Log4,

            "call" => Self::Call,
            "delegatecall" => Self::DelegateCall,
            "staticcall" => Self::StaticCall,

            "create" => Self::Create,
            "create2" => Self::Create2,
            "datasize" => Self::DataSize,
            "dataoffset" => Self::DataOffset,
            "datacopy" => Self::DataCopy,

            "stop" => Self::Stop,
            "invalid" => Self::Invalid,

            "linkersymbol" => Self::LinkerSymbol,
            "memoryguard" => Self::MemoryGuard,

            "address" => Self::Address,
            "caller" => Self::Caller,

            "callvalue" => Self::CallValue,
            "gas" => Self::Gas,
            "balance" => Self::Balance,
            "selfbalance" => Self::SelfBalance,

            "gaslimit" => Self::GasLimit,
            "gasprice" => Self::GasPrice,
            "origin" => Self::Origin,
            "chainid" => Self::ChainId,
            "timestamp" => Self::Timestamp,
            "number" => Self::Number,
            "blockhash" => Self::BlockHash,
            "blobhash" => Self::BlobHash,
            "difficulty" => Self::Difficulty,
            "prevrandao" => Self::Prevrandao,
            "coinbase" => Self::CoinBase,
            "basefee" => Self::BaseFee,
            "blobbasefee" => Self::BlobBaseFee,
            "msize" => Self::MSize,

            "callcode" => Self::CallCode,
            "pc" => Self::Pc,
            "extcodecopy" => Self::ExtCodeCopy,
            "selfdestruct" => Self::SelfDestruct,

            input => Self::UserDefined(input.to_owned()),
        }
    }
}

impl fmt::Display for Name {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Self::Verbatim {
            input_size,
            output_size,
        } = self
        {
            return write!(f, "verbatim_{input_size}i_{output_size}o");
        }

        let token = match self {
            Self::Add => "add",
            Self::Sub => "sub",
            Self::Mul => "mul",
            Self::Div => "div",
            Self::Mod => "mod",
            Self::Sdiv => "sdiv",
            Self::Smod => "smod",

            Self::Lt => "lt",
            Self::Gt => "gt",
            Self::Eq => "eq",
            Self::IsZero => "iszero",
            Self::Slt => "slt",
            Self::Sgt => "sgt",

            Self::Or => "or",
            Self::Xor => "xor",
            Self::Not => "not",
            Self::And => "and",
            Self::Shl => "shl",
            Self::Shr => "shr",
            Self::Sar => "sar",
            Self::Clz => "clz",
            Self::Byte => "byte",
            Self::Pop => "pop",

            Self::AddMod => "addmod",
            Self::MulMod => "mulmod",
            Self::Exp => "exp",
            Self::SignExtend => "signextend",

            Self::Keccak256 => "keccak256",

            Self::MLoad => "mload",
            Self::MStore => "mstore",
            Self::MStore8 => "mstore8",
            Self::MCopy => "mcopy",

            Self::SLoad => "sload",
            Self::SStore => "sstore",
            Self::TLoad => "tload",
            Self::TStore => "tstore",
            Self::LoadImmutable => "loadimmutable",
            Self::SetImmutable => "setimmutable",

            Self::CallDataLoad => "calldataload",
            Self::CallDataSize => "calldatasize",
            Self::CallDataCopy => "calldatacopy",
            Self::CodeSize => "codesize",
            Self::CodeCopy => "codecopy",
            Self::ReturnDataSize => "returndatasize",
            Self::ReturnDataCopy => "returndatacopy",
            Self::ExtCodeSize => "extcodesize",
            Self::ExtCodeHash => "extcodehash",

            Self::Return => "return",
            Self::Revert => "revert",

            Self::Log0 => "log0",
            Self::Log1 => "log1",
            Self::Log2 => "log2",
            Self::Log3 => "log3",
            Self::Log4 => "log4",

            Self::Call => "call",
            Self::DelegateCall => "delegatecall",
            Self::StaticCall => "staticcall",
            Self::Create => "create",
            Self::Create2 => "create2",

            Self::DataSize => "datasize",
            Self::DataOffset => "dataoffset",
            Self::DataCopy => "datacopy",

            Self::Stop => "stop",
            Self::Invalid => "invalid",

            Self::LinkerSymbol => "linkersymbol",
            Self::MemoryGuard => "memoryguard",

            Self::Address => "address",
            Self::Caller => "caller",

            Self::CallValue => "callvalue",
            Self::Gas => "gas",
            Self::Balance => "balance",
            Self::SelfBalance => "selfbalance",

            Self::GasLimit => "gaslimit",
            Self::GasPrice => "gasprice",
            Self::Origin => "origin",
            Self::ChainId => "chainid",
            Self::Timestamp => "timestamp",
            Self::Number => "number",
            Self::BlockHash => "blockhash",
            Self::BlobHash => "blobhash",
            Self::Difficulty => "difficulty",
            Self::Prevrandao => "prevrandao",
            Self::CoinBase => "coinbase",
            Self::BaseFee => "basefee",
            Self::BlobBaseFee => "blobbasefee",
            Self::MSize => "msize",

            Self::CallCode => "callcode",
            Self::Pc => "pc",
            Self::ExtCodeCopy => "extcodecopy",
            Self::SelfDestruct => "selfdestruct",

            Self::UserDefined(s) => s.as_str(),

            Self::Verbatim { .. } => unreachable!(),
        };

        write!(f, "{token}")
    }
}
