use crate::{ComplexType, Value};
use espy_heart::debug;
use std::num::NonZero;

#[must_use]
pub struct Error {
    /// Program counter relative to the start of the file rather than the start of a block.
    ///
    /// This can be used to derive the block, local pc, line number, etc.
    /// It is "nonzero" to reduce the size of the error structure;
    /// the first byte of an espy binary is never executable code.
    absolute_program_counter: Option<NonZero<u32>>,
    kind: ErrorKind,
}

impl Error {
    pub fn kind(&self) -> &ErrorKind {
        &self.kind
    }

    pub fn into_kind(self) -> ErrorKind {
        self.kind
    }

    pub fn suggest_location(self, absolute_program_counter: usize) -> Self {
        Self {
            absolute_program_counter: self.absolute_program_counter.or_else(|| {
                u32::try_from(absolute_program_counter)
                    .ok()?
                    .try_into()
                    .ok()
            }),
            ..self
        }
    }

    /// Converts the error's location hint into the source code span it originated from.
    ///
    /// This may result in `None`--not all errors have locations.
    ///
    /// # Errors
    ///
    /// Returns a [`DebugInfoError`] if the debug info is malformed.
    ///
    /// The reported position of this error may be trusted and will never be the
    /// source of an error.
    pub fn locate(&self, debug_info: &[u8]) -> Result<Option<(usize, usize)>, DebugInfoError> {
        let Some(absolute_program_counter) = self.absolute_program_counter.map(|x| {
            u32::from(x)
                .try_into()
                .expect("program counter should not exceed usize")
        }) else {
            return Ok(None);
        };
        let mut this_pc = 0;
        let mut last_span = None;
        let mut debug_info = debug_info.iter().copied();
        while let Some(directive) = debug_info.next() {
            match directive {
                debug::SPAN => {
                    let mut get_u64 = || {
                        let mut unpacked = [0; size_of::<u64>()];
                        for dest in unpacked.iter_mut().take(debug_info.next()? as usize) {
                            *dest = debug_info.next()?;
                        }
                        Some(u64::from_le_bytes(unpacked))
                    };
                    let start = get_u64().ok_or(DebugInfoError {
                        kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
                    })?;
                    let end = get_u64().ok_or(DebugInfoError {
                        kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
                    })?;
                    last_span = Some((start, end));
                    this_pc += 1;
                }
                debug::DITTO => {
                    this_pc += debug_info.next().ok_or(DebugInfoError {
                        kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
                    })? as usize;
                }
                _ => {
                    return Err(DebugInfoError {
                        kind: DebugInfoErrorKind::UnknownDirective,
                    });
                }
            }
            if this_pc > absolute_program_counter {
                return Ok(last_span.map(|(start, end)| (start as usize, end as usize)));
            }
        }
        Err(DebugInfoError {
            kind: DebugInfoErrorKind::UnexpectedEndOfSequence,
        })
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.kind.fmt(f)
    }
}

impl std::fmt::Debug for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.kind.fmt(f)
    }
}

impl std::error::Error for Error {}

impl Error {
    pub fn type_error(value: &Value, ty: &ComplexType) -> Self {
        ErrorKind::TypeError {
            value: format!("{value:?}").into_boxed_str(),
            ty: format!("{:?}", ty).into_boxed_str(),
        }
        .into()
    }

    pub fn index_not_found(container: &Value, index: &Value) -> Self {
        ErrorKind::IndexNotFound {
            index: format!("{index:?}").into_boxed_str(),
            container: format!("{container:?}").into_boxed_str(),
        }
        .into()
    }

    pub fn expected_integers(l: &Value, r: &Value) -> Self {
        ErrorKind::ExpectedIntegers(
            format!("{l:?}").into_boxed_str(),
            format!("{r:?}").into_boxed_str(),
        )
        .into()
    }

    pub fn expected_tuple(i: &Value) -> Self {
        ErrorKind::ExpectedTuple(format!("{i:?}").into_boxed_str()).into()
    }

    pub fn expected_named_tuple(i: &Value) -> Self {
        ErrorKind::ExpectedNamedTuple(format!("{i:?}").into_boxed_str()).into()
    }

    pub fn expected_function(function: &Value) -> Self {
        ErrorKind::ExpectedFunction(format!("{function:?}").into_boxed_str()).into()
    }

    pub fn incomparable_values(l: &Value, r: &Value) -> Self {
        ErrorKind::IncomparableValues(
            format!("{l:?}").into_boxed_str(),
            format!("{r:?}").into_boxed_str(),
        )
        .into()
    }

    pub fn expected_enum_variant(i: &Value) -> Error {
        ErrorKind::ExpectedEnumVariant(format!("{i:?}").into_boxed_str()).into()
    }

    pub fn expected_enum_type(i: &Value) -> Error {
        ErrorKind::ExpectedEnumType(format!("{i:?}").into_boxed_str()).into()
    }

    pub fn expected_option(i: &Value) -> Error {
        ErrorKind::ExpectedOption(format!("{i:?}").into_boxed_str()).into()
    }

    pub fn expected_reference(i: &Value) -> Error {
        ErrorKind::ExpectedReference(format!("{i:?}").into_boxed_str()).into()
    }

    pub fn other(e: impl Into<anyhow::Error>) -> Error {
        ErrorKind::Other(e.into()).into()
    }

    pub fn custom(message: &'static str) -> Error {
        ErrorKind::Other(anyhow::Error::msg(message)).into()
    }
}

