//! External names.
//!
//! These are identifiers for declaring entities defined outside the current
//! function. The name of an external declaration doesn't have any meaning to
//! Cranelift, which compiles functions independently.

use crate::ir::{KnownSymbol, LibCall};
use alloc::boxed::Box;
use core::fmt::{self, Write};
use core::str::FromStr;

use cranelift_entity::EntityRef as _;
#[cfg(feature = "enable-serde")]
use serde_derive::{Deserialize, Serialize};

use super::entities::UserExternalNameRef;
use super::function::FunctionParameters;

/// An explicit name for a user-defined function, be it defined in code or in CLIF text.
///
/// This is used both for naming a function (for debugging purposes) and for declaring external
/// functions. In the latter case, this becomes an `ExternalName`, which gets embedded in
/// relocations later, etc.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum UserFuncName {
    /// A user-defined name, with semantics left to the user.
    User(UserExternalName),
    /// A name for a test case, mostly intended for Cranelift testing.
    Testcase(TestcaseName),
}

impl UserFuncName {
    /// Creates a new external name from a sequence of bytes. Caller is expected
    /// to guarantee bytes are only ascii alphanumeric or `_`.
    pub fn testcase<T: AsRef<[u8]>>(v: T) -> Self {
        Self::Testcase(TestcaseName::new(v))
    }

    /// Create a new external name from a user-defined external function reference.
    pub fn user(namespace: u32, index: u32) -> Self {
        Self::User(UserExternalName::new(namespace, index))
    }

    /// Get a `UserExternalName` if this is a user-defined name.
    pub fn get_user(&self) -> Option<&UserExternalName> {
        match self {
            UserFuncName::User(user) => Some(user),
            UserFuncName::Testcase(_) => None,
        }
    }
}

impl Default for UserFuncName {
    fn default() -> Self {
        UserFuncName::User(UserExternalName::default())
    }
}

impl fmt::Display for UserFuncName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UserFuncName::User(user) => user.fmt(f),
            UserFuncName::Testcase(testcase) => testcase.fmt(f),
        }
    }
}

/// An external name in a user-defined symbol table.
///
/// Cranelift does not interpret these numbers in any way, so they can represent arbitrary values.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct UserExternalName {
    /// Arbitrary.
    pub namespace: u32,
    /// Arbitrary.
    pub index: u32,
}

impl UserExternalName {
    /// Creates a new [UserExternalName].
    pub fn new(namespace: u32, index: u32) -> Self {
        Self { namespace, index }
    }
}

impl fmt::Display for UserExternalName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "u{}:{}", self.namespace, self.index)
    }
}

/// A name for a test case.
#[derive(Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct TestcaseName(Box<[u8]>);

impl fmt::Display for TestcaseName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_char('%')?;
        f.write_str(std::str::from_utf8(&self.0).unwrap())
    }
}

impl fmt::Debug for TestcaseName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self}")
    }
}

impl TestcaseName {
    pub(crate) fn new<T: AsRef<[u8]>>(v: T) -> Self {
        Self(v.as_ref().into())
    }
}

/// The name of an external is either a reference to a user-defined symbol
/// table, or a short sequence of ascii bytes so that test cases do not have
/// to keep track of a symbol table.
///
/// External names are primarily used as keys by code using Cranelift to map
/// from a `cranelift_codegen::ir::FuncRef` or similar to additional associated
/// data.
///
/// External names can also serve as a primitive testing and debugging tool.
/// In particular, many `.clif` test files use function names to identify
/// functions.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum ExternalName {
    /// A reference to a name in a user-defined symbol table.
    User(UserExternalNameRef),
    /// A test case function name of up to a hardcoded amount of ascii
    /// characters. This is not intended to be used outside test cases.
    TestCase(TestcaseName),
    /// A well-known runtime library function.
    LibCall(LibCall),
    /// A well-known symbol.
    KnownSymbol(KnownSymbol),
}

impl Default for ExternalName {
    fn default() -> Self {
        Self::User(UserExternalNameRef::new(0))
    }
}

impl ExternalName {
    /// Creates a new external name from a sequence of bytes. Caller is expected
    /// to guarantee bytes are only ascii alphanumeric or `_`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cranelift_codegen::ir::ExternalName;
    /// // Create `ExternalName` from a string.
    /// let name = ExternalName::testcase("hello");
    /// assert_eq!(name.display(None).to_string(), "%hello");
    /// ```
    pub fn testcase<T: AsRef<[u8]>>(v: T) -> Self {
        Self::TestCase(TestcaseName::new(v))
    }

