use super::{
    operation::{EdgeIndexOperation, EdgeIndicesOperation, EdgeOperation},
    BinaryArithmeticKind, EdgeOperandContext, MultipleComparisonKind, SingleComparisonKind,
    SingleKind,
};
use crate::{
    errors::MedRecordResult,
    medrecord::{
        querying::{
            attributes::{AttributesTreeContext, AttributesTreeOperand},
            edges::{group_by, EdgeIndicesOperandContext, EdgeOperandGroupDiscriminator},
            group_by::{GroupKey, GroupOperand, PartitionGroups},
            nodes::{self, NodeOperand},
            operand_traits::{
                Add, Attribute, Attributes, Contains, Count, EitherOr, EndsWith, EqualTo, Exclude,
                GreaterThan, GreaterThanOrEqualTo, HasAttribute, InGroup, Index, IsIn, IsMax,
                IsMin, IsNotIn, LessThan, LessThanOrEqualTo, Max, Min, Mod, Mul, NotEqualTo, Pow,
                Random, SourceNode, StartsWith, Sub, Sum, TargetNode,
            },
            values::{self, MultipleValuesWithIndexOperand},
            wrapper::{CardinalityWrapper, Wrapper},
            BoxedIterator, DeepClone, EvaluateBackward, EvaluateForward, EvaluateForwardGrouped,
            GroupedIterator, ReduceInput, RootOperand,
        },
        EdgeIndex, Group, MedRecordAttribute,
    },
    prelude::MedRecordValue,
    MedRecord,
};
use medmodels_utils::{aliases::MrHashSet, traits::ReadWriteOrPanic};
use std::{
    collections::{HashMap, HashSet},
    fmt::Debug,
};

#[derive(Debug, Clone)]
pub struct EdgeOperand {
    context: Option<EdgeOperandContext>,
    operations: Vec<EdgeOperation>,
}

impl DeepClone for EdgeOperand {
    fn deep_clone(&self) -> Self {
        Self {
            context: self.context.clone(),
            operations: self
                .operations
                .iter()
                .map(|operation| operation.deep_clone())
                .collect(),
        }
    }
}

impl RootOperand for EdgeOperand {
    type Index = EdgeIndex;
    type Discriminator = EdgeOperandGroupDiscriminator;

