//! Low level assembler implementation for EVM.
//!
//! TODO: refactor this module with Result as outputs. (issue-21)

use crate::{Buffer, Error, Result};
use opcodes::{for_each_cancun_operator, Cancun as OpCode, OpCode as _};

const MAX_STACK_SIZE: u16 = 1024;

/// Low level assembler implementation for EVM.
#[derive(Default, Clone, Debug)]
pub struct Assembler {
    /// Buffer of the assembler.
    buffer: Buffer,
    /// Gas counter.
    ///
    /// This is used to calculate the gas cost of the generated code.
    ///
    /// TODO: use a more precise type, eq `u256`. (issue-20)
    gas: u128,
    /// Memory pointer for byte offset.
    pub mp: usize,
    /// Stack pointer, maximum `MAX_STACK_SIZE` items.
    pub sp: u16,
}

impl Assembler {
    /// Buffer of the assembler.
    pub fn buffer(&self) -> &[u8] {
        &self.buffer
    }

    /// Mutable buffer of the assembler.
    pub fn buffer_mut(&mut self) -> &mut Buffer {
        &mut self.buffer
    }

    /// Increment the gas counter.
    ///
    /// TODO: use number bigger than `u256` for throwing proper errors. (#21)
    pub fn increment_gas(&mut self, gas: u128) {
        self.gas += gas;
    }

    /// Increment stack pointer
    pub fn increment_sp(&mut self, items: u16) -> Result<()> {
        if items == 0 {
            return Ok(());
        }

        tracing::trace!(
            "increment stack pointer {}.add({items}) -> {}",
            self.sp,
            self.sp + items
        );
        self.sp = self
            .sp
            .checked_add(items)
            .ok_or(Error::StackOverflow(self.sp, items))?;

        if self.sp > MAX_STACK_SIZE {
            return Err(Error::StackOverflow(self.sp, items));
        }

        Ok(())
    }

    /// Decrement stack pointer
    pub fn decrement_sp(&mut self, items: u16) -> Result<()> {
        if items == 0 {
            return Ok(());
        }

        tracing::trace!(
            "decrement stack pointer {}.sub({items}) -> {}",
            self.sp,
            self.sp - items
        );
        self.sp = if self.sp == items {
            0
        } else {
            self.sp
                .checked_sub(items)
                .ok_or(Error::StackUnderflow(self.sp, items))?
        };

        Ok(())
    }

    /// Increment memory pointer
    pub fn increment_mp(&mut self, offset: usize) -> Result<()> {
        self.mp = self
            .mp
            .checked_add(offset)
            .ok_or(Error::MemoryOutOfBounds)?;

        Ok(())
    }

    /// Decrement memory pointer
    pub fn decrement_mp(&mut self, offset: usize) -> Result<()> {
        self.mp = self
            .mp
            .checked_sub(offset)
            .ok_or(Error::MemoryOutOfBounds)?;
        Ok(())
    }

    /// Emit a byte.
    pub fn emit(&mut self, byte: u8) {
        self.buffer.push(byte);
    }

    /// Emit n bytes.
    pub fn emitn(&mut self, bytes: &[u8]) {
        self.buffer.extend_from_slice(bytes);
    }

    /// Emit a single opcode.
    ///
    /// Mock the stack input and output for checking
    /// the stack usages.
    pub fn emit_op(&mut self, opcode: OpCode) -> Result<()> {
        tracing::trace!("emit opcode: {:?}", opcode);
        self.decrement_sp(opcode.stack_in())?;
        self.emit(opcode.into());
        self.increment_gas(opcode.gas().into());
        self.increment_sp(opcode.stack_out())?;

        Ok(())
    }
}

macro_rules! impl_opcodes {
    ($($name:ident => $opcode:ident),+) => {
        $(
            #[doc = concat!(" Emit ", stringify!($opcode))]
            pub fn $name(&mut self) -> Result<()> {
                self.emit_op(OpCode::$opcode)?;
                Ok(())
            }
        )*
    };
}

/// Basic instruction implementations
impl Assembler {
    for_each_cancun_operator!(impl_opcodes);
}