use crate::{
    core::{
        bounds::{Bounds, FieldBounds},
        expressions::{
            conversion_expr::ConversionExpr,
            domain::Domain,
            expr::{EvalValue, Expr},
            field_expr::FieldExpr,
            InputKind,
        },
    },
    traits::{
        FromF25519,
        FromLeBits,
        FromLeBytes,
        GetBit,
        MxeRescueKey,
        MxeX25519PrivateKey,
        Random,
        Reveal,
        WithBooleanBounds,
    },
    utils::{
        crypto::key::{
            MXE_RESCUE_BASE_FIELD_KEY,
            MXE_RESCUE_SCALAR_FIELD_KEY,
            MXE_X25519_PRIVATE_KEY,
            RESCUE_KEY_COUNT,
        },
        field::{BaseField, ScalarField},
        used_field::UsedField,
    },
};
use ff::PrimeField;

type Curve = primitives::algebra::elliptic_curve::Curve25519Ristretto;
type Gate = core_utils::circuit::Gate<Curve>;

/// A trait for actually used fields: fields which have Exprs.
pub trait ActuallyUsedField: UsedField {
    fn bounds_to_field_bounds(b: Bounds) -> FieldBounds<Self>;
    fn field_bounds_to_bounds(field: FieldBounds<Self>) -> Bounds;
    fn field_expr_to_expr<T: Clone>(expr: FieldExpr<Self, T>) -> Expr<T>;
    fn conversion_expr_to_expr<T: Clone>(expr: ConversionExpr<Self, T>) -> Expr<T>;
    fn expr_to_field_expr<T: Clone>(expr: Expr<T>) -> Option<FieldExpr<Self, T>>;
    fn expr_to_conversion_expr<T: Clone>(expr: Expr<T>) -> Option<ConversionExpr<Self, T>>;
    fn eval_value_to_field(eval_value: EvalValue) -> Self;
    fn field_to_eval_value(a: Self) -> EvalValue;
    fn field_to_constant_gate(a: Self) -> Gate;
    fn field_type() -> core_utils::circuit::FieldType;
    fn algebraic_type() -> core_utils::circuit::AlgebraicType;
    fn input(kind: InputKind) -> core_utils::circuit::Input<Curve>;
}

impl ActuallyUsedField for ScalarField {
    fn bounds_to_field_bounds(b: Bounds) -> FieldBounds<Self> {
        if let Bounds::Scalar(s) = b {
            s
        } else {
            panic!("invalid bounds");
        }
    }
    fn field_bounds_to_bounds(b: FieldBounds<Self>) -> Bounds {
        Bounds::Scalar(b)
    }
    fn field_expr_to_expr<T: Clone>(expr: FieldExpr<Self, T>) -> Expr<T> {
        Expr::Scalar(expr)
    }
    fn conversion_expr_to_expr<T: Clone>(expr: ConversionExpr<Self, T>) -> Expr<T> {
        Expr::ScalarConversion(expr)
    }

    fn expr_to_field_expr<T: Clone>(expr: Expr<T>) -> Option<FieldExpr<Self, T>> {
        if let Expr::Scalar(e) = expr {
            Some(e)
        } else {
            None
        }
    }

    fn expr_to_conversion_expr<T: Clone>(expr: Expr<T>) -> Option<ConversionExpr<Self, T>> {
        if let Expr::ScalarConversion(e) = expr {
            Some(e)
        } else {
            None
        }
    }
    fn eval_value_to_field(eval_value: EvalValue) -> Self {
        let EvalValue::Scalar(x) = eval_value else {
            panic!("wrong inner type for eval_value")
        };
        x
    }
    fn field_to_eval_value(a: Self) -> EvalValue {
        EvalValue::Scalar(a)
    }

    fn field_to_constant_gate(a: Self) -> Gate {
        Gate::Input {
            input_type: core_utils::circuit::Input::Scalar(core_utils::circuit::ScalarPlaintext::<
                Curve,
            >::Fixed(
                primitives::algebra::elliptic_curve::Scalar::<Curve>::from_le_bytes(
                    &a.to_le_bytes(),
                )
                .expect("failed to convert"),
            )),
        }
    }
    fn field_type() -> core_utils::circuit::FieldType {
        core_utils::circuit::FieldType::ScalarField
    }

    fn algebraic_type() -> core_utils::circuit::AlgebraicType {
        core_utils::circuit::AlgebraicType::ScalarField
    }
    fn input(kind: InputKind) -> core_utils::circuit::Input<Curve> {
        let algebraic_type = Self::algebraic_type();
        kind.to_input(algebraic_type)
    }
}

impl ActuallyUsedField for BaseField {
    fn bounds_to_field_bounds(b: Bounds) -> FieldBounds<Self> {
        BaseField::unwrap(b)
    }

    fn field_bounds_to_bounds(field: FieldBounds<Self>) -> Bounds {
        BaseField::wrap(field)
    }

    fn field_expr_to_expr<T: Clone>(expr: FieldExpr<Self, T>) -> Expr<T> {
        Expr::Base(expr)
    }

