/* SPDX-FileCopyrightText: © 2025 Decompollaborate */
/* SPDX-License-Identifier: MIT */

use core::fmt;

#[cfg(not(feature = "std"))]
use core::error;
#[cfg(feature = "std")]
use std::error;

use crate::encoder::BracketType;
use crate::instr_suffixes::InstrSuffix;
use crate::opcodes::Opcode;
use crate::operands::Operand;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum EncodingError<'s> {
    CommaInsteadOfOpcode,
    UnrecognizedOpcode(&'s str),
    BracketedInsteadOfOpcode(&'s str, &'s str, BracketType),
    BracketSoloInsteadOfOpcode(&'s str, BracketType),
    RanOutOfTokens(Opcode, Operand),
    EndTokenInsteadOfOperand(Opcode, Operand),
    CommaInsteadOfOperand(Opcode, Operand),
    BracketedInsteadOfSingleOperand(Opcode, Operand, &'s str, &'s str, BracketType),
    BracketSoloInsteadOfSingleOperand(Opcode, Operand, &'s str, BracketType),
    TextInsteadOfBracketedOperand(Opcode, Operand, &'s str, BracketType),
    BracketSoloInsteadOfBracketedOperand(Opcode, Operand, &'s str, BracketType, BracketType),
    BracketedInsteadOfBracketSoloOperand(
        Opcode,
        Operand,
        &'s str,
        &'s str,
        BracketType,
        BracketType,
    ),
    WrongBracketedOperand(Opcode, Operand, &'s str, &'s str, BracketType, BracketType),
    UnrecognizedOperand(Opcode, &'s str, Option<(&'s str, BracketType)>, Operand),
    EndButMissingOperands(Opcode, usize),
    TokenInsteadOfCommaEnd(Opcode, Operand, &'s str),
    BracketedInsteadOfCommaEnd(Opcode, Operand, &'s str, &'s str, BracketType),
    BracketSoloInsteadOfCommaEnd(Opcode, Operand, &'s str, BracketType),
    MissingCommaInComposedOperand(Opcode, Operand),
    MalformedInstrSuffix(Opcode, &'s str),
    UnrecognizedInstrSuffix(Opcode, InstrSuffix, &'s str, char),
    InvalidDuplicatedSuffix(Opcode, InstrSuffix, &'s str, char),
}

impl fmt::Display for EncodingError<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::CommaInsteadOfOpcode => write!(f, "Found a comma instead of an opcode"),
            Self::UnrecognizedOpcode(text) => write!(f, "The token '{}' did not match any known opcode", text),
            Self::BracketedInsteadOfOpcode(left, right, bracket_type) => {
                write!(f, "The token '")?;
                write_bracketed(f, *bracket_type, left, right)?;
                write!(f, "' is not a valid opcode")
            }
            Self::BracketSoloInsteadOfOpcode(text, bracket_type) => {
                write!(f, "The token '")?;
                write_bracketed(f, *bracket_type, "", text)?;
                write!(f, "' is not a valid opcode")
            }
            Self::RanOutOfTokens(opcode, operand) => write!(f, "Unable to encode opcode '{:?}': Ran out of tokens before being able to encode operand '{:?}'", opcode, operand),
            Self::EndTokenInsteadOfOperand(opcode, operand) => write!(f, "Unable to encode opcode '{:?}': End token found instead of operand for '{:?}'", opcode, operand),
            Self::CommaInsteadOfOperand(opcode, operand) => write!(f, "Unable to encode opcode '{:?}': Comma found instead of operand for '{:?}'", opcode, operand),
            Self::BracketedInsteadOfSingleOperand(opcode, operand, left, right, bracket_type) => {
                write!(f, "Unable to encode opcode '{:?}': Token '", opcode)?;
                write_bracketed(f, *bracket_type, left, right)?;
                write!(f, "' is not valid for operand for '{:?}'", operand)
            }
            Self::BracketSoloInsteadOfSingleOperand(opcode, operand, text, bracket_type) => {
                write!(f, "Unable to encode opcode '{:?}': Token '", opcode)?;
                write_bracketed(f, *bracket_type, "", text)?;
                write!(f, "' is not valid for operand for '{:?}'", operand)
            }
            Self::TextInsteadOfBracketedOperand(opcode, operand, text, bracket_type) => write!(f, "Unable to encode opcode '{:?}': Found token '{}' instead of bracketed expression '{}{}' when trying to encode operand '{:?}'", opcode, text, bracket_type.left(), bracket_type.right(), operand),
            Self::BracketSoloInsteadOfBracketedOperand(opcode, operand, text, bracket_type, _required_bracket_type) => {
                write!(f, "Unable to encode opcode '{:?}': Token '", opcode)?;
                write_bracketed(f, *bracket_type, "", text)?;
                write!(f, "' is not valid for operand for '{:?}'", operand)
            }
            Self::BracketedInsteadOfBracketSoloOperand(opcode, operand, left, right, bracket_type, _required_bracket_type) => {
                write!(f, "Unable to encode opcode '{:?}': Token '", opcode)?;
                write_bracketed(f, *bracket_type, left, right)?;
                write!(f, "' is not valid for operand for '{:?}'", operand)
            }
            Self::WrongBracketedOperand(opcode, operand, left, right, bracket_type, required_bracket_type) => {
                write!(f, "Unable to encode opcode '{:?}': Found wrong kind of bracketed expression when trying to encode operand '{:?}'. ", opcode, operand)?;
                write!(f, "Expected expression '")?;
                write_bracketed(f, *required_bracket_type, left, right)?;
                write!(f, "', but found '")?;
                write_bracketed(f, *bracket_type, left, right)?;
                write!(f, "'")
            }
            Self::UnrecognizedOperand(opcode, text, right, operand) => {
                write!(f, "Unable to encode opcode '{:?}': The token '", opcode)?;
                if let Some((right, bracket_type)) = right {
                    write_bracketed(f, *bracket_type, text, right)?;
                } else {
                    write!(f, "{}", text)?;
                }
                write!(f, "' could not be encoded as operand '{:?}'", operand)
            }
            Self::EndButMissingOperands(opcode, reamining_operands) => write!(f, "Unable to encode opcode '{:?}': Reached end of stream, but there were still {} operands to process", opcode, reamining_operands),
            Self::TokenInsteadOfCommaEnd(opcode, operand, text) => write!(f, "Unable to encode opcode '{:?}': Found unrecognized token '{}' instead of comma or end while processing operand '{:?}'", opcode, text, operand),
            Self::BracketedInsteadOfCommaEnd(opcode, operand, left, right, bracket_type) => {
                write!(f, "Unable to encode opcode '{:?}': Found unrecognized token '", opcode)?;
                write_bracketed(f, *bracket_type, left, right)?;
                write!(f, "' instead of comma or end while processing operand '{:?}'", operand)
            }
            Self::BracketSoloInsteadOfCommaEnd(opcode, operand, text, bracket_type) => {
                write!(f, "Unable to encode opcode '{:?}': Found unrecognized token '", opcode)?;
                write_bracketed(f, *bracket_type, "", text)?;
                write!(f, "' instead of comma or end while processing operand '{:?}'", operand)
            }
            Self::MissingCommaInComposedOperand(opcode, operand) => {
                write!(f, "Unable to encode opcode '{:?}': Missing comma in composed operand '{:?}'", opcode, operand)
            }
            Self::MalformedInstrSuffix(opcode, suffix_str) => {
                write!(f, "Instruction suffix '{}' for opcode '{:?}' is malformed", suffix_str, opcode)
            }
            Self::UnrecognizedInstrSuffix(opcode, expected_instr_suffix, suffix_str, invalid_char) => {
                write!(f, "Unable to encode '{:?}' ('{}{}'): The character '{}' is not valid for the instruction suffix '{:?}'", opcode, opcode.name(), suffix_str, invalid_char, expected_instr_suffix)
            }
            Self::InvalidDuplicatedSuffix(opcode, _expected_instr_suffix, suffix_str, invalid_char) => {
                write!(f, "Unable to encode '{:?}' ('{}{}'): The suffix character '{}' is duplicated", opcode, opcode.name(), suffix_str, invalid_char)
            }
        }
    }
}

impl error::Error for EncodingError<'_> {}

fn write_bracketed(
    f: &mut fmt::Formatter<'_>,
    bracket_type: BracketType,
    left: &str,
    right: &str,
) -> fmt::Result {
    write!(
        f,
        "{}{}{}{}",
        left,
        bracket_type.left(),
        right,
        bracket_type.right()
    )
}