    fn _evaluate_forward<'a>(
        &self,
        medrecord: &'a MedRecord,
        edge_indices: BoxedIterator<'a, &'a Self::Index>,
    ) -> MedRecordResult<BoxedIterator<'a, &'a Self::Index>> {
        self.operations
            .iter()
            .try_fold(edge_indices, |edge_indices, operation| {
                operation.evaluate(medrecord, edge_indices)
            })
    }

    fn _evaluate_forward_grouped<'a>(
        &self,
        medrecord: &'a MedRecord,
        edge_indices: GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>,
    ) -> MedRecordResult<GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>> {
        self.operations
            .iter()
            .try_fold(edge_indices, |edge_indices, operation| {
                operation.evaluate_grouped(medrecord, edge_indices)
            })
    }

    fn _evaluate_backward<'a>(
        &self,
        medrecord: &'a MedRecord,
    ) -> MedRecordResult<BoxedIterator<'a, &'a Self::Index>> {
        let edge_indices: BoxedIterator<_> = match &self.context {
            Some(EdgeOperandContext::Edges { operand, kind }) => {
                let node_indices = operand.evaluate_backward(medrecord)?;

                match kind {
                    nodes::EdgeDirection::Incoming => {
                        Box::new(node_indices.flat_map(|node_index| {
                            medrecord
                                .incoming_edges(node_index)
                                .expect("Node must exist.")
                        }))
                    }
                    nodes::EdgeDirection::Outgoing => {
                        Box::new(node_indices.flat_map(|node_index| {
                            medrecord
                                .outgoing_edges(node_index)
                                .expect("Node must exist.")
                        }))
                    }
                    nodes::EdgeDirection::Both => Box::new(node_indices.flat_map(|node_index| {
                        medrecord
                            .incoming_edges(node_index)
                            .expect("Node must exist")
                            .chain(
                                medrecord
                                    .outgoing_edges(node_index)
                                    .expect("Node must exist"),
                            )
                    })),
                }
            }
            Some(EdgeOperandContext::GroupBy { operand }) => {
                operand.evaluate_backward(medrecord)?
            }
            None => Box::new(medrecord.edge_indices()),
        };

        self.evaluate_forward(medrecord, edge_indices)
    }

    fn _evaluate_backward_grouped_operand<'a>(
        group_operand: &GroupOperand<Self>,
        medrecord: &'a MedRecord,
    ) -> MedRecordResult<GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>> {
        match &group_operand.context {
            group_by::EdgeOperandContext::Discriminator(discriminator) => {
                let edge_indices = group_operand.operand.evaluate_backward(medrecord)?;

                Ok(Self::partition(medrecord, edge_indices, discriminator))
            }
            group_by::EdgeOperandContext::Edges(operand) => {
                let partitions = operand.evaluate_backward(medrecord)?;

                let Some(EdgeOperandContext::Edges {
                    operand: _,
                    ref kind,
                }) = group_operand.operand.0.read_or_panic().context
                else {
                    unreachable!()
                };

                let indices: Vec<_> = partitions
                    .map(|(key, partition)| {
                        let reduced_partition: BoxedIterator<_> = match kind {
                            nodes::EdgeDirection::Incoming => {
                                Box::new(partition.flat_map(|node_index| {
                                    medrecord
                                        .incoming_edges(node_index)
                                        .expect("Node must exist.")
                                }))
                            }
                            nodes::EdgeDirection::Outgoing => {
                                Box::new(partition.flat_map(|node_index| {
                                    medrecord
                                        .outgoing_edges(node_index)
                                        .expect("Node must exist.")
                                }))
                            }
                            nodes::EdgeDirection::Both => {
                                Box::new(partition.flat_map(|node_index| {
                                    medrecord
                                        .incoming_edges(node_index)
                                        .expect("Node must exist")
                                        .chain(
                                            medrecord
                                                .outgoing_edges(node_index)
                                                .expect("Node must exist"),
                                        )
                                }))
                            }
                        };

                        let partition = group_operand
                            .operand
                            .evaluate_forward(medrecord, reduced_partition)?;

                        Ok((key, partition))
                    })
                    .collect::<MedRecordResult<_>>()?;

                Ok(Box::new(indices.into_iter()))
            }
        }
    }

    fn _group_by(&mut self, discriminator: Self::Discriminator) -> Wrapper<GroupOperand<Self>> {
        let edge_operand = Wrapper::<Self>::new(Some(EdgeOperandContext::GroupBy {
            operand: Box::new(self.deep_clone()),
        }));
        let operand = Wrapper::<GroupOperand<Self>>::new(discriminator.into(), edge_operand);

        self.operations.push(EdgeOperation::GroupBy {
            operand: operand.clone(),
        });

        operand
    }

    fn _partition<'a>(
        medrecord: &'a MedRecord,
        edge_indices: BoxedIterator<'a, &'a Self::Index>,
        discriminator: &Self::Discriminator,
    ) -> GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>> {
        match discriminator {
            EdgeOperandGroupDiscriminator::SourceNode => {
                let mut buckets: HashMap<&'a MedRecordAttribute, Vec<&'a EdgeIndex>> =
                    HashMap::new();

                for edge_index in edge_indices {
                    let source_node = medrecord
                        .edge_endpoints(edge_index)
                        .expect("Edge must exist")
                        .0;

                    buckets.entry(source_node).or_default().push(edge_index);
                }

                Box::new(buckets.into_iter().map(|(key, group)| {
                    (
                        GroupKey::NodeIndex(key),
                        Box::new(group.into_iter()) as BoxedIterator<_>,
                    )
                }))
            }
            EdgeOperandGroupDiscriminator::TargetNode => {
                let mut buckets: HashMap<&'a MedRecordAttribute, Vec<&'a EdgeIndex>> =
                    HashMap::new();

                for edge_index in edge_indices {
                    let target_node = medrecord
                        .edge_endpoints(edge_index)
                        .expect("Edge must exist")
                        .1;

                    buckets.entry(target_node).or_default().push(edge_index);
                }

                Box::new(buckets.into_iter().map(|(key, group)| {
                    (
                        GroupKey::NodeIndex(key),
                        Box::new(group.into_iter()) as BoxedIterator<_>,
                    )
                }))
            }
            EdgeOperandGroupDiscriminator::Parallel => {
                let mut buckets: HashMap<
                    (&'a MedRecordAttribute, &'a MedRecordAttribute),
                    Vec<&'a EdgeIndex>,
                > = HashMap::new();

                for edge_index in edge_indices {
                    let endpoints = medrecord
                        .edge_endpoints(edge_index)
                        .expect("Edge must exist");

                    buckets
                        .entry((endpoints.0, endpoints.1))
                        .or_default()
                        .push(edge_index);
                }

                Box::new(buckets.into_iter().map(|(key, group)| {
                    (
                        GroupKey::TupleKey((
                            Box::new(GroupKey::NodeIndex(key.0)),
                            Box::new(GroupKey::NodeIndex(key.1)),
                        )),
                        Box::new(group.into_iter()) as BoxedIterator<_>,
                    )
                }))
            }
            EdgeOperandGroupDiscriminator::Attribute(attribute) => {
                let mut buckets: Vec<(Option<&'a MedRecordValue>, Vec<&'a EdgeIndex>)> = Vec::new();

                for edge_index in edge_indices {
                    let value = medrecord
                        .edge_attributes(edge_index)
                        .expect("Edge must exist")
                        .get(attribute);

                    if let Some((_, bucket)) = buckets.iter_mut().find(|(k, _)| *k == value) {
                        bucket.push(edge_index);
                    } else {
                        buckets.push((value, vec![edge_index]));
                    }
                }

                Box::new(buckets.into_iter().map(|(key, group)| {
                    (
                        GroupKey::OptionalValue(key),
                        Box::new(group.into_iter()) as BoxedIterator<_>,
                    )
                }))
            }
        }
    }

    fn _merge<'a>(
        edge_indices: GroupedIterator<'a, BoxedIterator<'a, &'a Self::Index>>,
    ) -> BoxedIterator<'a, &'a Self::Index> {
        Box::new(edge_indices.flat_map(|(_, edge_indices)| edge_indices))
    }
}

