use std::iter::Zip;

use crate::{sequence::PlainCollection, *};

#[derive(
    ListHashes, Topological, Tagged, Size, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord,
)]
pub struct TupleOfArrays<A, B>(A, B);

impl<A: ByteOrd + Size, B: ByteOrd> ByteOrd for TupleOfArrays<A, B> {
    fn bytes_cmp(&self, other: &Self) -> Ordering {
        self.0
            .bytes_cmp(&other.0)
            .then_with(|| self.1.bytes_cmp(&other.1))
    }
}

impl<A: ToOutput, B: ToOutput> ToOutput for TupleOfArrays<A, B> {
    fn to_output(&self, output: &mut impl crate::Output) {
        self.0.to_output(output);
        self.1.to_output(output);
    }
}

pub fn try_divide(n: usize, k: usize) -> crate::Result<usize> {
    if !n.is_multiple_of(k) {
        return Err(crate::error_parse!("doesn't divide evenly"));
    }
    n.checked_div(k)
        .ok_or_else(|| crate::error_parse!("division failed"))
}

impl<
    A: Parse<I> + PlainCollection<Item = Ae>,
    B: Parse<I> + PlainCollection<Item = Be>,
    I: ParseInput,
    Ae: Size,
    Be: Size,
> Parse<I> for TupleOfArrays<A, B>
{
    fn parse(input: I) -> crate::Result<Self> {
        let (mut input, n) = input.remaining()?;
        let n = try_divide(n, Ae::SIZE + Be::SIZE)? * Ae::SIZE;
        Ok(Self(input.split_parse(n)?, input.parse()?))
    }
}

impl<A: IntoIterator, B: IntoIterator> IntoIterator for TupleOfArrays<A, B> {
    type Item = (A::Item, B::Item);

    type IntoIter = Zip<A::IntoIter, B::IntoIter>;

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

impl<'a, A, B> IntoIterator for &'a TupleOfArrays<A, B>
where
    &'a A: IntoIterator,
    &'a B: IntoIterator,
{
    type Item = (<&'a A as IntoIterator>::Item, <&'a B as IntoIterator>::Item);

    type IntoIter = Zip<<&'a A as IntoIterator>::IntoIter, <&'a B as IntoIterator>::IntoIter>;

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

impl<'a, A, B> IntoIterator for &'a mut TupleOfArrays<A, B>
where
    &'a mut A: IntoIterator,
    &'a mut B: IntoIterator,
{
    type Item = (
        <&'a mut A as IntoIterator>::Item,
        <&'a mut B as IntoIterator>::Item,
    );

    type IntoIter =
        Zip<<&'a mut A as IntoIterator>::IntoIter, <&'a mut B as IntoIterator>::IntoIter>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter().zip(&mut self.1)
    }
}

impl<A: PlainCollection, B: PlainCollection> PlainCollection for TupleOfArrays<A, B> {}

impl<A: Extend<L>, B: Extend<R>, L, R> Extend<(L, R)> for TupleOfArrays<A, B> {
    fn extend<T: IntoIterator<Item = (L, R)>>(&mut self, iter: T) {
        let (a, b): (Vec<_>, Vec<_>) = iter.into_iter().collect();
        self.0.extend(a);
        self.1.extend(b);
    }
}

impl<A: FromIterator<L>, B: FromIterator<R>, L, R> FromIterator<(L, R)> for TupleOfArrays<A, B> {
    fn from_iter<T: IntoIterator<Item = (L, R)>>(iter: T) -> Self {
        let (a, b): (Vec<_>, Vec<_>) = iter.into_iter().collect();
        Self(a.into_iter().collect(), b.into_iter().collect())
    }
}
