pub mod attributes;
pub mod edges;
pub mod group_by;
pub mod nodes;
mod operand_traits;
pub mod values;
pub mod wrapper;

use super::{EdgeIndex, MedRecord, MedRecordAttribute, MedRecordValue, NodeIndex, Wrapper};
use crate::{
    errors::MedRecordResult,
    medrecord::querying::{
        attributes::{
            EdgeMultipleAttributesWithoutIndexOperand, EdgeSingleAttributeWithoutIndexOperand,
            NodeMultipleAttributesWithoutIndexOperand, NodeSingleAttributeWithoutIndexOperand,
        },
        group_by::{GroupBy, GroupKey, PartitionGroups},
        values::{EdgeSingleValueWithoutIndexOperand, NodeSingleValueWithoutIndexOperand},
    },
};
use attributes::{
    EdgeAttributesTreeOperand, EdgeMultipleAttributesWithIndexOperand,
    EdgeSingleAttributeWithIndexOperand, GetAllAttributes, GetAttributes,
    NodeAttributesTreeOperand, NodeMultipleAttributesWithIndexOperand,
    NodeSingleAttributeWithIndexOperand,
};
use edges::{EdgeIndexOperand, EdgeIndicesOperand, EdgeOperand};
use group_by::{GroupOperand, GroupedOperand};
use itertools::Itertools;
use medmodels_utils::traits::ReadWriteOrPanic;
use nodes::{NodeIndexOperand, NodeIndicesOperand, NodeOperand};
use std::{
    fmt::{Debug, Display},
    hash::Hash,
};
use values::{
    EdgeMultipleValuesWithIndexOperand, EdgeMultipleValuesWithoutIndexOperand,
    EdgeSingleValueWithIndexOperand, GetValues, NodeMultipleValuesWithIndexOperand,
    NodeMultipleValuesWithoutIndexOperand, NodeSingleValueWithIndexOperand,
};

macro_rules! impl_return_operand_for_tuples {
    ($($T:ident),+) => {
        impl<'a, $($T: ReturnOperand<'a>),+> ReturnOperand<'a> for ($($T,)+) {
            type ReturnValue = ($($T::ReturnValue,)+);

            #[allow(non_snake_case)]
            fn evaluate(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
                let ($($T,)+) = self;

                $(let $T = $T.evaluate(medrecord)?;)+

                Ok(($($T,)+))
            }
        }
    };
}

macro_rules! impl_iterator_return_operand {
    ($( $Operand:ident => $Item:ty ),* $(,)?) => {
        $(
            impl<'a> ReturnOperand<'a> for Wrapper<$Operand> {
                type ReturnValue = Box<dyn Iterator<Item = $Item> + 'a>;

                fn evaluate(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
                    Ok(Box::new(self.evaluate_backward(medrecord)?))
                }
            }
        )*
    };
}

macro_rules! impl_direct_return_operand {
    ($( $Operand:ident => $ReturnValue:ty ),* $(,)?) => {
        $(
            impl<'a> ReturnOperand<'a> for Wrapper<$Operand> {
                type ReturnValue = $ReturnValue;

                fn evaluate(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
                    self.evaluate_backward(medrecord)
                }
            }
        )*
    };
}

pub trait Index: Eq + Clone + Hash + Display + GetAttributes {}

impl Index for NodeIndex {}

impl Index for EdgeIndex {}

impl<I: Index> Index for &I {}