impl Attribute for EdgeOperand {
    type ReturnOperand = MultipleValuesWithIndexOperand<Self>;

    fn attribute(&mut self, attribute: MedRecordAttribute) -> Wrapper<Self::ReturnOperand> {
        let operand = Wrapper::<Self::ReturnOperand>::new(
            values::MultipleValuesWithIndexContext::Operand((self.deep_clone(), attribute)),
        );

        self.operations.push(EdgeOperation::Values {
            operand: operand.clone(),
        });

        operand
    }
}

impl Attributes for EdgeOperand {
    type ReturnOperand = AttributesTreeOperand<Self>;

    fn attributes(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand =
            Wrapper::<Self::ReturnOperand>::new(AttributesTreeContext::Operand(self.deep_clone()));

        self.operations.push(EdgeOperation::Attributes {
            operand: operand.clone(),
        });

        operand
    }
}

impl Index for EdgeOperand {
    type ReturnOperand = EdgeIndicesOperand;

    fn index(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand = Wrapper::<Self::ReturnOperand>::new(EdgeIndicesOperandContext::EdgeOperand(
            self.deep_clone(),
        ));

        self.operations.push(EdgeOperation::Indices {
            operand: operand.clone(),
        });

        operand
    }
}

impl InGroup for EdgeOperand {
    fn in_group<G: Into<CardinalityWrapper<Group>>>(&mut self, group: G) {
        self.operations.push(EdgeOperation::InGroup {
            group: group.into(),
        });
    }
}

impl HasAttribute for EdgeOperand {
    fn has_attribute<A: Into<CardinalityWrapper<MedRecordAttribute>>>(&mut self, attribute: A) {
        self.operations.push(EdgeOperation::HasAttribute {
            attribute: attribute.into(),
        });
    }
}

impl SourceNode for EdgeOperand {
    type ReturnOperand = NodeOperand;

