//! Ethereum virtual machine opcode
#![deny(missing_docs)]

mod cancun;
mod shanghai;

pub use cancun::Cancun;
pub use shanghai::ShangHai;

/// Ethereum virtual machine opcode generator.
#[macro_export]
macro_rules! opcodes {
    ($name:ident, $desc:literal) => {
        #[doc = $desc]
        $name
    };
    {
        $version:ident,
        $((
            $opcode:expr,
            $name:ident,
            $gas:expr,
            $input:expr,
            $output:expr,
            $desc:literal,
            $since:ident,
            $group:ident
        )),+
    } => {
        /// Ethereum virtual machine opcode.
        #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
        pub enum $version {
            #[cfg(feature = "data")]
            /// No operation but provides a byte for serializing.
            Data(u8),
            $(
                #[doc = concat!(" ", $desc)]
                $name,
            )*
        }

        impl From<u8> for $version {
            fn from(value: u8) -> Self {
                match value {
                    $(
                        $opcode => Self::$name,
                    )*
                    _ => unreachable!("Invalid opcode."),
                }
            }
        }

        impl From<$version> for u8 {
            fn from(version: $version) -> Self {
                match version {
                    #[cfg(feature = "data")]
                    $version::Data(data) => data,
                    $(
                        $version::$name => $opcode,
                    )*
                }
            }
        }

        impl OpCode for $version {
            fn group(&self) -> Group {
                match self {
                    #[cfg(feature = "data")]
                    Self::Data(_) => Group::StopArithmetic,
                    $(
                        Self::$name => Group::$group,
                    )*
                }
            }

            fn gas(&self) -> u16 {
                match self {
                    #[cfg(feature = "data")]
                    Self::Data(_) => 0,
                    $(
                        Self::$name => $gas,
                    )*
                }
            }

            fn since(&self) -> Upgrade {
                match self {
                    #[cfg(feature = "data")]
                    Self::Data(_) => Upgrade::Shanghai,
                    $(
                        Self::$name => Upgrade::$since,
                    )*
                }
            }

            fn stack_in(&self) -> u16 {
                match self {
                    #[cfg(feature = "data")]
                    Self::Data(_) => 0,
                    $(
                        Self::$name => $input,
                    )*
                }
            }

            fn stack_out(&self) -> u16 {
                match self {
                    #[cfg(feature = "data")]
                    Self::Data(_) => 0,
                    $(
                        Self::$name => $output,
                    )*
                }
            }
        }

        impl core::str::FromStr for $version {
            type Err = ();

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                paste::paste! {
                    match s {
                        $(
                            stringify!([< $name:lower >]) => Ok(Self::$name),
                        )*
                            _ => Err(()),
                    }
                }
            }
        }

        paste::paste! {
            #[doc = concat!(" For each ", stringify!($version), " operator.")]
            #[macro_export]
            macro_rules! [<for_each_ $version:lower _operator>] {
                ($mac:ident) => {
                    $mac! {
                        $([<_ $name:lower>] => $name),+
                    }
                }
            }
        }
    };
}

/// EVM opcode groups
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub enum Group {
    /// Stop and Arithmetic Operations
    StopArithmetic,
    /// Comparison & Bitwise Logic Operations
    ComparisonBitwiseLogic,
    /// SHA3
    Sha3,
    /// Environmental Information
    EnvironmentalInformation,
    /// Block Information
    BlockInformation,
    /// Stack, Memory, Storage and Flow Operations
    StackMemoryStorageFlow,
    /// Push Operations
    Push,
    /// Duplication Operations
    Duplication,
    /// Exchange Operations
    Exchange,
    /// Logging Operations
    Logging,
    /// System operations
    System,
}

/// Ethereum upgrades.
#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub enum Upgrade {
    /// Frontier
    Frontier,
    /// Byzantium
    Byzantium,
    /// Constantinople
    Constantinople,
    /// Istanbul
    Istanbul,
    /// Berlin
    Berlin,
    /// London
    London,
    /// Shanghai
    Shanghai,
    /// Cancun
    Cancun,
}

/// Ethereum virtual machine opcode.
pub trait OpCode: From<u8> + Into<u8> {
    /// The stack input count.
    fn stack_in(&self) -> u16;

    /// The stack output count.
    fn stack_out(&self) -> u16;

    /// The OpCode is available since.
    fn since(&self) -> Upgrade;

    /// The group of the OpCode.
    fn group(&self) -> Group;

    /// The basic gas cost of the OpCode.
    fn gas(&self) -> u16;
}