pub trait RootOperand:
    GetAllAttributes<Self::Index> + GetValues<Self::Index> + GroupedOperand + Debug + Clone + DeepClone
{
    type Index: Index;
    type Discriminator: Debug + Clone + DeepClone;

    fn _evaluate_forward<'a>(
        &self,
        medrecord: &'a MedRecord,
        indices: BoxedIterator<'a, &'a Self::Index>,
    ) -> MedRecordResult<BoxedIterator<'a, &'a Self::Index>>;

    fn _evaluate_forward_grouped<'a>(
        &self,
        medrecord: &'a MedRecord,
        indices: GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>,
    ) -> MedRecordResult<GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>>;

    fn _evaluate_backward<'a>(
        &self,
        medrecord: &'a MedRecord,
    ) -> MedRecordResult<BoxedIterator<'a, &'a Self::Index>>;

    fn _evaluate_backward_grouped_operand<'a>(
        group_operand: &GroupOperand<Self>,
        medrecord: &'a MedRecord,
    ) -> MedRecordResult<GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>>;

    fn _group_by(&mut self, discriminator: Self::Discriminator) -> Wrapper<GroupOperand<Self>>;

    fn _partition<'a>(
        medrecord: &'a MedRecord,
        indices: BoxedIterator<'a, &'a Self::Index>,
        discriminator: &Self::Discriminator,
    ) -> GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>;

    fn _merge<'a>(
        indices: GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>,
    ) -> BoxedIterator<'a, &'a Self::Index>;
}

impl<'a, O: RootOperand> EvaluateForward<'a> for O
where
    O: 'a,
{
    type InputValue = BoxedIterator<'a, &'a O::Index>;
    type ReturnValue = BoxedIterator<'a, &'a O::Index>;

    fn evaluate_forward(
        &self,
        medrecord: &'a MedRecord,
        indices: Self::InputValue,
    ) -> MedRecordResult<Self::ReturnValue> {
        self._evaluate_forward(medrecord, indices)
    }
}

pub type GroupedIterator<'a, O> = BoxedIterator<'a, (GroupKey<'a>, O)>;

pub(crate) fn tee_grouped_iterator<'a, O: 'a + Clone>(
    iterator: GroupedIterator<'a, BoxedIterator<'a, O>>,
) -> (
    GroupedIterator<'a, BoxedIterator<'a, O>>,
    GroupedIterator<'a, BoxedIterator<'a, O>>,
) {
    let mut iterators = (Vec::new(), Vec::new());

    iterator.for_each(|(key, inner_iterator)| {
        let (inner_iterator_1, inner_iterator_2) = Itertools::tee(inner_iterator);

        iterators
            .0
            .push((key.clone(), Box::new(inner_iterator_1) as BoxedIterator<_>));
        iterators
            .1
            .push((key, Box::new(inner_iterator_2) as BoxedIterator<_>));
    });

    (
        Box::new(iterators.0.into_iter()),
        Box::new(iterators.1.into_iter()),
    )
}

impl<'a, O: RootOperand> EvaluateForwardGrouped<'a> for O
where
    O: 'a,
{
    fn evaluate_forward_grouped(
        &self,
        medrecord: &'a MedRecord,
        indices: GroupedIterator<'a, BoxedIterator<'a, &'a O::Index>>,
    ) -> MedRecordResult<GroupedIterator<'a, BoxedIterator<'a, &'a O::Index>>> {
        self._evaluate_forward_grouped(medrecord, indices)
    }
}

impl<'a, O: RootOperand> EvaluateBackward<'a> for O
where
    O: 'a,
{
    type ReturnValue = BoxedIterator<'a, &'a O::Index>;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        self._evaluate_backward(medrecord)
    }
}

impl<'a, O: RootOperand> EvaluateBackward<'a> for GroupOperand<O>
where
    O: 'a,
{
    type ReturnValue = GroupedIterator<'a, BoxedIterator<'a, &'a O::Index>>;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        O::_evaluate_backward_grouped_operand(self, medrecord)
    }
}

impl<O: RootOperand> GroupBy for O {
    type Discriminator = <Self as RootOperand>::Discriminator;

    fn group_by(&mut self, discriminator: Self::Discriminator) -> Wrapper<GroupOperand<Self>>
    where
        Self: Sized,
    {
        self._group_by(discriminator)
    }
}

impl<'a, O: RootOperand> PartitionGroups<'a> for O
where
    O: 'a,
{
    type Values = BoxedIterator<'a, &'a O::Index>;

    fn partition(
        medrecord: &'a MedRecord,
        indices: Self::Values,
        discriminator: &Self::Discriminator,
    ) -> GroupedIterator<'a, Self::Values> {
        Self::_partition(medrecord, indices, discriminator)
    }

    fn merge(indices: GroupedIterator<'a, Self::Values>) -> Self::Values {
        Self::_merge(indices)
    }
}