    fn source_node(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand =
            Wrapper::<Self::ReturnOperand>::new(Some(nodes::NodeOperandContext::SourceNode {
                operand: self.deep_clone(),
            }));

        self.operations.push(EdgeOperation::SourceNode {
            operand: operand.clone(),
        });

        operand
    }
}

impl TargetNode for EdgeOperand {
    type ReturnOperand = NodeOperand;

    fn target_node(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand =
            Wrapper::<Self::ReturnOperand>::new(Some(nodes::NodeOperandContext::TargetNode {
                operand: self.deep_clone(),
            }));

        self.operations.push(EdgeOperation::TargetNode {
            operand: operand.clone(),
        });

        operand
    }
}

impl EitherOr for EdgeOperand {
    type QueryOperand = Self;

    fn either_or<EQ, OQ>(&mut self, either_query: EQ, or_query: OQ)
    where
        EQ: FnOnce(&mut Wrapper<Self::QueryOperand>),
        OQ: FnOnce(&mut Wrapper<Self::QueryOperand>),
    {
        let mut either_operand = Wrapper::<Self>::new(self.context.deep_clone());
        let mut or_operand = Wrapper::<Self>::new(self.context.deep_clone());

        either_query(&mut either_operand);
        or_query(&mut or_operand);

        self.operations.push(EdgeOperation::EitherOr {
            either: either_operand,
            or: or_operand,
        });
    }
}

impl Exclude for EdgeOperand {
    type QueryOperand = Self;

    fn exclude<Q>(&mut self, query: Q)
    where
        Q: FnOnce(&mut Wrapper<Self::QueryOperand>),
    {
        let mut operand = Wrapper::<Self>::new(self.context.deep_clone());

        query(&mut operand);

        self.operations.push(EdgeOperation::Exclude { operand });
    }
}

impl EdgeOperand {
    pub(crate) fn new(context: Option<EdgeOperandContext>) -> Self {
        Self {
            context,
            operations: Vec::new(),
        }
    }
}

impl Wrapper<EdgeOperand> {
    pub(crate) fn new(context: Option<EdgeOperandContext>) -> Self {
        EdgeOperand::new(context).into()
    }
}

#[derive(Debug, Clone)]
pub enum EdgeIndexComparisonOperand {
    Operand(EdgeIndexOperand),
    Index(EdgeIndex),
}

impl DeepClone for EdgeIndexComparisonOperand {
    fn deep_clone(&self) -> Self {
        match self {
            Self::Operand(operand) => Self::Operand(operand.deep_clone()),
            Self::Index(index) => Self::Index(*index),
        }
    }
}

impl From<Wrapper<EdgeIndexOperand>> for EdgeIndexComparisonOperand {
    fn from(index: Wrapper<EdgeIndexOperand>) -> Self {
        Self::Operand(index.0.read_or_panic().deep_clone())
    }
}

impl From<&Wrapper<EdgeIndexOperand>> for EdgeIndexComparisonOperand {
    fn from(index: &Wrapper<EdgeIndexOperand>) -> Self {
        Self::Operand(index.0.read_or_panic().deep_clone())
    }
}

impl<V: Into<EdgeIndex>> From<V> for EdgeIndexComparisonOperand {
    fn from(index: V) -> Self {
        Self::Index(index.into())
    }
}

impl EdgeIndexComparisonOperand {
    pub(crate) fn evaluate_backward(
        &self,
        medrecord: &MedRecord,
    ) -> MedRecordResult<Option<EdgeIndex>> {
        match self {
            Self::Operand(operand) => operand.evaluate_backward(medrecord),
            Self::Index(index) => Ok(Some(*index)),
        }
    }
}

#[derive(Debug, Clone)]
pub enum EdgeIndicesComparisonOperand {
    Operand(EdgeIndicesOperand),
    Indices(MrHashSet<EdgeIndex>),
}

impl DeepClone for EdgeIndicesComparisonOperand {
    fn deep_clone(&self) -> Self {
        match self {
            Self::Operand(operand) => Self::Operand(operand.deep_clone()),
            Self::Indices(indices) => Self::Indices(indices.clone()),
        }
    }
}