    fn conversion_expr_to_expr<T: Clone>(expr: ConversionExpr<Self, T>) -> Expr<T> {
        Expr::BaseConversion(expr)
    }

    fn expr_to_field_expr<T: Clone>(expr: Expr<T>) -> Option<FieldExpr<Self, T>> {
        if let Expr::Base(e) = expr {
            Some(e)
        } else {
            None
        }
    }

    fn expr_to_conversion_expr<T: Clone>(expr: Expr<T>) -> Option<ConversionExpr<Self, T>> {
        if let Expr::BaseConversion(e) = expr {
            Some(e)
        } else {
            None
        }
    }

    fn eval_value_to_field(eval_value: EvalValue) -> Self {
        BaseField::unwrap(eval_value)
    }
    fn field_to_eval_value(a: Self) -> EvalValue {
        EvalValue::Base(a)
    }
    fn field_to_constant_gate(a: Self) -> Gate {
        Gate::Input {
            input_type: core_utils::circuit::Input::BaseField(
                core_utils::circuit::BaseFieldPlaintext::<Curve>::Fixed(primitives::algebra::elliptic_curve::curve::BaseFieldElement::<Curve>::from_le_bytes(&a.to_le_bytes()).expect("failed to convert")),
            ),
        }
    }
    fn field_type() -> core_utils::circuit::FieldType {
        core_utils::circuit::FieldType::BaseField
    }

    fn algebraic_type() -> core_utils::circuit::AlgebraicType {
        core_utils::circuit::AlgebraicType::BaseField
    }
    fn input(kind: InputKind) -> core_utils::circuit::Input<Curve> {
        let algebraic_type = Self::algebraic_type();
        let batched = core_utils::circuit::Batched::No;
        match kind {
            InputKind::Secret => core_utils::circuit::Input::Share {
                algebraic_type,
                batched,
            },
            InputKind::SecretFromPlayer(i) => core_utils::circuit::Input::SecretPlaintext {
                inputer: i,
                algebraic_type,
                batched,
            },
            InputKind::Plaintext => {
                core_utils::circuit::Input::BaseField(core_utils::circuit::BaseFieldPlaintext::<
                    Curve,
                >::Input(1))
            }
        }
    }
}

impl<F: ActuallyUsedField> GetBit for F {
    type Output = bool;

    fn get_bit(&self, index: usize, signed: bool) -> Self::Output {
        if signed {
            self.signed_bit(index)
        } else {
            self.unsigned_bit(index)
        }
    }
}

impl<F: ActuallyUsedField> FromLeBits<bool> for F {
    fn from_le_bits(bits: Vec<bool>, signed: bool) -> Self {
        let n = bits.len();
        bits.into_iter()
            .enumerate()
            .map(|(i, x)| {
                if x {
                    if signed && i + 1 == n {
                        F::negative_power_of_two(i)
                    } else {
                        F::power_of_two(i)
                    }
                } else {
                    F::ZERO
                }
            })
            .sum()
    }
}

impl FromF25519<BaseField> for BaseField {
    fn from_F25519(value: BaseField) -> Vec<BaseField> {
        vec![value]
    }
}

impl FromF25519<BaseField> for ScalarField {
    /// Injectively map a BaseField element into a Vec of ScalarField elements.
    fn from_F25519(value: BaseField) -> Vec<ScalarField> {
        let bytes = value.to_le_bytes();
        // we chunk bytes by ScalarField::n_bytes - 1 and convert to ScalarField
        let chunk_size = ScalarField::NUM_BITS.div_ceil(8) as usize - 1;
        bytes
            .chunks(chunk_size)
            .map(|chunk| {
                let mut chunk_bytes = [0u8; 32];
                chunk_bytes[..chunk.len()].copy_from_slice(chunk);
                ScalarField::from_le_bytes(chunk_bytes)
            })
            .collect::<Vec<ScalarField>>()
    }
}

impl<F: ActuallyUsedField> Random for F {
    fn random() -> Self {
        let rng = &mut rand::thread_rng();
        F::random(rng)
    }
}

impl<F: ActuallyUsedField> Reveal for F {
    fn reveal(self) -> Self {
        self
    }
}

impl<F: ActuallyUsedField> WithBooleanBounds for F {
    fn with_boolean_bounds(&self) -> Self {
        assert!(self.le(&F::from(1)));
        *self
    }
}

impl MxeX25519PrivateKey for ScalarField {
    fn mxe_x25519_private_key() -> Self {
        ScalarField::from_le_bytes(MXE_X25519_PRIVATE_KEY)
    }
}

impl MxeRescueKey for BaseField {
    fn mxe_rescue_key(i: usize) -> Self {
        debug_assert!(i < RESCUE_KEY_COUNT);
        BaseField::from_le_bytes(MXE_RESCUE_BASE_FIELD_KEY[i])
    }
}

impl MxeRescueKey for ScalarField {
    fn mxe_rescue_key(i: usize) -> Self {
        debug_assert!(i < RESCUE_KEY_COUNT);
        ScalarField::from_le_bytes(MXE_RESCUE_SCALAR_FIELD_KEY[i])
    }
}
