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

use crate::abi::Abi;
use crate::register_descriptors::RegisterDescriptor;
use crate::registers_meta::IntRegisterConversionError;

use super::RegisterIterator;

/// A trait that provides common functionality for every register kind.
pub trait Register: PartialEq + PartialOrd + Default {
    /// Converts this register into a raw number.
    ///
    /// This number can be used as an index of an array of [`count`] elements.
    ///
    /// [`count`]: Register::count
    #[must_use]
    fn as_index(&self) -> usize;

    /// How many registers this type has.
    ///
    /// For example, on the [`Gpr`] type this returns 32.
    ///
    /// [`Gpr`]: crate::registers::Gpr
    #[must_use]
    fn count() -> usize;

    /// Returns an object which allows iterating over every register of this specific register type
    /// in ascending order.
    ///
    /// The given iterator can iterate in reverse too.
    ///
    /// # Examples
    ///
    /// ```
    /// use rabbitizer::{registers::Gpr, registers_meta::Register};
    ///
    /// let mut gpr_iter = Gpr::iter();
    ///
    /// assert_eq!(gpr_iter.next(), Some(Gpr::zero));
    /// assert_eq!(gpr_iter.next(), Some(Gpr::at));
    /// assert_eq!(gpr_iter.next(), Some(Gpr::v0));
    /// assert_eq!(gpr_iter.next_back(), Some(Gpr::ra));
    /// ```
    #[must_use]
    fn iter() -> RegisterIterator<Self> {
        RegisterIterator::new()
    }

    /// Returns a Register that represents the given value.
    ///
    /// The conversion may fail if the value is larger than the amount of
    /// registers in the given register kind.
    fn try_from_u32(value: u32) -> Result<Self, IntRegisterConversionError>;

    /// Returns a reference to an array holding descriptor information for each
    /// register of this kind.
    ///
    /// This array can be indexed by the numeric representation of each
    /// register.
    #[must_use]
    fn descriptor_array() -> &'static [RegisterDescriptor];

    /// Returns the descriptor with the actual data of the register.
    #[must_use]
    fn get_descriptor(&self) -> &'static RegisterDescriptor {
        &Self::descriptor_array()[self.as_index()]
    }

    /// Return the numeric "name" for the register.
    #[must_use]
    fn name_numeric(&self) -> &'static str {
        self.get_descriptor().name_numeric()
    }
    /// Return the an actual name for the register.
    ///
    /// This name may be different depending on the `abi` parameter.
    #[must_use]
    fn name_abi(&self, abi: Abi, strip_dollar: bool) -> &'static str {
        self.get_descriptor().name_abi(abi, strip_dollar)
    }
    /// Returns either the numeric name or the abi name based on the `use_named` parameter.
    #[must_use]
    fn either_name(&self, abi: Abi, use_named: bool, strip_dollar: bool) -> &'static str {
        if use_named {
            self.name_abi(abi, strip_dollar)
        } else {
            self.name_numeric()
        }
    }

    /// A function call invalidates the value hold by the register.
    #[must_use]
    fn is_clobbered_by_func_call(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_clobbered_by_func_call()
    }
    /// This register is not implemented and is reserved for future use.
    #[must_use]
    fn is_reserved(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_reserved()
    }
    /// Kernel-only registers ($k0, $k1).
    #[must_use]
    fn is_kernel(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_kernel()
    }
    /// This register is hardcoded to the zero value ($zero).
    #[must_use]
    fn is_zero(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_zero()
    }
    /// The assembler uses this register as a temporary value to expand and reorder instructions
    /// ($at).
    #[must_use]
    fn is_assembler_temp(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_assembler_temp()
    }
    /// The register usually is used to hold the return value of a function ($v0, $v1)-
    #[must_use]
    fn holds_return_value(&self, _abi: Abi) -> bool {
        self.get_descriptor().holds_return_value()
    }
    /// The register holds the address to return to when returning to the caller of the current
    /// function ($ra).
    #[must_use]
    fn holds_return_address(&self, _abi: Abi) -> bool {
        self.get_descriptor().holds_return_address()
    }

    /// The register holds the stack pointer ($sp).
    #[must_use]
    fn is_stack_pointer(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_stack_pointer()
    }
    /// The register holds the global pointer ($gp).
    #[must_use]
    fn is_global_pointer(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_global_pointer()
    }
    /// A saved register. It will hold the same value after calling a function ($sX).
    #[must_use]
    fn is_saved(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_saved()
    }
    /// A temporary register. The value of this register may not be preserved after calling a
    /// function ($tX).
    #[must_use]
    fn is_temp(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_temp()
    }
    /// The register is used as arguments for functions ($aX).
    #[must_use]
    fn is_arg(&self, _abi: Abi) -> bool {
        self.get_descriptor().is_arg()
    }

    /// Returns the register that corresponds to the given string, or `None` if
    /// the given name does not correspond to any register of the current kind.
    ///
    /// The accepted names will change dependending on the given [`Abi`].
    #[must_use]
    #[cfg(feature = "encoder")]
    #[cfg_attr(docsrs, doc(cfg(feature = "encoder")))]
    fn from_name(name: &str, abi: Abi, allow_dollarless: bool) -> Option<Self> {
        let allow_dollarless = allow_dollarless && !name.starts_with('$');

        for descriptor in Self::descriptor_array() {
            let found = descriptor.name_abi(abi, false) == name
                || (allow_dollarless && descriptor.name_abi(abi, true) == name)
                || descriptor.name_numeric() == name;

            if found {
                let reg_value = descriptor.value().into();
                let reg = Self::try_from_u32(reg_value).expect("The value returned by the descriptor should always be a valid value for the try_from_u32 method");
                return Some(reg);
            }
        }

        None
    }
}

#[cfg(feature = "R4000ALLEGREX")]
pub(crate) trait R4000AllegrexVectorRegister: Register {}