impl From<Wrapper<EdgeIndicesOperand>> for EdgeIndicesComparisonOperand {
    fn from(indices: Wrapper<EdgeIndicesOperand>) -> Self {
        Self::Operand(indices.0.read_or_panic().deep_clone())
    }
}

impl From<&Wrapper<EdgeIndicesOperand>> for EdgeIndicesComparisonOperand {
    fn from(indices: &Wrapper<EdgeIndicesOperand>) -> Self {
        Self::Operand(indices.0.read_or_panic().deep_clone())
    }
}

impl<V: Into<EdgeIndex>> From<Vec<V>> for EdgeIndicesComparisonOperand {
    fn from(indices: Vec<V>) -> Self {
        Self::Indices(indices.into_iter().map(Into::into).collect())
    }
}

impl<V: Into<EdgeIndex>> From<HashSet<V>> for EdgeIndicesComparisonOperand {
    fn from(indices: HashSet<V>) -> Self {
        Self::Indices(indices.into_iter().map(Into::into).collect())
    }
}

impl<V: Into<EdgeIndex>> From<MrHashSet<V>> for EdgeIndicesComparisonOperand {
    fn from(indices: MrHashSet<V>) -> Self {
        Self::Indices(indices.into_iter().map(Into::into).collect())
    }
}

impl<V: Into<EdgeIndex> + Clone, const N: usize> From<[V; N]> for EdgeIndicesComparisonOperand {
    fn from(indices: [V; N]) -> Self {
        indices.to_vec().into()
    }
}

impl EdgeIndicesComparisonOperand {
    pub(crate) fn evaluate_backward(
        &self,
        medrecord: &MedRecord,
    ) -> MedRecordResult<MrHashSet<EdgeIndex>> {
        Ok(match self {
            Self::Operand(operand) => operand.evaluate_backward(medrecord)?.collect(),
            Self::Indices(indices) => indices.clone(),
        })
    }
}

#[derive(Debug, Clone)]
pub struct EdgeIndicesOperand {
    context: EdgeIndicesOperandContext,
    operations: Vec<EdgeIndicesOperation>,
}

impl DeepClone for EdgeIndicesOperand {
    fn deep_clone(&self) -> Self {
        Self {
            context: self.context.clone(),
            operations: self.operations.iter().map(DeepClone::deep_clone).collect(),
        }
    }
}

impl<'a> EvaluateForward<'a> for EdgeIndicesOperand {
    type InputValue = BoxedIterator<'a, EdgeIndex>;
    type ReturnValue = BoxedIterator<'a, EdgeIndex>;

    fn evaluate_forward(
        &self,
        medrecord: &'a MedRecord,
        edge_indices: Self::InputValue,
    ) -> MedRecordResult<Self::ReturnValue> {
        let edge_indices: BoxedIterator<_> = Box::new(edge_indices);

        self.operations
            .iter()
            .try_fold(edge_indices, |index_tuples, operation| {
                operation.evaluate(medrecord, index_tuples)
            })
    }
}

impl<'a> EvaluateForwardGrouped<'a> for EdgeIndicesOperand {
    fn evaluate_forward_grouped(
        &self,
        medrecord: &'a MedRecord,
        edge_indices: GroupedIterator<'a, Self::InputValue>,
    ) -> MedRecordResult<GroupedIterator<'a, Self::ReturnValue>> {
        self.operations
            .iter()
            .try_fold(edge_indices, |index_tuples, operation| {
                operation.evaluate_grouped(medrecord, index_tuples)
            })
    }
}

impl<'a> EvaluateBackward<'a> for EdgeIndicesOperand {
    type ReturnValue = BoxedIterator<'a, EdgeIndex>;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        let edge_indices = self.context.evaluate_backward(medrecord)?;

        self.evaluate_forward(medrecord, edge_indices)
    }
}

impl Max for EdgeIndicesOperand {
    type ReturnOperand = EdgeIndexOperand;

    fn max(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand = Wrapper::<Self::ReturnOperand>::new(self.deep_clone(), SingleKind::Max);

        self.operations
            .push(EdgeIndicesOperation::EdgeIndexOperation {
                operand: operand.clone(),
            });

        operand
    }
}

impl Min for EdgeIndicesOperand {
    type ReturnOperand = EdgeIndexOperand;