pub trait EvaluateForward<'a> {
    type InputValue;
    type ReturnValue;

    fn evaluate_forward(
        &self,
        medrecord: &'a MedRecord,
        values: Self::InputValue,
    ) -> MedRecordResult<Self::ReturnValue>;
}

impl<'a, O: EvaluateForward<'a>> EvaluateForward<'a> for Wrapper<O> {
    type InputValue = O::InputValue;
    type ReturnValue = O::ReturnValue;

    fn evaluate_forward(
        &self,
        medrecord: &'a MedRecord,
        values: Self::InputValue,
    ) -> MedRecordResult<Self::ReturnValue> {
        self.0.read_or_panic().evaluate_forward(medrecord, values)
    }
}

pub trait EvaluateForwardGrouped<'a>: EvaluateForward<'a> {
    fn evaluate_forward_grouped(
        &self,
        medrecord: &'a MedRecord,
        values: GroupedIterator<'a, Self::InputValue>,
    ) -> MedRecordResult<GroupedIterator<'a, Self::ReturnValue>>;
}

impl<'a, O: EvaluateForwardGrouped<'a>> EvaluateForwardGrouped<'a> for Wrapper<O> {
    fn evaluate_forward_grouped(
        &self,
        medrecord: &'a MedRecord,
        values: GroupedIterator<'a, Self::InputValue>,
    ) -> MedRecordResult<GroupedIterator<'a, Self::ReturnValue>> {
        self.0
            .read_or_panic()
            .evaluate_forward_grouped(medrecord, values)
    }
}

pub trait EvaluateBackward<'a> {
    type ReturnValue;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue>;
}

impl<'a, O: EvaluateBackward<'a>> EvaluateBackward<'a> for Wrapper<O> {
    type ReturnValue = O::ReturnValue;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        self.0.read_or_panic().evaluate_backward(medrecord)
    }
}

pub trait ReduceInput<'a>: EvaluateForward<'a> {
    type Context: EvaluateBackward<'a>;

    fn reduce_input(
        &self,
        values: <Self::Context as EvaluateBackward<'a>>::ReturnValue,
    ) -> MedRecordResult<<Self as EvaluateForward<'a>>::InputValue>;
}

impl<'a, O> Wrapper<O> {
    pub(crate) fn reduce_input(
        &self,
        values: <<O as ReduceInput<'a>>::Context as EvaluateBackward<'a>>::ReturnValue,
    ) -> MedRecordResult<<O as EvaluateForward<'a>>::InputValue>
    where
        O: ReduceInput<'a>,
    {
        self.0.read_or_panic().reduce_input(values)
    }
}

pub trait DeepClone {
    fn deep_clone(&self) -> Self;
}

impl<T: DeepClone> DeepClone for Option<T> {
    fn deep_clone(&self) -> Self {
        self.as_ref().map(|value| value.deep_clone())
    }
}

impl<T: DeepClone> DeepClone for Box<T> {
    fn deep_clone(&self) -> Self {
        Box::new(T::deep_clone(self))
    }
}

impl<T: DeepClone> DeepClone for Vec<T> {
    fn deep_clone(&self) -> Self {
        self.iter().map(|value| value.deep_clone()).collect()
    }
}

pub(crate) type BoxedIterator<'a, T> = Box<dyn Iterator<Item = T> + 'a>;

#[derive(Debug, Clone)]
pub struct Selection<'a, R: ReturnOperand<'a>> {
    medrecord: &'a MedRecord,
    return_operand: R,
}