impl From<ErrorKind> for Error {
    fn from(kind: ErrorKind) -> Self {
        Self {
            absolute_program_counter: None,
            kind,
        }
    }
}

impl From<anyhow::Error> for Error {
    fn from(e: anyhow::Error) -> Error {
        Error::other(e)
    }
}

#[derive(Debug)]
#[non_exhaustive]
#[must_use]
pub enum ErrorKind {
    ExpectedIntegers(Box<str>, Box<str>),
    ExpectedFunction(Box<str>),
    ExpectedEnumVariant(Box<str>),
    ExpectedOption(Box<str>),
    ExpectedEnumType(Box<str>),
    ExpectedTuple(Box<str>),
    ExpectedNamedTuple(Box<str>),
    ExpectedReference(Box<str>),

    IncomparableValues(Box<str>, Box<str>),
    TypeError {
        value: Box<str>,
        ty: Box<str>,
    },
    CalledNeverFunction,
    IndexNotFound {
        index: Box<str>,
        container: Box<str>,
    },
    OutOfScopeMut,
    InvalidBytecode(InvalidBytecode),

    Other(anyhow::Error),
}

impl std::fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ErrorKind::ExpectedIntegers(l, r) => {
                write!(f, "expected two integers, got {l} and {r}")
            }
            ErrorKind::ExpectedFunction(i) => write!(f, "expected a function, got {i}"),
            ErrorKind::ExpectedEnumVariant(i) => write!(f, "expected an enum variant, got {i}"),
            ErrorKind::ExpectedOption(i) => write!(f, "expected an option, got {i}"),
            ErrorKind::ExpectedEnumType(i) => write!(f, "expected an enum, got {i}"),
            ErrorKind::ExpectedTuple(i) => write!(f, "expected a tuple or named tuple, got {i}"),
            ErrorKind::ExpectedNamedTuple(i) => write!(f, "expected a named tuple, got {i}"),
            ErrorKind::ExpectedReference(i) => write!(f, "expected a reference type, got {i}"),
            ErrorKind::IncomparableValues(l, r) => write!(f, "{l} cannot be compared to {r}"),
            ErrorKind::TypeError { value, ty } => {
                write!(f, "expected a value of type {ty}, got {value}")
            }
            ErrorKind::CalledNeverFunction => write!(f, "attempted to call a \"never\" function"),
            ErrorKind::IndexNotFound { index, container } => {
                write!(f, "{container} does not contain the field {index}")
            }
            ErrorKind::OutOfScopeMut => write!(
                f,
                "attempted to read or write a mutable value which has gone out of scope"
            ),
            ErrorKind::InvalidBytecode(invalid_bytecode) => {
                write!(f, "invalid bytecode: {invalid_bytecode}")
            }
            ErrorKind::Other(error) => error.fmt(f),
        }
    }
}

/// Errors that may only be caused due to invalid bytecode.
///
/// If this error is returned by an espy program compiled by a compatible
/// version of the compiler, it is considered an espy bug.
#[derive(Debug)]
pub enum InvalidBytecode {
    /// Caused by an imbalance in stack operations.
    ///
    /// Well-behaved bytecode never has any reason to cause this.
    StackUnderflow,
    /// An instruction byte had an unexpected value.
    InvalidInstruction,
    /// A clone referred to a builtin value that does not exist.
    InvalidBuiltin,
    /// An instruction referred to a string id that did not exist.
    InvalidStringId,
    /// Occurs when the header is too short or
    /// describes a program which is longer than the provided slice.
    MalformedHeader,
    /// Occurs when the program counter becomes greater than the length of the program.
    ///
    /// Note the "*greater*"; a pc of the program's length
    /// (after the last byte) is considered an intentional return.
    ProgramOutOfBounds,
    /// Occurs when a stack access goes beyond the length of the stack.
    StackOutOfBounds,
    Utf8Error(std::str::Utf8Error),
}

impl From<InvalidBytecode> for Error {
    fn from(e: InvalidBytecode) -> Error {
        Error {
            absolute_program_counter: None,
            kind: ErrorKind::InvalidBytecode(e),
        }
    }
}

impl std::fmt::Display for InvalidBytecode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            InvalidBytecode::StackUnderflow => write!(f, "stack underflow"),
            InvalidBytecode::InvalidInstruction => write!(f, "invalid instruction"),
            InvalidBytecode::InvalidBuiltin => write!(f, "invalid builtin"),
            InvalidBytecode::InvalidStringId => write!(f, "unexpected string id"),
            InvalidBytecode::MalformedHeader => write!(f, "malformed header"),
            InvalidBytecode::ProgramOutOfBounds => write!(f, "program out of bounds"),
            InvalidBytecode::StackOutOfBounds => write!(f, "stack out of bounds"),
            InvalidBytecode::Utf8Error(utf8_error) => utf8_error.fmt(f),
        }
    }
}

#[derive(Debug)]
pub struct DebugInfoError {
    kind: DebugInfoErrorKind,
}

impl std::fmt::Display for DebugInfoError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use DebugInfoErrorKind as Kind;
        match &self.kind {
            Kind::UnexpectedEndOfSequence => write!(f, "unexpected end of sequence"),
            Kind::UnknownDirective => write!(f, "unknown directive"),
        }
    }
}

#[derive(Debug)]
pub enum DebugInfoErrorKind {
    UnexpectedEndOfSequence,
    UnknownDirective,
}
