//! 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 api_list_common::{ALL_OPERATIONS, Group, MathOpInfo, Ty};

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

/// 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 Identifier {
    /// Return information about this operation.
    pub fn math_op(self) -> &'static MathOpInfo {
        ALL_OPERATIONS
            .iter()
            .find(|op| op.name == self.as_str())
            .unwrap()
    }
}

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 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
        + Tuple
        + 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;
}

/* Most of these are workarounds for <https://github.com/rust-lang/rust/issues/38078> */

/// Access the associated `CFn` type from an op.
pub type OpCFn<Op> = <Op as MathOp>::CFn;
/// Access the associated `CRet` type from an op.
pub type OpCRet<Op> = <Op as MathOp>::CRet;
/// Access the associated `RustFn` type from an op.
pub type OpRustFn<Op> = <Op as MathOp>::RustFn;
/// Access the associated `RustArgs` type from an op.
pub type OpRustArgs<Op> = <Op as MathOp>::RustArgs;
/// Access the associated `RustRet` type from an op.
pub type OpRustRet<Op> = <Op as MathOp>::RustRet;

/// Get the type of the first Rust argument.
pub type Arg0<Op> = <OpRustArgs<Op> as Tuple>::T0;
/// Get the type of the second Rust argument.
pub type Arg1<Op> = <OpRustArgs<Op> as Tuple>::T1;
/// Get the type of the third Rust argument.
pub type Arg2<Op> = <OpRustArgs<Op> as Tuple>::T2;

/// If the Rust return type is a tuple, get the first type.
pub type Ret0<Op> = <OpRustRet<Op> as Tuple>::T0;
/// If the Rust return type is a tuple, get the second type.
pub type Ret1<Op> = <OpRustRet<Op> as Tuple>::T1;

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

                impl MathOp for Routine {
                    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 = $path;
                }
            }

        }
    };
}

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