use super::{
    crypto::MerkleError,
    system::{FMP_MAX, FMP_MIN},
    CodeBlock, Digest, Felt, QuadFelt, Word,
};
use alloc::string::String;
use core::fmt::{Display, Formatter};
use vm_core::{stack::STACK_TOP_SIZE, utils::to_hex};
use winter_prover::{math::FieldElement, ProverError};
#[cfg(feature = "std")]
use std::error::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecutionError {
    AdviceMapKeyNotFound(Word),
    AdviceStackReadFailed(u32),
    CallerNotInSyscall,
    CodeBlockNotFound(Digest),
    CycleLimitExceeded(u32),
    DivideByZero(u32),
    DynamicCodeBlockNotFound(Digest),
    EventError(String),
    Ext2InttError(Ext2InttError),
    FailedAssertion {
        clk: u32,
        err_code: u32,
        err_msg: Option<String>,
    },
    FailedSignatureGeneration(&'static str),
    InvalidFmpValue(Felt, Felt),
    InvalidFriDomainSegment(u64),
    InvalidFriLayerFolding(QuadFelt, QuadFelt),
    InvalidMemoryRange {
        start_addr: u64,
        end_addr: u64,
    },
    InvalidStackDepthOnReturn(usize),
    InvalidStackWordOffset(usize),
    InvalidTreeDepth {
        depth: Felt,
    },
    InvalidTreeNodeIndex {
        depth: Felt,
        value: Felt,
    },
    LogArgumentZero(u32),
    MalformedSignatureKey(&'static str),
    MemoryAddressOutOfBounds(u64),
    MerklePathVerificationFailed {
        value: Word,
        index: Felt,
        root: Digest,
    },
    MerkleStoreLookupFailed(MerkleError),
    MerkleStoreMergeFailed(MerkleError),
    MerkleStoreUpdateFailed(MerkleError),
    NotBinaryValue(Felt),
    NotU32Value(Felt, Felt),
    ProverError(ProverError),
    SmtNodeNotFound(Word),
    SmtNodePreImageNotValid(Word, usize),
    SyscallTargetNotInKernel(Digest),
    UnexecutableCodeBlock(CodeBlock),
}
impl Display for ExecutionError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
        use ExecutionError::*;
        match self {
            AdviceMapKeyNotFound(key) => {
                let hex = to_hex(Felt::elements_as_bytes(key))?;
                write!(f, "Value for key {hex} not present in the advice map")
            }
            AdviceStackReadFailed(step) => write!(f, "Advice stack read failed at step {step}"),
            CallerNotInSyscall => {
                write!(f, "Instruction `caller` used outside of kernel context")
            }
            CodeBlockNotFound(digest) => {
                let hex = to_hex(&digest.as_bytes())?;
                write!(
                    f,
                    "Failed to execute code block with root {hex}; the block could not be found"
                )
            }
            CycleLimitExceeded(max_cycles) => {
                write!(f, "Exceeded the allowed number of cycles (max cycles = {max_cycles})")
            }
            DivideByZero(clk) => write!(f, "Division by zero at clock cycle {clk}"),
            DynamicCodeBlockNotFound(digest) => {
                let hex = to_hex(&digest.as_bytes())?;
                write!(
                    f,
                    "Failed to execute the dynamic code block provided by the stack with root {hex}; the block could not be found"
                )
            }
            EventError(error) => write!(f, "Failed to process event - {error}"),
            Ext2InttError(err) => write!(f, "Failed to execute Ext2Intt operation: {err}"),
            FailedAssertion {
                clk,
                err_code,
                err_msg,
            } => {
                if let Some(err_msg) = err_msg {
                    write!(
                        f,
                        "Assertion failed at clock cycle {clk} with error code {err_code}: {err_msg}"
                    )
                } else {
                    write!(f, "Assertion failed at clock cycle {clk} with error code {err_code}")
                }
            }
            FailedSignatureGeneration(signature) => {
                write!(f, "Failed to generate signature: {signature}")
            }
            InvalidFmpValue(old, new) => {
                write!(f, "Updating FMP register from {old} to {new} failed because {new} is outside of {FMP_MIN}..{FMP_MAX}")
            }
            InvalidFriDomainSegment(value) => {
                write!(f, "FRI domain segment value cannot exceed 3, but was {value}")
            }
            InvalidFriLayerFolding(expected, actual) => {
                write!(f, "Degree-respecting projection is inconsistent: expected {expected} but was {actual}")
            }
            InvalidMemoryRange {
                start_addr,
                end_addr,
            } => {
                write!(f, "Memory range start address cannot exceed end address, but was ({start_addr}, {end_addr})")
            }
            InvalidStackDepthOnReturn(depth) => {
                write!(f, "When returning from a call, stack depth must be {STACK_TOP_SIZE}, but was {depth}")
            }
            InvalidStackWordOffset(offset) => {
                write!(f, "Stack word offset cannot exceed 12, but was {offset}")
            }
            InvalidTreeDepth { depth } => {
                write!(f, "The provided {depth} is out of bounds and cannot be represented as an unsigned 8-bits integer")
            }
            InvalidTreeNodeIndex { depth, value } => {
                write!(f, "The provided index {value} is out of bounds for a node at depth {depth}")
            }
            LogArgumentZero(clk) => {
                write!(
                    f,
                    "Calculating of the integer logarithm with zero argument at clock cycle {clk}"
                )
            }
            MalformedSignatureKey(signature) => write!(f, "Malformed signature key: {signature}"),
            MemoryAddressOutOfBounds(addr) => {
                write!(f, "Memory address cannot exceed 2^32 but was {addr}")
            }
            MerklePathVerificationFailed { value, index, root } => {
                let value = to_hex(Felt::elements_as_bytes(value))?;
                let root = to_hex(&root.as_bytes())?;
                write!(f, "Merkle path verification failed for value {value} at index {index}, in the Merkle tree with root {root}")
            }
            MerkleStoreLookupFailed(reason) => {
                write!(f, "Advice provider Merkle store backend lookup failed: {reason}")
            }
            MerkleStoreMergeFailed(reason) => {
                write!(f, "Advice provider Merkle store backend merge failed: {reason}")
            }
            MerkleStoreUpdateFailed(reason) => {
                write!(f, "Advice provider Merkle store backend update failed: {reason}")
            }
            NotBinaryValue(v) => {
                write!(f, "An operation expected a binary value, but received {v}")
            }
            NotU32Value(v, err_code) => {
                write!(
                    f,
                    "An operation expected a u32 value, but received {v} (error code: {err_code})"
                )
            }
            SmtNodeNotFound(node) => {
                let node_hex = to_hex(Felt::elements_as_bytes(node))?;
                write!(f, "Smt node {node_hex} not found")
            }
            SmtNodePreImageNotValid(node, preimage_len) => {
                let node_hex = to_hex(Felt::elements_as_bytes(node))?;
                write!(f, "Invalid pre-image for node {node_hex}. Expected pre-image length to be a multiple of 8, but was {preimage_len}")
            }
            ProverError(error) => write!(f, "Proof generation failed: {error}"),
            SyscallTargetNotInKernel(proc) => {
                let hex = to_hex(&proc.as_bytes())?;
                write!(f, "Syscall failed: procedure with root {hex} was not found in the kernel")
            }
            UnexecutableCodeBlock(block) => {
                write!(f, "Execution reached unexecutable code block {block:?}")
            }
        }
    }
}
#[cfg(feature = "std")]
impl Error for ExecutionError {}
impl From<Ext2InttError> for ExecutionError {
    fn from(value: Ext2InttError) -> Self {
        Self::Ext2InttError(value)
    }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Ext2InttError {
    DomainSizeNotPowerOf2(u64),
    DomainSizeTooSmall(u64),
    InputEndAddressTooBig(u64),
    InputSizeTooBig(u64),
    InputStartAddressTooBig(u64),
    OutputSizeTooBig(usize, usize),
    OutputSizeIsZero,
    UninitializedMemoryAddress(u32),
}
impl Display for Ext2InttError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
        use Ext2InttError::*;
        match self {
            DomainSizeNotPowerOf2(v) => {
                write!(f, "input domain size must be a power of two, but was {v}")
            }
            DomainSizeTooSmall(v) => {
                write!(f, "input domain size ({v} elements) is too small")
            }
            InputEndAddressTooBig(addr) => {
                write!(f, "address of the last input must be smaller than 2^32, but was {addr}")
            }
            InputSizeTooBig(size) => {
                write!(f, "input size must be smaller than 2^32, but was {size}")
            }
            InputStartAddressTooBig(addr) => {
                write!(f, "address of the first input must be smaller than 2^32, but was {addr}")
            }
            OutputSizeIsZero => {
                write!(f, "output size must be greater than 0")
            }
            OutputSizeTooBig(output_size, input_size) => {
                write!(
                    f,
                    "output size ({output_size}) cannot be greater than the input size ({input_size})"
                )
            }
            UninitializedMemoryAddress(address) => {
                write!(f, "uninitialized memory at address {address}")
            }
        }
    }
}
#[cfg(feature = "std")]
impl Error for Ext2InttError {}