    fn min(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand = Wrapper::<Self::ReturnOperand>::new(self.deep_clone(), SingleKind::Min);

        self.operations
            .push(EdgeIndicesOperation::EdgeIndexOperation {
                operand: operand.clone(),
            });

        operand
    }
}

impl Count for EdgeIndicesOperand {
    type ReturnOperand = EdgeIndexOperand;

    fn count(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand = Wrapper::<Self::ReturnOperand>::new(self.deep_clone(), SingleKind::Count);

        self.operations
            .push(EdgeIndicesOperation::EdgeIndexOperation {
                operand: operand.clone(),
            });

        operand
    }
}

impl Sum for EdgeIndicesOperand {
    type ReturnOperand = EdgeIndexOperand;

    fn sum(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand = Wrapper::<Self::ReturnOperand>::new(self.deep_clone(), SingleKind::Sum);

        self.operations
            .push(EdgeIndicesOperation::EdgeIndexOperation {
                operand: operand.clone(),
            });

        operand
    }
}

impl Random for EdgeIndicesOperand {
    type ReturnOperand = EdgeIndexOperand;

    fn random(&mut self) -> Wrapper<Self::ReturnOperand> {
        let operand = Wrapper::<Self::ReturnOperand>::new(self.deep_clone(), SingleKind::Random);

        self.operations
            .push(EdgeIndicesOperation::EdgeIndexOperation {
                operand: operand.clone(),
            });

        operand
    }
}

impl GreaterThan for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn greater_than<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::GreaterThan,
            });
    }
}

impl GreaterThanOrEqualTo for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn greater_than_or_equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::GreaterThanOrEqualTo,
            });
    }
}

impl LessThan for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn less_than<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::LessThan,
            });
    }
}

impl LessThanOrEqualTo for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn less_than_or_equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::LessThanOrEqualTo,
            });
    }
}

impl EqualTo for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::EqualTo,
            });
    }
}

impl NotEqualTo for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn not_equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::NotEqualTo,
            });
    }
}

impl StartsWith for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn starts_with<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::StartsWith,
            });
    }
}

impl EndsWith for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn ends_with<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::EndsWith,
            });
    }
}

impl Contains for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn contains<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::Contains,
            });
    }
}

impl IsIn for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndicesComparisonOperand;

    fn is_in<V: Into<Self::ComparisonOperand>>(&mut self, indices: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndicesComparisonOperation {
                operand: indices.into(),
                kind: MultipleComparisonKind::IsIn,
            });
    }
}

impl IsNotIn for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndicesComparisonOperand;

    fn is_not_in<V: Into<Self::ComparisonOperand>>(&mut self, indices: V) {
        self.operations
            .push(EdgeIndicesOperation::EdgeIndicesComparisonOperation {
                operand: indices.into(),
                kind: MultipleComparisonKind::IsNotIn,
            });
    }
}

impl Add for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn add<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Add,
            });
    }
}

impl Sub for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn sub<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Sub,
            });
    }
}

impl Mul for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn mul<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Mul,
            });
    }
}

impl Pow for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn pow<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Pow,
            });
    }
}

impl Mod for EdgeIndicesOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn r#mod<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndicesOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Mod,
            });
    }
}

impl IsMax for EdgeIndicesOperand {
    fn is_max(&mut self) {
        self.operations.push(EdgeIndicesOperation::IsMax);
    }
}

impl IsMin for EdgeIndicesOperand {
    fn is_min(&mut self) {
        self.operations.push(EdgeIndicesOperation::IsMin);
    }
}

impl EitherOr for EdgeIndicesOperand {
    type QueryOperand = Self;

    fn either_or<EQ, OQ>(&mut self, either_query: EQ, or_query: OQ)
    where
        EQ: FnOnce(&mut Wrapper<Self::QueryOperand>),
        OQ: FnOnce(&mut Wrapper<Self::QueryOperand>),
    {
        let mut either_operand = Wrapper::<Self>::new(self.context.clone());
        let mut or_operand = Wrapper::<Self>::new(self.context.clone());

        either_query(&mut either_operand);
        or_query(&mut or_operand);

        self.operations.push(EdgeIndicesOperation::EitherOr {
            either: either_operand,
            or: or_operand,
        });
    }
}

