#![allow(clippy::type_complexity)]
//! Context trait and related types.
pub use crate::journaled_state::StateLoad;
use crate::{
    result::FromStringError, Block, Cfg, Database, Host, JournalTr, LocalContextTr, Transaction,
};
use auto_impl::auto_impl;
use primitives::StorageValue;
use std::string::String;

/// Trait that defines the context of the EVM execution.
///
/// This trait is used to access the environment and state of the EVM.
/// It is used to access the transaction, block, configuration, database, journal, and chain.
/// It is also used to set the error of the EVM.
///
/// All functions have a `*_mut` variant except the function for [`ContextTr::tx`] and [`ContextTr::block`].
#[auto_impl(&mut, Box)]
pub trait ContextTr: Host {
    /// Block type
    type Block: Block;
    /// Transaction type
    type Tx: Transaction;
    /// Configuration type
    type Cfg: Cfg;
    /// Database type
    type Db: Database;
    /// Journal type
    type Journal: JournalTr<Database = Self::Db>;
    /// Chain type
    type Chain;
    /// Local context type
    type Local: LocalContextTr;

    /// Get all contexts
    fn all(
        &self,
    ) -> (
        &Self::Block,
        &Self::Tx,
        &Self::Cfg,
        &Self::Db,
        &Self::Journal,
        &Self::Chain,
        &Self::Local,
    );

    /// Get all contexts mutably
    fn all_mut(
        &mut self,
    ) -> (
        &Self::Block,
        &Self::Tx,
        &Self::Cfg,
        &mut Self::Journal,
        &mut Self::Chain,
        &mut Self::Local,
    );

    /// Get the transaction
    fn tx(&self) -> &Self::Tx {
        let (_, tx, _, _, _, _, _) = self.all();
        tx
    }
    /// Get the block
    fn block(&self) -> &Self::Block {
        let (block, _, _, _, _, _, _) = self.all();
        block
    }
    /// Get the configuration
    fn cfg(&self) -> &Self::Cfg {
        let (_, _, cfg, _, _, _, _) = self.all();
        cfg
    }
    /// Get the journal
    fn journal(&self) -> &Self::Journal {
        let (_, _, _, _, journal, _, _) = self.all();
        journal
    }
    /// Get the journal mutably
    fn journal_mut(&mut self) -> &mut Self::Journal {
        let (_, _, _, journal, _, _) = self.all_mut();
        journal
    }
    /// Get the journal reference
    fn journal_ref(&self) -> &Self::Journal {
        self.journal()
    }
    /// Get the database
    fn db(&self) -> &Self::Db {
        let (_, _, _, db, _, _, _) = self.all();
        db
    }
    /// Get the database mutably
    fn db_mut(&mut self) -> &mut Self::Db {
        let db = self.journal_mut().db_mut();
        db
    }
    /// Get the database reference
    fn db_ref(&self) -> &Self::Db {
        self.db()
    }
    /// Get the chain
    fn chain(&self) -> &Self::Chain {
        let (_, _, _, _, _, chain, _) = self.all();
        chain
    }
    /// Get the chain mutably
    fn chain_mut(&mut self) -> &mut Self::Chain {
        let (_, _, _, _, chain, _) = self.all_mut();
        chain
    }
    /// Get the chain reference
    fn chain_ref(&self) -> &Self::Chain {
        self.chain()
    }
    /// Get the local context
    fn local(&self) -> &Self::Local {
        let (_, _, _, _, _, _, local) = self.all();
        local
    }
    /// Get the local context mutably
    fn local_mut(&mut self) -> &mut Self::Local {
        let (_, _, _, _, _, local) = self.all_mut();
        local
    }
    /// Get the local context reference
    fn local_ref(&self) -> &Self::Local {
        self.local()
    }
    /// Get the error
    fn error(&mut self) -> &mut Result<(), ContextError<<Self::Db as Database>::Error>>;

