//! Configuration for skipping or changing the result for individual test cases (inputs) rather
//! than ignoring entire tests.

use CheckBasis::{Mpfr, Musl};
use libm::support::CastFrom;
use {BaseName as Bn, Identifier as Id};

use crate::{BaseName, CheckBasis, CheckCtx, Float, Identifier, Int, TestResult};

/// Type implementing [`IgnoreCase`].
pub struct SpecialCase;

/// ULP allowed to differ from the results returned by a test basis.
#[allow(clippy::single_match)]
pub fn default_ulp(ctx: &CheckCtx) -> u32 {
    // ULP compared to the infinite (MPFR) result.
    let mut ulp = match ctx.base_name {
        // Operations that require exact results. This list should correlate with what we
        // have documented at <https://doc.rust-lang.org/std/primitive.f32.html>.
        Bn::Ceil
        | Bn::Copysign
        | Bn::Fabs
        | Bn::Fdim
        | Bn::Floor
        | Bn::Fma
        | Bn::Fmax
        | Bn::Fmaximum
        | Bn::FmaximumNum
        | Bn::Fmin
        | Bn::Fminimum
        | Bn::FminimumNum
        | Bn::Fmod
        | Bn::Frexp
        | Bn::Ilogb
        | Bn::Ldexp
        | Bn::Modf
        | Bn::Nextafter
        | Bn::Remainder
        | Bn::Remquo
        | Bn::Rint
        | Bn::Round
        | Bn::Roundeven
        | Bn::Scalbn
        | Bn::Sqrt
        | Bn::Trunc => 0,

        // Operations that aren't required to be exact, but our implementations are.
        Bn::Cbrt => 0,

        // Bessel functions have large inaccuracies.
        Bn::J0 | Bn::J1 | Bn::Y0 | Bn::Y1 | Bn::Jn | Bn::Yn => 8_000_000,

        // For all other operations, specify our implementation's worst case precision.
        Bn::Acos => 1,
        Bn::Acosh => 4,
        Bn::Asin => 1,
        Bn::Asinh => 2,
        Bn::Atan => 1,
        Bn::Atan2 => 2,
        Bn::Atanh => 2,
        Bn::Cos => 1,
        Bn::Cosh => 1,
        Bn::Erf => 1,
        Bn::Erfc => 4,
        Bn::Exp => 1,
        Bn::Exp10 => 6,
        Bn::Exp2 => 1,
        Bn::Expm1 => 1,
        Bn::Hypot => 1,
        Bn::Lgamma | Bn::LgammaR => 16,
        Bn::Log => 1,
        Bn::Log10 => 1,
        Bn::Log1p => 1,
        Bn::Log2 => 1,
        Bn::Pow => 1,
        Bn::Sin => 1,
        Bn::Sincos => 1,
        Bn::Sinh => 2,
        Bn::Tan => 1,
        Bn::Tanh => 2,
        // tgammaf has higher accuracy than tgamma.
        Bn::Tgamma if ctx.fn_ident != Id::Tgamma => 1,
        Bn::Tgamma => 20,
    };

    // There are some cases where musl's approximation is less accurate than ours. For these
    // cases, increase the ULP.
    if ctx.basis == Musl {
        match ctx.base_name {
            Bn::Cosh => ulp = 2,
            Bn::Exp10 if usize::BITS < 64 => ulp = 4,
            Bn::Lgamma | Bn::LgammaR => ulp = 400,
            Bn::Tanh => ulp = 4,
            _ => (),
        }

        match ctx.fn_ident {
            Id::Cbrt => ulp = 2,
            // FIXME(#401): musl has an incorrect result here.
            Id::Fdim => ulp = 2,
            Id::Sincosf => ulp = 500,
            Id::Tgamma => ulp = 20,
            _ => (),
        }
    }

    if cfg!(target_arch = "x86") {
        match ctx.fn_ident {
            // Input `fma(0.999999999999999, 1.0000000000000013, 0.0) = 1.0000000000000002` is
            // incorrect on i586 and i686.
            Id::Fma => ulp = 1,
            _ => (),
        }
    }

    // In some cases, our implementation is less accurate than musl on i586.
    if cfg!(x86_no_sse) {
        match ctx.fn_ident {
            // FIXME(#401): these need to be correctly rounded but are not.
            Id::Fmaf => ulp = 1,
            Id::Fdim => ulp = 1,
            Id::Round => ulp = 1,

            Id::Asinh => ulp = 3,
            Id::Asinhf => ulp = 3,
            Id::Cbrt => ulp = 1,
            Id::Exp10 | Id::Exp10f => ulp = 1_000_000,
            Id::Exp2 | Id::Exp2f => ulp = 10_000_000,
            Id::Log1p | Id::Log1pf => ulp = 2,
            Id::Tan => ulp = 2,
            _ => (),
        }
    }

    ulp
}