impl Exclude for EdgeIndicesOperand {
    type QueryOperand = Self;

    fn exclude<Q>(&mut self, query: Q)
    where
        Q: FnOnce(&mut Wrapper<Self::QueryOperand>),
    {
        let mut operand = Wrapper::<Self>::new(self.context.clone());

        query(&mut operand);

        self.operations
            .push(EdgeIndicesOperation::Exclude { operand });
    }
}

impl EdgeIndicesOperand {
    pub(crate) fn new(context: EdgeIndicesOperandContext) -> Self {
        Self {
            context,
            operations: Vec::new(),
        }
    }

    pub(crate) fn push_merge_operation(&mut self, operand: Wrapper<EdgeIndicesOperand>) {
        self.operations.push(EdgeIndicesOperation::Merge {
            operand: operand.clone(),
        });
    }
}

impl Wrapper<EdgeIndicesOperand> {
    pub(crate) fn new(context: EdgeIndicesOperandContext) -> Self {
        EdgeIndicesOperand::new(context).into()
    }

    pub(crate) fn push_merge_operation(&self, operand: Wrapper<EdgeIndicesOperand>) {
        self.0.write_or_panic().push_merge_operation(operand);
    }
}

#[derive(Debug, Clone)]
pub struct EdgeIndexOperand {
    context: EdgeIndicesOperand,
    pub(crate) kind: SingleKind,
    operations: Vec<EdgeIndexOperation>,
}

impl DeepClone for EdgeIndexOperand {
    fn deep_clone(&self) -> Self {
        Self {
            context: self.context.deep_clone(),
            kind: self.kind.clone(),
            operations: self.operations.iter().map(DeepClone::deep_clone).collect(),
        }
    }
}

impl<'a> EvaluateForward<'a> for EdgeIndexOperand {
    type InputValue = Option<EdgeIndex>;
    type ReturnValue = Option<EdgeIndex>;

    fn evaluate_forward(
        &self,
        medrecord: &'a MedRecord,
        edge_index: Self::InputValue,
    ) -> MedRecordResult<Self::ReturnValue> {
        self.operations
            .iter()
            .try_fold(edge_index, |edge_index, operation| {
                operation.evaluate(medrecord, edge_index)
            })
    }
}

impl<'a> EvaluateForwardGrouped<'a> for EdgeIndexOperand {
    fn evaluate_forward_grouped(
        &self,
        medrecord: &'a MedRecord,
        edge_indices: GroupedIterator<'a, Self::InputValue>,
    ) -> MedRecordResult<GroupedIterator<'a, Self::ReturnValue>> {
        self.operations
            .iter()
            .try_fold(edge_indices, |edge_indices, operation| {
                operation.evaluate_grouped(medrecord, edge_indices)
            })
    }
}

impl<'a> EvaluateBackward<'a> for EdgeIndexOperand {
    type ReturnValue = Option<EdgeIndex>;

    fn evaluate_backward(&self, medrecord: &'a MedRecord) -> MedRecordResult<Self::ReturnValue> {
        let edge_indices = self.context.evaluate_backward(medrecord)?;

        let edge_index = self.reduce_input(edge_indices)?;

        self.evaluate_forward(medrecord, edge_index)
    }
}

impl<'a> ReduceInput<'a> for EdgeIndexOperand {
    type Context = EdgeIndicesOperand;

    #[inline]
    fn reduce_input(
        &self,
        edge_indices: <Self::Context as EvaluateBackward<'a>>::ReturnValue,
    ) -> MedRecordResult<<Self as EvaluateForward<'a>>::InputValue> {
        Ok(match self.kind {
            SingleKind::Max => EdgeIndicesOperation::get_max(edge_indices),
            SingleKind::Min => EdgeIndicesOperation::get_min(edge_indices),
            SingleKind::Count => Some(EdgeIndicesOperation::get_count(edge_indices)),
            SingleKind::Sum => Some(EdgeIndicesOperation::get_sum(edge_indices)),
            SingleKind::Random => EdgeIndicesOperation::get_random(edge_indices),
        })
    }
}

