use super::{MODULUS, R_2};
use crate::arithmetic::util::{adc, mac, sbb};
pub type Fe = [u64; 4];
#[inline]
pub const fn fe_from_montgomery(w: &Fe) -> Fe {
montgomery_reduce(&[w[0], w[1], w[2], w[3], 0, 0, 0, 0])
}
#[inline]
pub const fn fe_to_montgomery(w: &Fe) -> Fe {
fe_mul(w, R_2.as_words())
}
pub const fn fe_add(a: &Fe, b: &Fe) -> Fe {
let (w0, carry) = adc(a[0], b[0], 0);
let (w1, carry) = adc(a[1], b[1], carry);
let (w2, carry) = adc(a[2], b[2], carry);
let (w3, w4) = adc(a[3], b[3], carry);
let modulus = MODULUS.as_words();
sub_inner(
&[w0, w1, w2, w3, w4],
&[modulus[0], modulus[1], modulus[2], modulus[3], 0],
)
}
pub const fn fe_sub(a: &Fe, b: &Fe) -> Fe {
sub_inner(&[a[0], a[1], a[2], a[3], 0], &[b[0], b[1], b[2], b[3], 0])
}
pub const fn fe_mul(a: &Fe, b: &Fe) -> Fe {
let (w0, carry) = mac(0, a[0], b[0], 0);
let (w1, carry) = mac(0, a[0], b[1], carry);
let (w2, carry) = mac(0, a[0], b[2], carry);
let (w3, w4) = mac(0, a[0], b[3], carry);
let (w1, carry) = mac(w1, a[1], b[0], 0);
let (w2, carry) = mac(w2, a[1], b[1], carry);
let (w3, carry) = mac(w3, a[1], b[2], carry);
let (w4, w5) = mac(w4, a[1], b[3], carry);
let (w2, carry) = mac(w2, a[2], b[0], 0);
let (w3, carry) = mac(w3, a[2], b[1], carry);
let (w4, carry) = mac(w4, a[2], b[2], carry);
let (w5, w6) = mac(w5, a[2], b[3], carry);
let (w3, carry) = mac(w3, a[3], b[0], 0);
let (w4, carry) = mac(w4, a[3], b[1], carry);
let (w5, carry) = mac(w5, a[3], b[2], carry);
let (w6, w7) = mac(w6, a[3], b[3], carry);
montgomery_reduce(&[w0, w1, w2, w3, w4, w5, w6, w7])
}
pub const fn fe_neg(w: &Fe) -> Fe {
fe_sub(&[0, 0, 0, 0], w)
}
pub const fn fe_square(w: &Fe) -> Fe {
fe_mul(w, w)
}
#[inline]
#[allow(clippy::too_many_arguments)]
const fn montgomery_reduce(r: &[u64; 8]) -> Fe {
let r0 = r[0];
let r1 = r[1];
let r2 = r[2];
let r3 = r[3];
let r4 = r[4];
let r5 = r[5];
let r6 = r[6];
let r7 = r[7];
let modulus = MODULUS.as_words();
let (r1, carry) = mac(r1, r0, modulus[1], r0);
let (r2, carry) = adc(r2, 0, carry);
let (r3, carry) = mac(r3, r0, modulus[3], carry);
let (r4, carry2) = adc(r4, 0, carry);
let (r2, carry) = mac(r2, r1, modulus[1], r1);
let (r3, carry) = adc(r3, 0, carry);
let (r4, carry) = mac(r4, r1, modulus[3], carry);
let (r5, carry2) = adc(r5, carry2, carry);
let (r3, carry) = mac(r3, r2, modulus[1], r2);
let (r4, carry) = adc(r4, 0, carry);
let (r5, carry) = mac(r5, r2, modulus[3], carry);
let (r6, carry2) = adc(r6, carry2, carry);
let (r4, carry) = mac(r4, r3, modulus[1], r3);
let (r5, carry) = adc(r5, 0, carry);
let (r6, carry) = mac(r6, r3, modulus[3], carry);
let (r7, r8) = adc(r7, carry2, carry);
sub_inner(
&[r4, r5, r6, r7, r8],
&[modulus[0], modulus[1], modulus[2], modulus[3], 0],
)
}
#[inline]
#[allow(clippy::too_many_arguments)]
const fn sub_inner(l: &[u64; 5], r: &[u64; 5]) -> Fe {
let (w0, borrow) = sbb(l[0], r[0], 0);
let (w1, borrow) = sbb(l[1], r[1], borrow);
let (w2, borrow) = sbb(l[2], r[2], borrow);
let (w3, borrow) = sbb(l[3], r[3], borrow);
let (_, borrow) = sbb(l[4], r[4], borrow);
let modulus = MODULUS.as_words();
let (w0, carry) = adc(w0, modulus[0] & borrow, 0);
let (w1, carry) = adc(w1, modulus[1] & borrow, carry);
let (w2, carry) = adc(w2, modulus[2] & borrow, carry);
let (w3, _) = adc(w3, modulus[3] & borrow, carry);
[w0, w1, w2, w3]
}