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

use crate::access_type::AccessType;
use crate::encoded_field_mask::EncodedFieldMask;
use crate::instr::InstrType;
use crate::instr_suffixes::InstrSuffix;
#[cfg(any(
    feature = "RSP",
    feature = "R3000GTE",
    feature = "R4000ALLEGREX",
    feature = "R5900EE",
))]
use crate::isa::IsaExtension;
use crate::isa::IsaVersion;
use crate::opcodes::{Opcode, OpcodeCategory, OpcodeDescriptor, OPCODES};
use crate::operands::{Operand, OperandIterator, OPERAND_COUNT_MAX};

// Rust doesn't have a way to automatically get the larger value of an enum and
// I didn't want to have a `Opcode::MAX` value, so instead we manually maintain
// this constant.
pub(crate) const OPCODE_COUNT: usize = {
    let mut count = 1;
    count += 143;

    if cfg!(feature = "MIPS_II") {
        count += 44;
    }
    if cfg!(feature = "MIPS_III") {
        count += 45;
    }
    if cfg!(feature = "MIPS_IV") {
        count += 3;
    }

    if cfg!(feature = "RSP") {
        count += 73;
    }
    if cfg!(feature = "R3000GTE") {
        count += 22;
    }
    if cfg!(feature = "R4000ALLEGREX") {
        count += 294;
    }
    if cfg!(feature = "R5900EE") {
        count += 266;
    }

    if cfg!(feature = "RspViceMsp") {
        count += 21;
    }

    count
};

impl Opcode {
    #[must_use]
    pub fn get_descriptor(&self) -> &'static OpcodeDescriptor {
        &OPCODES[*self]
    }
}

/// Getters
impl Opcode {
    #[must_use]
    pub fn opcode_category(&self) -> OpcodeCategory {
        self.get_descriptor().opcode_category()
    }
    #[must_use]
    pub fn bitpattern(&self) -> u32 {
        self.get_descriptor().bitpattern()
    }