/// Result of checking for possible overrides.
#[derive(Debug, Default)]
pub enum CheckAction {
    /// The check should pass. Default case.
    #[default]
    AssertSuccess,

    /// Override the ULP for this check.
    AssertWithUlp(u32),

    /// Failure is expected, ensure this is the case (xfail). Takes a contxt string to help trace
    /// back exactly why we expect this to fail.
    AssertFailure(&'static str),

    /// The override somehow validated the result, here it is.
    Custom(TestResult),

    /// Disregard the output.
    Skip,
}

/// Don't run further validation on this test case.
const SKIP: CheckAction = CheckAction::Skip;

/// Return this to skip checks on a test that currently fails but shouldn't. Takes a description
/// of context.
const XFAIL: fn(&'static str) -> CheckAction = CheckAction::AssertFailure;

/// Indicates that we expect a test to fail but we aren't asserting that it does (e.g. some results
/// within a range do actually pass).
///
/// Same as `SKIP`, just indicates we have something to eventually fix.
const XFAIL_NOCHECK: CheckAction = CheckAction::Skip;

/// By default, all tests should pass.
const DEFAULT: CheckAction = CheckAction::AssertSuccess;

/// Allow overriding the outputs of specific test cases.
///
/// There are some cases where we want to xfail specific cases or handle certain inputs
/// differently than the rest of calls to `validate`. This provides a hook to do that.
///
/// If `None` is returned, checks will proceed as usual. If `Some(result)` is returned, checks
/// are skipped and the provided result is returned instead.
///
/// This gets implemented once per input type, then the functions provide further filtering
/// based on function name and values.
///
/// `ulp` can also be set to adjust the ULP for that specific test, even if `None` is still
/// returned.
pub trait MaybeOverride<Input> {
    fn check_float<F: Float>(
        _input: Input,
        _actual: F,
        _expected: F,
        _ctx: &CheckCtx,
    ) -> CheckAction {
        DEFAULT
    }

    fn check_int<I: Int>(_input: Input, _actual: I, _expected: I, _ctx: &CheckCtx) -> CheckAction {
        DEFAULT
    }
}

#[cfg(f16_enabled)]
impl MaybeOverride<(f16,)> for SpecialCase {}

impl MaybeOverride<(f32,)> for SpecialCase {
    fn check_float<F: Float>(input: (f32,), actual: F, expected: F, ctx: &CheckCtx) -> CheckAction {
        if ctx.base_name == BaseName::Expm1
            && !input.0.is_infinite()
            && input.0 > 80.0
            && actual.is_infinite()
            && !expected.is_infinite()
        {
            // we return infinity but the number is representable
            if ctx.basis == CheckBasis::Musl {
                return XFAIL_NOCHECK;
            }
            return XFAIL("expm1 representable numbers");
        }

        if cfg!(x86_no_sse)
            && ctx.base_name == BaseName::Exp2
            && !expected.is_infinite()
            && actual.is_infinite()
        {
            // We return infinity when there is a representable value. Test input: 127.97238
            return XFAIL("586 exp2 representable numbers");
        }

        if ctx.base_name == BaseName::Sinh && input.0.abs() > 80.0 && actual.is_nan() {
            // we return some NaN that should be real values or infinite
            if ctx.basis == CheckBasis::Musl {
                return XFAIL_NOCHECK;
            }
            return XFAIL("sinh unexpected NaN");
        }

        if (ctx.base_name == BaseName::Lgamma || ctx.base_name == BaseName::LgammaR)
            && input.0 > 4e36
            && expected.is_infinite()
            && !actual.is_infinite()
        {
            // This result should saturate but we return a finite value.
            return XFAIL_NOCHECK;
        }

        if ctx.base_name == BaseName::J0 && input.0 < -1e34 {
            // Errors get huge close to -inf
            return XFAIL_NOCHECK;
        }

        unop_common(input, actual, expected, ctx)
    }

    fn check_int<I: Int>(input: (f32,), actual: I, expected: I, ctx: &CheckCtx) -> CheckAction {
        // On MPFR for lgammaf_r, we set -1 as the integer result for negative infinity but MPFR
        // sets +1
        if ctx.basis == CheckBasis::Mpfr
            && ctx.base_name == BaseName::LgammaR
            && input.0 == f32::NEG_INFINITY
            && actual.abs() == expected.abs()
        {
            return XFAIL("lgammar integer result");
        }

        DEFAULT
    }
}

impl MaybeOverride<(f64,)> for SpecialCase {
    fn check_float<F: Float>(input: (f64,), actual: F, expected: F, ctx: &CheckCtx) -> CheckAction {
        if cfg!(x86_no_sse)
            && (ctx.base_name == BaseName::Rint || ctx.base_name == BaseName::Roundeven)
            && (expected - actual).abs() <= F::ONE
            && (expected - actual).abs() > F::ZERO
        {
            // Our rounding mode is incorrect.
            return XFAIL("i586 rint rounding mode");
        }

        if cfg!(x86_no_sse)
            && (ctx.fn_ident == Identifier::Exp10 || ctx.fn_ident == Identifier::Exp2)
        {
            // FIXME: i586 has very imprecise results with ULP > u32::MAX for these
            // operations so we can't reasonably provide a limit.
            return XFAIL_NOCHECK;
        }

        if ctx.base_name == BaseName::J0 && input.0 < -1e300 {
            // Errors get huge close to -inf
            return XFAIL_NOCHECK;
        }

        // maybe_check_nan_bits(actual, expected, ctx)
        unop_common(input, actual, expected, ctx)
    }

    fn check_int<I: Int>(input: (f64,), actual: I, expected: I, ctx: &CheckCtx) -> CheckAction {
        // On MPFR for lgamma_r, we set -1 as the integer result for negative infinity but MPFR
        // sets +1
        if ctx.basis == CheckBasis::Mpfr
            && ctx.base_name == BaseName::LgammaR
            && input.0 == f64::NEG_INFINITY
            && actual.abs() == expected.abs()
        {
            return XFAIL("lgammar integer result");
        }

        DEFAULT
    }
}

#[cfg(f128_enabled)]
impl MaybeOverride<(f128,)> for SpecialCase {}

// F1 and F2 are always the same type, this is just to please generics
fn unop_common<F1: Float, F2: Float>(
    input: (F1,),
    actual: F2,
    expected: F2,
    ctx: &CheckCtx,
) -> CheckAction {
    if ctx.base_name == BaseName::Acosh
        && input.0 < F1::NEG_ONE
        && !(expected.is_nan() && actual.is_nan())
    {
        // acoshf is undefined for x <= 1.0, but we return a random result at lower values.

        if ctx.basis == CheckBasis::Musl {
            return XFAIL_NOCHECK;
        }

        return XFAIL("acoshf undefined");
    }

    if (ctx.base_name == BaseName::Lgamma || ctx.base_name == BaseName::LgammaR)
        && input.0 < F1::ZERO
        && !input.0.is_infinite()
    {
        // loggamma should not be defined for x < 0, yet we both return results
        return XFAIL_NOCHECK;
    }

    // fabs and copysign must leave NaNs untouched.
    if ctx.base_name == BaseName::Fabs && input.0.is_nan() {
        // LLVM currently uses x87 instructions which quieten signalling NaNs to handle the i686
        // `extern "C"` `f32`/`f64` return ABI.
        // LLVM issue <https://github.com/llvm/llvm-project/issues/66803>
        // Rust issue <https://github.com/rust-lang/rust/issues/115567>
        if cfg!(target_arch = "x86") && ctx.basis == CheckBasis::Musl && actual.is_nan() {
            return XFAIL_NOCHECK;
        }

        // MPFR only has one NaN bitpattern; allow the default `.is_nan()` checks to validate.
        if ctx.basis == CheckBasis::Mpfr {
            return DEFAULT;
        }

        // abs and copysign require signaling NaNs to be propagated, so verify bit equality.
        if actual.biteq(expected) {
            return CheckAction::Custom(Ok(()));
        } else {
            return CheckAction::Custom(Err(anyhow::anyhow!("NaNs have different bitpatterns")));
        }
    }

    DEFAULT
}

#[cfg(f16_enabled)]
impl MaybeOverride<(f16, f16)> for SpecialCase {
    fn check_float<F: Float>(
        input: (f16, f16),
        actual: F,
        expected: F,
        ctx: &CheckCtx,
    ) -> CheckAction {
        binop_common(input, actual, expected, ctx)
    }
}

impl MaybeOverride<(f32, f32)> for SpecialCase {
    fn check_float<F: Float>(
        input: (f32, f32),
        actual: F,
        expected: F,
        ctx: &CheckCtx,
    ) -> CheckAction {
        binop_common(input, actual, expected, ctx)
    }
}

impl MaybeOverride<(f64, f64)> for SpecialCase {
    fn check_float<F: Float>(
        input: (f64, f64),
        actual: F,
        expected: F,
        ctx: &CheckCtx,
    ) -> CheckAction {
        binop_common(input, actual, expected, ctx)
    }
}

#[cfg(f128_enabled)]
impl MaybeOverride<(f128, f128)> for SpecialCase {
    fn check_float<F: Float>(
        input: (f128, f128),
        actual: F,
        expected: F,
        ctx: &CheckCtx,
    ) -> CheckAction {
        binop_common(input, actual, expected, ctx)
    }
}

// F1 and F2 are always the same type, this is just to please generics
fn binop_common<F1: Float, F2: Float>(
    input: (F1, F1),
    actual: F2,
    expected: F2,
    ctx: &CheckCtx,
) -> CheckAction {
    // MPFR only has one NaN bitpattern; skip tests in cases where the first argument would take
    // the sign of a NaN second argument. The default NaN checks cover other cases.
    if ctx.base_name == BaseName::Copysign && ctx.basis == CheckBasis::Mpfr && input.1.is_nan() {
        return SKIP;
    }

    // FIXME(#939): this should not be skipped, there is a bug in our implementationi.
    if ctx.base_name == BaseName::FmaximumNum
        && ctx.basis == CheckBasis::Mpfr
        && ((input.0.is_nan() && actual.is_nan() && expected.is_nan()) || input.1.is_nan())
    {
        return XFAIL_NOCHECK;
    }

    /* FIXME(#439): our fmin and fmax do not compare signed zeros */

    if ctx.base_name == BaseName::Fmin
        && input.0.biteq(F1::NEG_ZERO)
        && input.1.biteq(F1::ZERO)
        && expected.biteq(F2::NEG_ZERO)
        && actual.biteq(F2::ZERO)
    {
        return XFAIL("fmin signed zeroes");
    }

    if ctx.base_name == BaseName::Fmax
        && input.0.biteq(F1::NEG_ZERO)
        && input.1.biteq(F1::ZERO)
        && expected.biteq(F2::ZERO)
        && actual.biteq(F2::NEG_ZERO)
    {
        return XFAIL("fmax signed zeroes");
    }

    // Musl propagates NaNs if one is provided as the input, but we return the other input.
    if (ctx.base_name == BaseName::Fmax || ctx.base_name == BaseName::Fmin)
        && ctx.basis == Musl
        && (input.0.is_nan() ^ input.1.is_nan())
        && expected.is_nan()
    {
        return XFAIL("fmax/fmin musl NaN");
    }

    DEFAULT
}

impl MaybeOverride<(i32, f32)> for SpecialCase {
    fn check_float<F: Float>(
        input: (i32, f32),
        actual: F,
        expected: F,
        ctx: &CheckCtx,
    ) -> CheckAction {
        // `ynf(213, 109.15641) = -inf` with our library, should be finite.
        if ctx.basis == Mpfr
            && ctx.base_name == BaseName::Yn
            && input.0 > 200
            && !expected.is_infinite()
            && actual.is_infinite()
        {
            return XFAIL("ynf infinity mismatch");
        }

        int_float_common(input, actual, expected, ctx)
    }
}

impl MaybeOverride<(i32, f64)> for SpecialCase {
    fn check_float<F: Float>(
        input: (i32, f64),
        actual: F,
        expected: F,
        ctx: &CheckCtx,
    ) -> CheckAction {
        int_float_common(input, actual, expected, ctx)
    }
}

fn int_float_common<F1: Float, F2: Float>(
    input: (i32, F1),
    actual: F2,
    expected: F2,
    ctx: &CheckCtx,
) -> CheckAction {
    if ctx.basis == Mpfr
        && (ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn)
        && input.1 == F1::NEG_INFINITY
        && actual == F2::ZERO
        && expected == F2::ZERO
    {
        return XFAIL("we disagree with MPFR on the sign of zero");
    }

    // Values near infinity sometimes get cut off for us. `ynf(681, 509.90924) = -inf` but should
    // be -3.2161271e38.
    if ctx.basis == Musl
        && ctx.fn_ident == Identifier::Ynf
        && !expected.is_infinite()
        && actual.is_infinite()
        && (expected.abs().to_bits().abs_diff(actual.abs().to_bits())
            < F2::Int::cast_from(10_000_000u32))
    {
        return XFAIL_NOCHECK;
    }

    // Our bessel functions blow up with large N values
    if ctx.basis == Musl && (ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn) {
        if cfg!(x86_no_sse) {
            // Precision is especially bad on i586, not worth checking.
            return XFAIL_NOCHECK;
        }

        if input.0 > 4000 {
            return XFAIL_NOCHECK;
        } else if input.0 > 100 {
            return CheckAction::AssertWithUlp(1_000_000);
        }
    }
    DEFAULT
}

#[cfg(f16_enabled)]
impl MaybeOverride<(f16, i32)> for SpecialCase {}
impl MaybeOverride<(f32, i32)> for SpecialCase {}
impl MaybeOverride<(f64, i32)> for SpecialCase {}
#[cfg(f128_enabled)]
impl MaybeOverride<(f128, i32)> for SpecialCase {}

impl MaybeOverride<(f32, f32, f32)> for SpecialCase {}
impl MaybeOverride<(f64, f64, f64)> for SpecialCase {}
#[cfg(f128_enabled)]
impl MaybeOverride<(f128, f128, f128)> for SpecialCase {}
