use crate::{
    core::{
        circuits::boolean::utils::shift_right,
        expressions::{circuit::ArithmeticCircuitId, field_expr::FieldExpr},
        global_value::{field_array::FieldArray, value::FieldValue},
    },
    traits::*,
    utils::{field::BaseField, number::Number, used_field::UsedField},
};
use ff::Field;
use std::ops::{Add, Div, Mul, Sub};

pub type ArcisField = BaseField;
pub type ArcisValue = FieldValue<ArcisField>;
pub type ArcisArray<const N: usize> = FieldArray<N, ArcisField>;

/// A struct for fixed-points "floats".
#[derive(Debug, Clone, Copy)]
pub struct ArcisFloatValue(pub ArcisValue);

impl ArcisFloatValue {
    pub const MINUS_PLSB: usize = DOUBLE_PRECISION_MANTISSA;
    /// The maximum bounds.
    pub fn max_bounds() -> (ArcisField, ArcisField) {
        (
            -ArcisField::power_of_two(127),
            ArcisField::power_of_two(127) - ArcisField::ONE,
        )
    }
    pub fn number_to_f64(a: Number) -> f64 {
        f64::from(a) / 2.0f64.powi(52)
    }
    pub fn number_with_precision_to_f64(a: Number, precision: usize) -> f64 {
        f64::from(a) / 2.0f64.powi(precision as i32)
    }
    pub fn i128_to_f64(a: i128) -> f64 {
        Self::number_to_f64(a.into())
    }
    pub fn i128_to_f32(a: i128) -> f32 {
        Self::i128_to_f64(a) as f32
    }
    pub fn f64_to_i128(a: f64) -> i128 {
        let value = a.clamp(-(2f64.powi(75)), 2f64.powi(75));
        (value * 2f64.powi(DOUBLE_PRECISION_MANTISSA as i32)) as i128
    }
    pub fn f32_to_i128(a: f32) -> i128 {
        let value = a.clamp(-(2f32.powi(75)), 2f32.powi(75));
        (value * 2f32.powi(DOUBLE_PRECISION_MANTISSA as i32)) as i128
    }
    pub fn f64_to_number(a: f64) -> Number {
        let value = a.clamp(-(2f64.powi(75)), 2f64.powi(75));
        Number::from(value * 2f64.powi(DOUBLE_PRECISION_MANTISSA as i32))
    }
    pub fn f32_to_number(a: f32) -> Number {
        let value = a.clamp(-(2f32.powi(75)), 2f32.powi(75));
        Number::from(value * 2f32.powi(DOUBLE_PRECISION_MANTISSA as i32))
    }
}

impl From<ArcisValue> for ArcisFloatValue {
    fn from(value: ArcisValue) -> Self {
        Self(value)
    }
}

impl From<ArcisFloatValue> for ArcisValue {
    fn from(a: ArcisFloatValue) -> ArcisValue {
        a.0
    }
}

impl TryFrom<f64> for ArcisFloatValue {
    type Error = &'static str;

    fn try_from(value: f64) -> Result<Self, &'static str> {
        if value < -(2f64.powi(75)) || value >= 2f64.powi(75) {
            Err("Arcis only supports inputs in the range [-2**75, 2**75)")
        } else {
            Ok(ArcisFloatValue(FieldValue::new(FieldExpr::Val(
                ArcisFloatValue::f64_to_number(value).into(),
            ))))
        }
    }
}

macro_rules! impl_float_comparison_op {
    ($op:ident, $opfn:ident) => {
        impl $op<Self> for ArcisFloatValue {
            type Output = ArcisValue;

            fn $opfn(self, other: Self) -> Self::Output {
                ArcisValue::from(self.0.$opfn(other.0))
            }
        }

        impl $op<f64> for ArcisFloatValue {
            type Output = ArcisValue;

            fn $opfn(self, other: f64) -> Self::Output {
                ArcisValue::from(self.0.$opfn(ArcisFloatValue::try_from(other).unwrap().0))
            }
        }
    };
}

impl_float_comparison_op!(Equal, eq);
impl_float_comparison_op!(GreaterThan, gt);
impl_float_comparison_op!(GreaterEqual, ge);

macro_rules! impl_float_arithmetic_op {
    ($op:ident, $opfn:ident) => {
        impl $op<ArcisFloatValue> for ArcisFloatValue {
            type Output = Self;

            fn $opfn(self, other: Self) -> Self {
                Self(self.0.$opfn(other.0))
            }
        }

        impl $op<f64> for ArcisFloatValue {
            type Output = ArcisFloatValue;

            fn $opfn(self, other: f64) -> Self::Output {
                Self(self.0.$opfn(ArcisFloatValue::try_from(other).unwrap().0))
            }
        }
    };
}

impl_float_arithmetic_op!(Add, add);
impl_float_arithmetic_op!(Sub, sub);

pub(crate) const DOUBLE_PRECISION_MANTISSA: usize = 52;

impl Mul for ArcisFloatValue {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        let prod = self.0 * rhs.0;
        Self(shift_right(prod, DOUBLE_PRECISION_MANTISSA, true))
    }
}
impl Div for ArcisFloatValue {
    type Output = Self;

    fn div(self, rhs: ArcisFloatValue) -> Self::Output {
        Self(ArithmeticCircuitId::Div.apply(vec![self.0, rhs.0])[0])
    }
}