    #[must_use]
    pub fn name(&self) -> &'static str {
        self.get_descriptor().name()
    }

    #[must_use]
    pub fn isa_version(&self) -> IsaVersion {
        self.get_descriptor().isa_version()
    }
    #[cfg(any(
        feature = "RSP",
        feature = "R3000GTE",
        feature = "R4000ALLEGREX",
        feature = "R5900EE",
    ))]
    #[must_use]
    pub fn isa_extension(&self) -> Option<IsaExtension> {
        self.get_descriptor().isa_extension()
    }

    #[must_use]
    pub fn operands(&self) -> &[Operand; OPERAND_COUNT_MAX] {
        self.get_descriptor().operands()
    }
    #[must_use]
    pub fn operands_iter(&self) -> OperandIterator<'static> {
        self.get_descriptor().operands_iter()
    }

    #[must_use]
    pub fn instr_type(&self) -> InstrType {
        self.get_descriptor().instr_type()
    }
    #[must_use]
    pub fn instr_suffix(&self) -> Option<InstrSuffix> {
        self.get_descriptor().instr_suffix()
    }
    #[must_use]
    pub fn is_branch(&self) -> bool {
        self.get_descriptor().is_branch()
    }
    #[must_use]
    pub fn is_branch_likely(&self) -> bool {
        self.get_descriptor().is_branch_likely()
    }
    #[must_use]
    pub fn is_jump(&self) -> bool {
        self.get_descriptor().is_jump()
    }
    #[must_use]
    pub fn is_jump_with_address(&self) -> bool {
        self.get_descriptor().is_jump_with_address()
    }
    #[must_use]
    pub fn jumps_to_register(&self) -> bool {
        self.get_descriptor().jumps_to_register()
    }
    #[must_use]
    pub fn is_trap(&self) -> bool {
        self.get_descriptor().is_trap()
    }
    #[must_use]
    pub fn causes_exception(&self) -> bool {
        self.get_descriptor().causes_exception()
    }
    #[must_use]
    pub fn causes_unconditional_exception(&self) -> bool {
        self.get_descriptor().causes_unconditional_exception()
    }
    #[must_use]
    pub fn causes_conditional_exception(&self) -> bool {
        self.get_descriptor().causes_conditional_exception()
    }
    #[must_use]
    pub fn causes_returnable_exception(&self) -> bool {
        self.get_descriptor().causes_returnable_exception()
    }
    #[must_use]
    pub fn is_float(&self) -> bool {
        self.get_descriptor().is_float()
    }
    #[must_use]
    pub fn is_double(&self) -> bool {
        self.get_descriptor().is_double()
    }
    #[must_use]
    pub fn modifies_rs(&self) -> bool {
        self.get_descriptor().modifies_rs()
    }
    #[must_use]
    pub fn modifies_rt(&self) -> bool {
        self.get_descriptor().modifies_rt()
    }
    #[must_use]
    pub fn modifies_rd(&self) -> bool {
        self.get_descriptor().modifies_rd()
    }
    #[must_use]
    pub fn reads_rs(&self) -> bool {
        self.get_descriptor().reads_rs()
    }
    #[must_use]
    pub fn reads_rt(&self) -> bool {
        self.get_descriptor().reads_rt()
    }
    #[must_use]
    pub fn reads_rd(&self) -> bool {
        self.get_descriptor().reads_rd()
    }
    #[must_use]
    pub fn reads_hi(&self) -> bool {
        self.get_descriptor().reads_hi()
    }
    #[must_use]
    pub fn reads_lo(&self) -> bool {
        self.get_descriptor().reads_lo()
    }
    #[must_use]
    pub fn modifies_hi(&self) -> bool {
        self.get_descriptor().modifies_hi()
    }
    #[must_use]
    pub fn modifies_lo(&self) -> bool {
        self.get_descriptor().modifies_lo()
    }
    #[must_use]
    pub fn modifies_fs(&self) -> bool {
        self.get_descriptor().modifies_fs()
    }
    #[must_use]
    pub fn modifies_ft(&self) -> bool {
        self.get_descriptor().modifies_ft()
    }
    #[must_use]
    pub fn modifies_fd(&self) -> bool {
        self.get_descriptor().modifies_fd()
    }
    #[must_use]
    pub fn reads_fs(&self) -> bool {
        self.get_descriptor().reads_fs()
    }
    #[must_use]
    pub fn reads_ft(&self) -> bool {
        self.get_descriptor().reads_ft()
    }
    #[must_use]
    pub fn reads_fd(&self) -> bool {
        self.get_descriptor().reads_fd()
    }
    #[must_use]
    pub fn not_emitted_by_compilers(&self) -> bool {
        self.get_descriptor().not_emitted_by_compilers()
    }
    #[must_use]
    pub fn can_be_hi(&self) -> bool {
        self.get_descriptor().can_be_hi()
    }
    #[must_use]
    pub fn can_be_lo(&self) -> bool {
        self.get_descriptor().can_be_lo()
    }
    #[must_use]
    pub fn can_be_unsigned_lo(&self) -> bool {
        self.get_descriptor().can_be_unsigned_lo()
    }
    #[must_use]
    pub fn does_link(&self) -> bool {
        self.get_descriptor().does_link()
    }
    #[must_use]
    pub fn does_dereference(&self) -> bool {
        self.get_descriptor().does_dereference()
    }
    #[must_use]
    pub fn does_load(&self) -> bool {
        self.get_descriptor().does_load()
    }
    #[must_use]
    pub fn does_store(&self) -> bool {
        self.get_descriptor().does_store()
    }
    #[must_use]
    pub fn adds_registers(&self) -> bool {
        self.get_descriptor().adds_registers()
    }
    #[must_use]
    pub fn subs_registers(&self) -> bool {
        self.get_descriptor().subs_registers()
    }
    #[must_use]
    pub fn ors_registers(&self) -> bool {
        self.get_descriptor().ors_registers()
    }
    #[must_use]
    pub fn ands_registers(&self) -> bool {
        self.get_descriptor().ands_registers()
    }
    #[must_use]
    pub fn is_pseudo(&self) -> bool {
        self.get_descriptor().is_pseudo()
    }
    #[must_use]
    pub fn access_type(&self) -> Option<AccessType> {
        self.get_descriptor().access_type()
    }
    #[must_use]
    pub fn does_unsigned_memory_access(&self) -> bool {
        self.get_descriptor().does_unsigned_memory_access()
    }
}

impl Opcode {
    #[must_use]
    pub fn opcode_bits(&self) -> u32 {
        self.get_descriptor().opcode_bits()
    }

    #[must_use]
    pub fn valid_bits(&self) -> EncodedFieldMask {
        self.get_descriptor().valid_bits()
    }

    #[must_use]
    pub fn has_delay_slot(&self) -> bool {
        self.get_descriptor().has_delay_slot()
    }

    #[must_use]
    pub fn has_any_operands(&self) -> bool {
        self.get_descriptor().has_any_operands()
    }

    #[must_use]
    pub fn has_specific_operand(&self, operand: Operand) -> bool {
        self.get_descriptor().has_specific_operand(operand)
    }

    #[must_use]
    pub fn has_operand_alias(&self, operand: Operand) -> bool {
        self.get_descriptor().has_operand_alias(operand)
    }
}

impl Opcode {
    /// Returns a default value.
    #[must_use]
    pub const fn default() -> Self {
        Self::ALL_INVALID
    }

    #[must_use]
    pub(crate) const fn is_valid(&self) -> bool {
        !matches!(*self, Self::ALL_INVALID)
    }
}

impl Default for Opcode {
    fn default() -> Self {
        Self::default()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_j() {
        assert!(OPCODES[Opcode::core_j].is_jump);
        assert!(Opcode::core_j.get_descriptor().is_jump);
        assert!(OPCODES[Opcode::core_j].is_jump_with_address);
        assert!(Opcode::core_j.get_descriptor().is_jump_with_address);
        assert!(!OPCODES[Opcode::core_j].is_branch);
        assert!(!Opcode::core_j.get_descriptor().is_branch);
    }
}
