//! Types representing individual functions.
//!
//! Each routine gets a module with its name, e.g. `mod sinf { /* ... */ }`. The module
//! contains a unit struct `Routine` which implements `MathOp`.
//!
//! Basically everything could be called a "function" here, so we loosely use the following
//! terminology:
//!
//! - "Function": the math operation that does not have an associated precision. E.g. `f(x) = e^x`,
//!   `f(x) = log(x)`.
//! - "Routine": A code implementation of a math operation with a specific precision. E.g. `exp`,
//!   `expf`, `expl`, `log`, `logf`.
//! - "Operation" / "Op": Something that relates a routine to a function or is otherwise higher
//!   level. `Op` is also used as the name for generic parameters since it is terse.

use std::fmt;
use std::panic::{RefUnwindSafe, UnwindSafe};

pub use shared::{ALL_OPERATIONS, FloatTy, MathOpInfo, Ty};

use crate::{CheckOutput, Float, TupleCall};

mod shared {
    include!("../../crates/libm-macros/src/shared.rs");
}

/// An enum representing each possible symbol name (`sin`, `sinf`, `sinl`, etc).
#[libm_macros::function_enum(BaseName)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Identifier {}

impl fmt::Display for Identifier {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// The name without any type specifier, e.g. `sin` and `sinf` both become `sin`.
#[libm_macros::base_name_enum]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BaseName {}

impl fmt::Display for BaseName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// Attributes ascribed to a `libm` routine including signature, type information,
/// and naming.
pub trait MathOp {
    /// The float type used for this operation.
    type FTy: Float;

    /// The function type representing the signature in a C library.
    type CFn: Copy;

    /// Arguments passed to the C library function as a tuple. These may include `&mut` return
    /// values.
    type CArgs<'a>
    where
        Self: 'a;

    /// The type returned by C implementations.
    type CRet;

    /// The signature of the Rust function as a `fn(...) -> ...` type.
    type RustFn: Copy + UnwindSafe;

    /// Arguments passed to the Rust library function as a tuple.
    ///
    /// The required `TupleCall` bounds ensure this type can be passed either to the C function or
    /// to the Rust function.
    type RustArgs: Copy
        + TupleCall<Self::RustFn, Output = Self::RustRet>
        + TupleCall<Self::CFn, Output = Self::RustRet>
        + RefUnwindSafe;

    /// Type returned from the Rust function.
    type RustRet: CheckOutput<Self::RustArgs>;

    /// The name of this function, including suffix (e.g. `sin`, `sinf`).
    const IDENTIFIER: Identifier;

    /// The name as a string.
    const NAME: &'static str = Self::IDENTIFIER.as_str();

    /// The name of the function excluding the type suffix, e.g. `sin` and `sinf` are both `sin`.
    const BASE_NAME: BaseName = Self::IDENTIFIER.base_name();

    /// The function in `libm` which can be called.
    const ROUTINE: Self::RustFn;

    /// Whether or not the function is part of libm public API.
    const PUBLIC: bool;
}

/// Access the associated `FTy` type from an op (helper to avoid ambiguous associated types).
pub type OpFTy<Op> = <Op as MathOp>::FTy;
/// Access the associated `FTy::Int` type from an op (helper to avoid ambiguous associated types).
pub type OpITy<Op> = <<Op as MathOp>::FTy as Float>::Int;
/// Access the associated `CFn` type from an op (helper to avoid ambiguous associated types).
pub type OpCFn<Op> = <Op as MathOp>::CFn;
/// Access the associated `CRet` type from an op (helper to avoid ambiguous associated types).
pub type OpCRet<Op> = <Op as MathOp>::CRet;
/// Access the associated `RustFn` type from an op (helper to avoid ambiguous associated types).
pub type OpRustFn<Op> = <Op as MathOp>::RustFn;
/// Access the associated `RustArgs` type from an op (helper to avoid ambiguous associated types).
pub type OpRustArgs<Op> = <Op as MathOp>::RustArgs;
/// Access the associated `RustRet` type from an op (helper to avoid ambiguous associated types).
pub type OpRustRet<Op> = <Op as MathOp>::RustRet;

macro_rules! create_op_modules {
    // Matcher for unary functions
    (
        fn_name: $fn_name:ident,
        FTy: $FTy:ty,
        CFn: $CFn:ty,
        CArgs: $CArgs:ty,
        CRet: $CRet:ty,
        RustFn: $RustFn:ty,
        RustArgs: $RustArgs:ty,
        RustRet: $RustRet:ty,
        public: $public:expr,
        attrs: [$($attr:meta),*],
    ) => {
        paste::paste! {
            $(#[$attr])*
            pub mod $fn_name {
                use super::*;
                pub struct Routine;

                impl MathOp for Routine {
                    type FTy = $FTy;
                    type CFn = for<'a> $CFn;
                    type CArgs<'a> = $CArgs where Self: 'a;
                    type CRet = $CRet;
                    type RustFn = $RustFn;
                    type RustArgs = $RustArgs;
                    type RustRet = $RustRet;

                    const IDENTIFIER: Identifier = Identifier::[< $fn_name:camel >];
                    const ROUTINE: Self::RustFn = libm::$fn_name;
                    const PUBLIC: bool = $public;
                }
            }

        }
    };
}

libm_macros::for_each_function! {
    callback: create_op_modules,
    emit_types: all,
}