    /// Get the transaction and journal. It is used to efficiently load access list
    /// into journal without copying them from transaction.
    fn tx_journal_mut(&mut self) -> (&Self::Tx, &mut Self::Journal) {
        let (_, tx, _, journal, _, _) = self.all_mut();
        (tx, journal)
    }

    /// Get the transaction, configuration and mutable journal.
    fn tx_block_cfg_journal_mut(
        &mut self,
    ) -> (&Self::Tx, &Self::Block, &Self::Cfg, &mut Self::Journal) {
        let (block, tx, cfg, journal, _, _) = self.all_mut();
        (tx, block, cfg, journal)
    }

    /// Get the transaction and local context. It is used to efficiently load initcode
    /// into local context without copying them from transaction.
    fn tx_local_mut(&mut self) -> (&Self::Tx, &mut Self::Local) {
        let (_, tx, _, _, _, local) = self.all_mut();
        (tx, local)
    }

    /// Get the configuration and journal mutably
    #[inline]
    fn cfg_journal_mut(&mut self) -> (&Self::Cfg, &mut Self::Journal) {
        let (_, _, cfg, journal, _, _) = self.all_mut();
        (cfg, journal)
    }
}

/// Inner Context error used for Interpreter to set error without returning it from instruction
#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ContextError<DbError> {
    /// Database error.
    Db(DbError),
    /// Custom string error.
    Custom(String),
}

impl<DbError> FromStringError for ContextError<DbError> {
    fn from_string(value: String) -> Self {
        Self::Custom(value)
    }
}

impl<DbError> From<DbError> for ContextError<DbError> {
    fn from(value: DbError) -> Self {
        Self::Db(value)
    }
}

/// Represents the result of an `sstore` operation.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SStoreResult {
    /// Value of the storage when it is first read
    pub original_value: StorageValue,
    /// Current value of the storage
    pub present_value: StorageValue,
    /// New value that is set
    pub new_value: StorageValue,
}

impl SStoreResult {
    /// Returns `true` if the new value is equal to the present value.
    #[inline]
    pub const fn is_new_eq_present(&self) -> bool {
        self.new_value.const_eq(&self.present_value)
    }

    /// Returns `true` if the new values changes the present value.
    #[inline]
    pub const fn new_values_changes_present(&self) -> bool {
        !self.is_new_eq_present()
    }

    /// Returns `true` if the original value is zero and the new value is not zero.
    #[inline]
    pub const fn have_changed_from_zero(&self) -> bool {
        self.is_original_zero() && !self.is_new_zero()
    }

    /// Returns `true` if the original value is equal to the present value.
    #[inline]
    pub const fn is_original_eq_present(&self) -> bool {
        self.original_value.const_eq(&self.present_value)
    }

    /// Returns `true` if the original value is equal to the new value.
    #[inline]
    pub const fn is_original_eq_new(&self) -> bool {
        self.original_value.const_eq(&self.new_value)
    }

    /// Returns `true` if the original value is zero.
    #[inline]
    pub const fn is_original_zero(&self) -> bool {
        self.original_value.const_is_zero()
    }

    /// Returns `true` if the present value is zero.
    #[inline]
    pub const fn is_present_zero(&self) -> bool {
        self.present_value.const_is_zero()
    }

    /// Returns `true` if the new value is zero.
    #[inline]
    pub const fn is_new_zero(&self) -> bool {
        self.new_value.const_is_zero()
    }
}

/// Result of a selfdestruct action
///
/// Value returned are needed to calculate the gas spent.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SelfDestructResult {
    /// Whether the account had a value.
    pub had_value: bool,
    /// Whether the target account exists.
    pub target_exists: bool,
    /// Whether the account was previously destroyed.
    pub previously_destroyed: bool,
}

/// Trait for setting the transaction and block in the context.
pub trait ContextSetters: ContextTr {
    /// Set the transaction
    fn set_tx(&mut self, tx: Self::Tx);
    /// Set the block
    fn set_block(&mut self, block: Self::Block);
}