impl GreaterThan for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn greater_than<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::GreaterThan,
            });
    }
}

impl GreaterThanOrEqualTo for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn greater_than_or_equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::GreaterThanOrEqualTo,
            });
    }
}

impl LessThan for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn less_than<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::LessThan,
            });
    }
}

impl LessThanOrEqualTo for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn less_than_or_equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::LessThanOrEqualTo,
            });
    }
}

impl EqualTo for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::EqualTo,
            });
    }
}

impl NotEqualTo for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn not_equal_to<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::NotEqualTo,
            });
    }
}

impl StartsWith for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn starts_with<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::StartsWith,
            });
    }
}

impl EndsWith for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn ends_with<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::EndsWith,
            });
    }
}

impl Contains for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn contains<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndexComparisonOperation {
                operand: value.into(),
                kind: SingleComparisonKind::Contains,
            });
    }
}

impl IsIn for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndicesComparisonOperand;

    fn is_in<V: Into<Self::ComparisonOperand>>(&mut self, indices: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndicesComparisonOperation {
                operand: indices.into(),
                kind: MultipleComparisonKind::IsIn,
            });
    }
}

impl IsNotIn for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndicesComparisonOperand;

    fn is_not_in<V: Into<Self::ComparisonOperand>>(&mut self, indices: V) {
        self.operations
            .push(EdgeIndexOperation::EdgeIndicesComparisonOperation {
                operand: indices.into(),
                kind: MultipleComparisonKind::IsNotIn,
            });
    }
}

impl Add for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn add<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Add,
            });
    }
}

impl Sub for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn sub<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Sub,
            });
    }
}

impl Mul for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn mul<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Mul,
            });
    }
}

impl Pow for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn pow<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Pow,
            });
    }
}

impl Mod for EdgeIndexOperand {
    type ComparisonOperand = EdgeIndexComparisonOperand;

    fn r#mod<V: Into<Self::ComparisonOperand>>(&mut self, value: V) {
        self.operations
            .push(EdgeIndexOperation::BinaryArithmeticOperation {
                operand: value.into(),
                kind: BinaryArithmeticKind::Mod,
            });
    }
}

impl EitherOr for EdgeIndexOperand {
    type QueryOperand = Self;

    fn either_or<EQ, OQ>(&mut self, either_query: EQ, or_query: OQ)
    where
        EQ: FnOnce(&mut Wrapper<Self::QueryOperand>),
        OQ: FnOnce(&mut Wrapper<Self::QueryOperand>),
    {
        let mut either_operand = Wrapper::<Self>::new(self.context.clone(), self.kind.clone());
        let mut or_operand = Wrapper::<Self>::new(self.context.clone(), self.kind.clone());

        either_query(&mut either_operand);
        or_query(&mut or_operand);

        self.operations.push(EdgeIndexOperation::EitherOr {
            either: either_operand,
            or: or_operand,
        });
    }
}

impl Exclude for EdgeIndexOperand {
    type QueryOperand = Self;

    fn exclude<Q>(&mut self, query: Q)
    where
        Q: FnOnce(&mut Wrapper<Self::QueryOperand>),
    {
        let mut operand = Wrapper::<Self>::new(self.context.clone(), self.kind.clone());

        query(&mut operand);

        self.operations
            .push(EdgeIndexOperation::Exclude { operand });
    }
}

impl EdgeIndexOperand {
    pub(crate) fn new(context: EdgeIndicesOperand, kind: SingleKind) -> Self {
        Self {
            context,
            kind,
            operations: Vec::new(),
        }
    }

    pub(crate) fn push_merge_operation(&mut self, operand: Wrapper<EdgeIndicesOperand>) {
        self.operations.push(EdgeIndexOperation::Merge { operand });
    }
}

impl Wrapper<EdgeIndexOperand> {
    pub(crate) fn new(context: EdgeIndicesOperand, kind: SingleKind) -> Self {
        EdgeIndexOperand::new(context, kind).into()
    }

    pub(crate) fn push_merge_operation(&self, operand: Wrapper<EdgeIndicesOperand>) {
        self.0.write_or_panic().push_merge_operation(operand);
    }
}
