use core::convert::From;
use std::cmp::Ordering;

use crate::iteration::PairwiseCombinations;
use crate::operations::Orient;
use crate::relatable::Relation;
use crate::relating::segment_endpoints;

use super::event::Event;
use super::events_registry::EventsRegistry;

pub(crate) struct Sweep<Point> {
    events_registry: EventsRegistry<Point, false>,
    next_start_event: Option<usize>,
    segments_ids_pairs: PairwiseCombinations<usize>,
    start_event: Option<usize>,
}

impl<Point, Input> From<Input> for Sweep<Point>
where
    EventsRegistry<Point, false>: From<Input> + Iterator<Item = Event>,
{
    fn from(input: Input) -> Self {
        let mut events_registry = EventsRegistry::from(input);
        let next_start_event = events_registry.next();
        Self {
            events_registry,
            next_start_event,
            segments_ids_pairs: PairwiseCombinations::default(),
            start_event: None,
        }
    }
}

impl<Point> Sweep<Point> {
    pub(super) fn get_segment_end(&self, segment_id: usize) -> &Point {
        self.events_registry.get_segment_end(segment_id)
    }

    pub(super) fn get_segment_start(&self, segment_id: usize) -> &Point {
        self.events_registry.get_segment_start(segment_id)
    }
}

pub(crate) struct Intersection<Point> {
    pub(super) first_segment_id: usize,
    pub(super) second_segment_id: usize,
    pub(super) relation: Relation,
    pub(super) start: Point,
    pub(super) end: Point,
}

impl<Point: Clone + Ord> Iterator for Sweep<Point>
where
    EventsRegistry<Point, false>: Iterator<Item = Event>,
    for<'a> &'a Point: Orient,
{
    type Item = Intersection<Point>;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some((first_segment_id, second_segment_id)) =
            self.segments_ids_pairs.next()
        {
            let first_start = self.get_segment_start(first_segment_id);
            let first_end = self.get_segment_end(first_segment_id);
            let second_start = self.get_segment_start(second_segment_id);
            let second_end = self.get_segment_end(second_segment_id);
            let (relation, start, end) = if first_segment_id
                == second_segment_id
            {
                (Relation::Equal, first_start, first_end)
            } else if !self
                .events_registry
                .are_collinear(first_segment_id, second_segment_id)
            {
                if let Some(start_event) = self.start_event {
                    let start =
                        self.events_registry.get_event_start(start_event);
                    (
                        if first_start == start
                            || first_end == start
                            || second_start == start
                            || second_end == start
                        {
                            Relation::Touch
                        } else {
                            Relation::Cross
                        },
                        start,
                        start,
                    )
                } else {
                    debug_assert!(first_start == second_start);
                    (Relation::Touch, first_start, first_start)
                }
            } else if first_start
                .max(second_start)
                .eq(first_end.min(second_end))
            {
                let point = first_start.max(second_start);
                (Relation::Touch, point, point)
            } else {
                match first_start.cmp(second_start) {
                    Ordering::Equal => match first_end.cmp(second_end) {
                        Ordering::Equal => {
                            (Relation::Equal, first_start, first_end)
                        }
                        Ordering::Greater => {
                            (Relation::Composite, first_start, second_end)
                        }
                        Ordering::Less => {
                            (Relation::Component, first_start, first_end)
                        }
                    },
                    Ordering::Greater => match first_end.cmp(second_end) {
                        Ordering::Greater => {
                            (Relation::Overlap, first_start, second_end)
                        }
                        _ => (Relation::Component, first_start, first_end),
                    },
                    Ordering::Less => match first_end.cmp(second_end) {
                        Ordering::Less => {
                            (Relation::Overlap, second_start, first_end)
                        }
                        _ => (Relation::Composite, second_start, second_end),
                    },
                }
            };
            debug_assert_eq!(
                relation,
                segment_endpoints::relate_to_segment_endpoints(
                    (first_start, first_end),
                    (second_start, second_end)
                )
            );
            Some(Intersection {
                first_segment_id,
                second_segment_id,
                relation,
                start: start.clone(),
                end: end.clone(),
            })
        } else if let Some(next_start_event) = self.next_start_event {
            self.populate_segments_ids_pairs(next_start_event);
            self.next()
        } else {
            None
        }
    }
}

impl<Point: PartialEq> Sweep<Point>
where
    EventsRegistry<Point, false>: Iterator<Item = Event>,
{
    fn populate_segments_ids_pairs(&mut self, start_event: Event) {
        let mut segments_ids_containing_start =
            Vec::from([self.events_registry.to_event_segment_id(start_event)]);
        while let Some(event) = self.events_registry.next() {
            if self
                .events_registry
                .get_event_start(start_event)
                .ne(self.events_registry.get_event_start(event))
            {
                self.segments_ids_pairs =
                    PairwiseCombinations::from(segments_ids_containing_start);
                (self.start_event, self.next_start_event) =
                    (self.next_start_event, Some(event));
                return;
            }
            segments_ids_containing_start
                .push(self.events_registry.to_event_segment_id(event));
        }
        self.segments_ids_pairs =
            PairwiseCombinations::from(segments_ids_containing_start);
        (self.start_event, self.next_start_event) =
            (self.next_start_event, None);
    }
}