    /// Create a new external name from a user-defined external function reference.
    ///
    /// # Examples
    /// ```rust
    /// # use cranelift_codegen::ir::{ExternalName, UserExternalNameRef};
    /// let user_func_ref: UserExternalNameRef = Default::default(); // usually obtained with `Function::declare_imported_user_function()`
    /// let name = ExternalName::user(user_func_ref);
    /// assert_eq!(name.display(None).to_string(), "userextname0");
    /// ```
    pub fn user(func_ref: UserExternalNameRef) -> Self {
        Self::User(func_ref)
    }

    /// Returns a display for the current `ExternalName`, with extra context to prettify the
    /// output.
    pub fn display<'a>(
        &'a self,
        params: Option<&'a FunctionParameters>,
    ) -> DisplayableExternalName<'a> {
        DisplayableExternalName { name: self, params }
    }
}

/// An `ExternalName` that has enough context to be displayed.
pub struct DisplayableExternalName<'a> {
    name: &'a ExternalName,
    params: Option<&'a FunctionParameters>,
}

impl<'a> fmt::Display for DisplayableExternalName<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.name {
            ExternalName::User(func_ref) => {
                if let Some(params) = self.params {
                    let name = &params.user_named_funcs()[*func_ref];
                    write!(f, "u{}:{}", name.namespace, name.index)
                } else {
                    // Best effort.
                    write!(f, "{}", *func_ref)
                }
            }
            ExternalName::TestCase(testcase) => testcase.fmt(f),
            ExternalName::LibCall(lc) => write!(f, "%{lc}"),
            ExternalName::KnownSymbol(ks) => write!(f, "%{ks}"),
        }
    }
}

impl FromStr for ExternalName {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // Try to parse as a known symbol
        if let Ok(ks) = s.parse() {
            return Ok(Self::KnownSymbol(ks));
        }

        // Try to parse as a libcall name
        if let Ok(lc) = s.parse() {
            return Ok(Self::LibCall(lc));
        }

        // Otherwise its a test case name
        Ok(Self::testcase(s.as_bytes()))
    }
}

#[cfg(test)]
mod tests {
    use super::ExternalName;
    use crate::ir::{
        entities::UserExternalNameRef, function::FunctionParameters, LibCall, UserExternalName,
    };
    use alloc::string::ToString;
    use core::u32;
    use cranelift_entity::EntityRef as _;

    #[cfg(target_pointer_width = "64")]
    #[test]
    fn externalname_size() {
        assert_eq!(core::mem::size_of::<ExternalName>(), 24);
    }

    #[test]
    fn display_testcase() {
        assert_eq!(ExternalName::testcase("").display(None).to_string(), "%");
        assert_eq!(ExternalName::testcase("x").display(None).to_string(), "%x");
        assert_eq!(
            ExternalName::testcase("x_1").display(None).to_string(),
            "%x_1"
        );
        assert_eq!(
            ExternalName::testcase("longname12345678")
                .display(None)
                .to_string(),
            "%longname12345678"
        );
        assert_eq!(
            ExternalName::testcase("longname123456789")
                .display(None)
                .to_string(),
            "%longname123456789"
        );
    }

    #[test]
    fn display_user() {
        assert_eq!(
            ExternalName::user(UserExternalNameRef::new(0))
                .display(None)
                .to_string(),
            "userextname0"
        );
        assert_eq!(
            ExternalName::user(UserExternalNameRef::new(1))
                .display(None)
                .to_string(),
            "userextname1"
        );
        assert_eq!(
            ExternalName::user(UserExternalNameRef::new((u32::MAX - 1) as _))
                .display(None)
                .to_string(),
            "userextname4294967294"
        );

        let mut func_params = FunctionParameters::new();

        // ref 0
        func_params.ensure_user_func_name(UserExternalName {
            namespace: 13,
            index: 37,
        });

        // ref 1
        func_params.ensure_user_func_name(UserExternalName {
            namespace: 2,
            index: 4,
        });

        assert_eq!(
            ExternalName::user(UserExternalNameRef::new(0))
                .display(Some(&func_params))
                .to_string(),
            "u13:37"
        );

        assert_eq!(
            ExternalName::user(UserExternalNameRef::new(1))
                .display(Some(&func_params))
                .to_string(),
            "u2:4"
        );
    }

    #[test]
    fn parsing() {
        assert_eq!(
            "FloorF32".parse(),
            Ok(ExternalName::LibCall(LibCall::FloorF32))
        );
        assert_eq!(
            ExternalName::LibCall(LibCall::FloorF32)
                .display(None)
                .to_string(),
            "%FloorF32"
        );
    }
}
