use crate::{
    core::{
        actually_used_field::ActuallyUsedField,
        circuits::boolean::boolean_value::{Boolean, BooleanValue},
        global_value::{field_array::FieldArray, value::FieldValue},
    },
    traits::{RandomBit, Reveal, Select},
};
use core::panic;
use std::ops::{BitAnd, BitAndAssign, BitXor, BitXorAssign, Index, Not};

#[derive(Debug, Clone, Copy)]
pub struct BooleanArray<const N: usize>([BooleanValue; N]);

impl<const N: usize> IntoIterator for BooleanArray<N> {
    type Item = BooleanValue;
    type IntoIter = <[BooleanValue; N] as IntoIterator>::IntoIter;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl<const N: usize> Index<usize> for BooleanArray<N> {
    type Output = BooleanValue;

    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index]
    }
}

impl<const N: usize> BitAnd for BooleanArray<N> {
    type Output = BooleanArray<N>;

    fn bitand(self, rhs: Self) -> Self::Output {
        Self(
            self.into_iter()
                .zip(rhs)
                .map(|(bit_lhs, bit_rhs)| bit_lhs & bit_rhs)
                .collect::<Vec<BooleanValue>>()
                .try_into()
                .unwrap_or_else(|v: Vec<BooleanValue>| {
                    panic!("Expected a Vec of length {} (found {})", N, v.len())
                }),
        )
    }
}

impl<const N: usize> BitAndAssign for BooleanArray<N> {
    fn bitand_assign(&mut self, rhs: Self) {
        *self = *self & rhs;
    }
}

impl<const N: usize> BitXor for BooleanArray<N> {
    type Output = BooleanArray<N>;

    fn bitxor(self, rhs: Self) -> Self::Output {
        Self(
            self.into_iter()
                .zip(rhs)
                .map(|(bit_lhs, bit_rhs)| bit_lhs ^ bit_rhs)
                .collect::<Vec<BooleanValue>>()
                .try_into()
                .unwrap_or_else(|v: Vec<BooleanValue>| {
                    panic!("Expected a Vec of length {} (found {})", N, v.len())
                }),
        )
    }
}

impl<const N: usize> BitXorAssign for BooleanArray<N> {
    fn bitxor_assign(&mut self, rhs: Self) {
        *self = *self ^ rhs;
    }
}

impl<const N: usize> Not for BooleanArray<N> {
    type Output = BooleanArray<N>;

    fn not(self) -> Self::Output {
        Self(self.0.map(|bit| !bit))
    }
}

impl<const N: usize> RandomBit for BooleanArray<N> {
    fn random() -> Self {
        Self(
            (0..N)
                .map(|_| BooleanValue::random())
                .collect::<Vec<BooleanValue>>()
                .try_into()
                .unwrap_or_else(|v: Vec<BooleanValue>| {
                    panic!("Expected a Vec of length {} (found {})", N, v.len())
                }),
        )
    }
}

impl<const N: usize> Reveal for BooleanArray<N> {
    fn reveal(self) -> Self {
        Self(self.0.map(|bit| bit.reveal()))
    }
}

impl<const N: usize> Select<BooleanArray<N>, BooleanArray<N>, BooleanArray<N>> for BooleanArray<N> {
    fn select(self, x: BooleanArray<N>, y: BooleanArray<N>) -> BooleanArray<N> {
        y ^ self & (x ^ y)
    }
}

impl<const N: usize> Select<Vec<BooleanArray<N>>, Vec<BooleanArray<N>>, Vec<BooleanArray<N>>>
    for BooleanArray<N>
{
    fn select(self, x: Vec<BooleanArray<N>>, y: Vec<BooleanArray<N>>) -> Vec<BooleanArray<N>> {
        assert_eq!(x.len(), y.len());
        x.into_iter()
            .zip(y)
            .map(|(bit_x, bit_y)| self.select(bit_x, bit_y))
            .collect::<Vec<BooleanArray<N>>>()
    }
}

impl<const N: usize, F: ActuallyUsedField>
    Select<FieldArray<N, F>, FieldArray<N, F>, FieldArray<N, F>> for BooleanArray<N>
{
    fn select(self, x: FieldArray<N, F>, y: FieldArray<N, F>) -> FieldArray<N, F> {
        FieldArray::from(
            TryInto::<[FieldValue<F>; N]>::try_into(
                self.into_iter()
                    .zip(x)
                    .zip(y)
                    .map(|((cond, val_x), val_y)| cond.select(val_x, val_y))
                    .collect::<Vec<FieldValue<F>>>(),
            )
            .unwrap_or_else(|v: Vec<FieldValue<F>>| {
                panic!("Expected a Vec of length {} (found {})", N, v.len())
            }),
        )
    }
}

impl<const N: usize> From<[BooleanValue; N]> for BooleanArray<N> {
    fn from(value: [BooleanValue; N]) -> Self {
        Self(value)
    }
}

impl<const N: usize> From<BooleanArray<N>> for [BooleanValue; N] {
    fn from(value: BooleanArray<N>) -> Self {
        value.0
    }
}

impl<const N: usize> From<bool> for BooleanArray<N> {
    fn from(value: bool) -> Self {
        Self([BooleanValue::from(value); N])
    }
}

impl<const N: usize> From<BooleanValue> for BooleanArray<N> {
    fn from(value: BooleanValue) -> Self {
        Self([value; N])
    }
}

impl<const N: usize> Boolean for BooleanArray<N> {}