impl<'a, R: ReturnOperand<'a>> Selection<'a, R> {
    pub fn new_node<Q>(medrecord: &'a MedRecord, query: Q) -> Self
    where
        Q: FnOnce(&Wrapper<NodeOperand>) -> R,
    {
        let operand = Wrapper::<NodeOperand>::new(None);

        Self {
            medrecord,
            return_operand: query(&operand),
        }
    }

    pub fn new_edge<Q>(medrecord: &'a MedRecord, query: Q) -> Self
    where
        Q: FnOnce(&Wrapper<EdgeOperand>) -> R,
    {
        let operand = Wrapper::<EdgeOperand>::new(None);

        Self {
            medrecord,
            return_operand: query(&operand),
        }
    }

    pub fn evaluate(&self) -> MedRecordResult<R::ReturnValue> {
        self.return_operand.evaluate(self.medrecord)
    }
}

pub trait ReturnOperand<'a> {
    type ReturnValue;

    fn evaluate(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue>;
}

impl_iterator_return_operand!(
    NodeAttributesTreeOperand                 => (&'a NodeIndex, Vec<MedRecordAttribute>),
    EdgeAttributesTreeOperand                 => (&'a EdgeIndex, Vec<MedRecordAttribute>),
    NodeMultipleAttributesWithIndexOperand    => (&'a NodeIndex, MedRecordAttribute),
    NodeMultipleAttributesWithoutIndexOperand => MedRecordAttribute,
    EdgeMultipleAttributesWithIndexOperand    => (&'a EdgeIndex, MedRecordAttribute),
    EdgeMultipleAttributesWithoutIndexOperand => MedRecordAttribute,
    EdgeIndicesOperand                        => EdgeIndex,
    NodeIndicesOperand                        => NodeIndex,
    NodeMultipleValuesWithIndexOperand        => (&'a NodeIndex, MedRecordValue),
    NodeMultipleValuesWithoutIndexOperand     => MedRecordValue,
    EdgeMultipleValuesWithIndexOperand        => (&'a EdgeIndex, MedRecordValue),
    EdgeMultipleValuesWithoutIndexOperand     => MedRecordValue,
);

impl_direct_return_operand!(
    NodeSingleAttributeWithIndexOperand    => Option<(&'a NodeIndex, MedRecordAttribute)>,
    NodeSingleAttributeWithoutIndexOperand => Option<MedRecordAttribute>,
    EdgeSingleAttributeWithIndexOperand    => Option<(&'a EdgeIndex, MedRecordAttribute)>,
    EdgeSingleAttributeWithoutIndexOperand => Option<MedRecordAttribute>,
    EdgeIndexOperand                       => Option<EdgeIndex>,
    NodeIndexOperand                       => Option<NodeIndex>,
    NodeSingleValueWithIndexOperand        => Option<(&'a NodeIndex, MedRecordValue)>,
    NodeSingleValueWithoutIndexOperand     => Option<MedRecordValue>,
    EdgeSingleValueWithIndexOperand        => Option<(&'a EdgeIndex, MedRecordValue)>,
    EdgeSingleValueWithoutIndexOperand     => Option<MedRecordValue>,
);

impl<'a, O: GroupedOperand> ReturnOperand<'a> for Wrapper<GroupOperand<O>>
where
    GroupOperand<O>: EvaluateBackward<'a>,
    Wrapper<O>: ReturnOperand<'a>,
{
    type ReturnValue = <Self as EvaluateBackward<'a>>::ReturnValue;

    fn evaluate(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        self.evaluate_backward(medrecord)
    }
}

impl_return_operand_for_tuples!(R1);
impl_return_operand_for_tuples!(R1, R2);
impl_return_operand_for_tuples!(R1, R2, R3);
impl_return_operand_for_tuples!(R1, R2, R3, R4);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14);
impl_return_operand_for_tuples!(R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15);

impl<'a, R: ReturnOperand<'a>> ReturnOperand<'a> for &R {
    type ReturnValue = R::ReturnValue;

    fn evaluate(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        R::evaluate(self, medrecord)
    }
}

impl<'a, R: ReturnOperand<'a>> ReturnOperand<'a> for &mut R {
    type ReturnValue = R::ReturnValue;

    fn evaluate(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        R::evaluate(self, medrecord)
    }
}
