use std::hash::Hash;
use std::ops::Div;

use crate::bounded::Bounded;
use crate::operations::{
    to_boxes_ids_with_intersection, CrossMultiply, IntersectCrossingSegments,
    Orient,
};
use crate::relatable::{Relatable, Relation};
use crate::sweeping::traits::{EventsQueue, SweepLine};
use crate::traits::{
    Contoural, Elemental, Iterable, Lengthsome, Multipolygonal,
    MultipolygonalIntoIteratorPolygon, Multisegmental,
    MultisegmentalIndexSegment, MultivertexalIndexVertex, Polygonal,
    PolygonalContour, PolygonalHoles, PolygonalIndexHole,
    PolygonalIntoIteratorHole, Segmental, Sequence,
};

use super::{mixed, segment_endpoints, Event};

pub(crate) fn relate_to_contour<
    'a,
    Contour,
    Point: 'a + Clone + PartialOrd,
    Segment: 'a,
>(
    segment: &'a Segment,
    contour: &'a Contour,
) -> Relation
where
    for<'b> &'b Contour: Multisegmental<IntoIteratorSegment = &'b Segment>,
    for<'b> &'b MultisegmentalIndexSegment<&'a Contour>: Segmental,
    for<'b> &'b Point: Orient,
    for<'b> &'b Segment: Segmental<Endpoint = &'b Point>,
{
    relate_to_contour_segments(segment, contour.segments().into_iter())
}

pub(crate) fn relate_to_multipolygon<
    Border,
    Multipolygon,
    Point: Clone + Ord,
    Polygon,
    Scalar: Ord,
    Segment: Clone + Segmental<Endpoint = Point>,
>(
    segment: &Segment,
    multipolygon: &Multipolygon,
) -> Relation
where
    mixed::Operation<true, Point>:
        EventsQueue<Event = Event> + SweepLine<Event = Event>,
    for<'a> &'a Border:
        Bounded<&'a Scalar> + Contoural<IntoIteratorSegment = &'a Segment>,
    for<'a> &'a Multipolygon: Multipolygonal<IndexPolygon = Polygon>,
    for<'a> &'a Point: Elemental<Coordinate = &'a Scalar>
        + IntersectCrossingSegments<Output = Point>
        + Orient,
    for<'a> &'a Polygon: Bounded<&'a Scalar>
        + Polygonal<Contour = &'a Border, IntoIteratorHole = &'a Border>,
    for<'a> &'a Segment: Bounded<&'a Scalar> + Segmental<Endpoint = &'a Point>,
    for<'a, 'b> &'a MultisegmentalIndexSegment<
        PolygonalContour<MultipolygonalIntoIteratorPolygon<&'b Multipolygon>>,
    >: Segmental,
    for<'a, 'b> &'a MultisegmentalIndexSegment<
        PolygonalIntoIteratorHole<
            MultipolygonalIntoIteratorPolygon<&'b Multipolygon>,
        >,
    >: Segmental,
    for<'a, 'b> &'a MultivertexalIndexVertex<
        PolygonalContour<MultipolygonalIntoIteratorPolygon<&'b Multipolygon>>,
    >: Elemental,
    for<'a, 'b> &'a MultivertexalIndexVertex<
        PolygonalIntoIteratorHole<
            MultipolygonalIntoIteratorPolygon<&'b Multipolygon>,
        >,
    >: Elemental,
    for<'a, 'b> &'a MultisegmentalIndexSegment<&'b Border>: Segmental,
    for<'a, 'b> &'a MultivertexalIndexVertex<&'b Border>: Elemental,
    for<'a, 'b> &'a PolygonalIndexHole<
        MultipolygonalIntoIteratorPolygon<&'b Multipolygon>,
    >: Contoural,
    for<'a, 'b> &'a PolygonalIndexHole<&'b Polygon>: Contoural,
    for<'a, 'b, 'c> &'a MultisegmentalIndexSegment<
        &'b PolygonalIndexHole<
            MultipolygonalIntoIteratorPolygon<&'c Multipolygon>,
        >,
    >: Segmental,
    for<'a, 'b, 'c> &'a MultivertexalIndexVertex<
        &'b PolygonalIndexHole<
            MultipolygonalIntoIteratorPolygon<&'c Multipolygon>,
        >,
    >: Elemental,
    for<'a, 'b, 'c> &'a MultisegmentalIndexSegment<&'b PolygonalIndexHole<&'c Polygon>>:
        Segmental,
    for<'a, 'b, 'c> &'a MultivertexalIndexVertex<&'b PolygonalIndexHole<&'c Polygon>>:
        Elemental,
{
    let segment_bounding_box = segment.to_bounding_box();
    let polygons = multipolygon.polygons();
    let polygons_bounding_boxes = polygons
        .iter()
        .map(Bounded::to_bounding_box)
        .collect::<Vec<_>>();
    let intersecting_polygons_ids = to_boxes_ids_with_intersection(
        &polygons_bounding_boxes,
        &segment_bounding_box,
    );
    if intersecting_polygons_ids.is_empty() {
        Relation::Disjoint
    } else {
        let min_max_x = segment_bounding_box.get_max_x().min(unsafe {
            intersecting_polygons_ids
                .iter()
                .map(|&border_id| {
                    polygons_bounding_boxes[border_id].get_max_x()
                })
                .max()
                .unwrap_unchecked()
        });
        let intersecting_polygons_borders_segments = intersecting_polygons_ids
            .iter()
            .map(|&polygon_id| polygons[polygon_id].border().segments())
            .collect::<Vec<_>>();
        let intersecting_polygons_holes_segments = intersecting_polygons_ids
            .iter()
            .flat_map(|&polygon_id| {
                polygons[polygon_id].holes().into_iter().filter_map(|hole| {
                    if hole
                        .to_bounding_box()
                        .disjoint_with(&segment_bounding_box)
                    {
                        None
                    } else {
                        Some(hole.segments())
                    }
                })
            })
            .collect::<Vec<_>>();
        mixed::Operation::<true, Point>::from_segments_iterators(
            (1, std::iter::once(segment.clone())),
            (
                intersecting_polygons_borders_segments
                    .iter()
                    .map(|segments| segments.len())
                    .sum::<usize>()
                    + intersecting_polygons_holes_segments
                        .iter()
                        .map(|segments| segments.len())
                        .sum::<usize>(),
                intersecting_polygons_borders_segments
                    .into_iter()
                    .flat_map(|border_segments| {
                        border_segments.into_iter().cloned()
                    })
                    .chain(
                        intersecting_polygons_holes_segments
                            .into_iter()
                            .flat_map(|hole_segments| {
                                hole_segments.into_iter().cloned()
                            }),
                    ),
            ),
        )
        .into_relation(true, min_max_x)
    }
}

pub(crate) fn relate_to_multisegment<
    'a,
    Multisegment,
    Point: 'a + Hash + Ord,
    Scalar: Div<Output = Scalar> + Eq + Hash + PartialOrd,
    Segment: 'a,
>(
    segment: &'a Segment,
    multisegment: &'a Multisegment,
) -> Relation
where
    &'a Multisegment: Multisegmental<IntoIteratorSegment = &'a Segment>,
    &'a Segment: Segmental<Endpoint = &'a Point>,
    for<'b> &'b MultisegmentalIndexSegment<&'a Multisegment>: Segmental,
    for<'b> &'b Point: CrossMultiply<Output = Scalar>
        + Elemental<Coordinate = &'b Scalar>
        + Orient,
{
    relate_to_multisegment_segments(
        segment,
        multisegment.segments().into_iter(),
    )
}

pub(crate) fn relate_to_polygon<
    Border,
    Point: Clone + Ord,
    Polygon,
    Scalar: Ord,
    Segment: Clone + Segmental<Endpoint = Point>,
>(
    segment: &Segment,
    polygon: &Polygon,
) -> Relation
where
    for<'a, 'b> &'a MultisegmentalIndexSegment<PolygonalIntoIteratorHole<&'b Polygon>>:
        Segmental,
    for<'a, 'b> &'a MultivertexalIndexVertex<&'b Border>: Elemental,
    for<'a, 'b> &'a MultivertexalIndexVertex<PolygonalIntoIteratorHole<&'b Polygon>>:
        Elemental,
    for<'a> &'a Border: Bounded<&'a Scalar>
        + Contoural<IndexSegment = Segment, IntoIteratorSegment = &'a Segment>,
    for<'a> &'a Point: Elemental<Coordinate = &'a Scalar>
        + IntersectCrossingSegments<Output = Point>
        + Orient,
    for<'a> &'a Polygon: Polygonal<Contour = &'a Border, IndexHole = Border>,
    for<'a> &'a Segment: Bounded<&'a Scalar> + Segmental<Endpoint = &'a Point>,
{
    let relation_without_holes =
        relate_to_region::<false, Border, Point, Scalar, Segment>(
            segment,
            polygon.border(),
        );
    if polygon.holes().len() > 0
        && matches!(
            relation_without_holes,
            Relation::Within | Relation::Enclosed
        )
    {
        let holes = polygon.holes();
        let relation_with_holes = if holes.len() == 1 {
            relate_to_region::<true, Border, Point, Scalar, Segment>(
                segment, &holes[0],
            )
        } else {
            relate_to_multiregion::<
                Border,
                PolygonalHoles<&Polygon>,
                Point,
                Scalar,
                Segment,
            >(segment, polygon.holes())
        };
        match relation_with_holes {
            Relation::Disjoint => relation_without_holes,
            Relation::Touch => Relation::Enclosed,
            Relation::Enclosed => Relation::Touch,
            Relation::Within => Relation::Disjoint,
            _ => relation_with_holes,
        }
    } else {
        relation_without_holes
    }
}

pub(crate) fn relate_to_segment<Point: PartialOrd, Segment>(
    first: &Segment,
    second: &Segment,
) -> Relation
where
    for<'a> &'a Point: Orient,
    for<'a> &'a Segment: Segmental<Endpoint = &'a Point>,
{
    segment_endpoints::relate_to_segment_endpoints(
        first.endpoints(),
        second.endpoints(),
    )
}

pub(super) fn relate_to_contour_segments<
    'a,
    Point: Clone + PartialOrd,
    Segment: 'a,
>(
    segment: &Segment,
    contour_segments: impl Iterator<Item = &'a Segment>,
) -> Relation
where
    for<'b> &'b Point: Orient,
    for<'b> &'b Segment: Segmental<Endpoint = &'b Point>,
{
    segment_endpoints::relate_to_contour_segments(
        segment.endpoints(),
        contour_segments,
    )
}

pub(super) fn relate_to_multiregion<
    Border,
    Borders: Sequence<IndexItem = Border>,
    Point: Clone + Ord,
    Scalar: Ord,
    Segment: Clone + Segmental<Endpoint = Point>,
>(
    segment: &Segment,
    borders: Borders,
) -> Relation
where
    mixed::Operation<true, Point>:
        EventsQueue<Event = Event> + SweepLine<Event = Event>,
    for<'a, 'b> &'a MultisegmentalIndexSegment<&'b Border>: Segmental,
    for<'a> &'a Border: Bounded<&'a Scalar>
        + Multisegmental<IntoIteratorSegment = &'a Segment>,
    for<'a> &'a Point: Elemental<Coordinate = &'a Scalar>
        + IntersectCrossingSegments<Output = Point>
        + Orient,
    for<'a> &'a Segment: Bounded<&'a Scalar> + Segmental<Endpoint = &'a Point>,
{
    debug_assert!(borders.len() > 1);
    let segment_bounding_box = segment.to_bounding_box();
    let borders_bounding_boxes = borders
        .iter()
        .map(Bounded::to_bounding_box)
        .collect::<Vec<_>>();
    let intersecting_borders_ids = to_boxes_ids_with_intersection(
        &borders_bounding_boxes,
        &segment_bounding_box,
    );
    if intersecting_borders_ids.is_empty() {
        Relation::Disjoint
    } else {
        let min_max_x = segment_bounding_box.get_max_x().min(unsafe {
            intersecting_borders_ids
                .iter()
                .map(|&border_id| {
                    borders_bounding_boxes[border_id].get_max_x()
                })
                .max()
                .unwrap_unchecked()
        });
        let intersecting_borders_segments = intersecting_borders_ids
            .iter()
            .map(|&border_id| borders[border_id].segments())
            .collect::<Vec<_>>();
        mixed::Operation::<true, Point>::from_segments_iterators(
            (1, std::iter::once(segment.clone())),
            (
                intersecting_borders_segments
                    .iter()
                    .map(|segments| segments.len())
                    .sum::<usize>(),
                intersecting_borders_segments.into_iter().flat_map(
                    |border_segments| border_segments.into_iter().cloned(),
                ),
            ),
        )
        .into_relation(
            intersecting_borders_ids.len() == borders.len(),
            min_max_x,
        )
    }
}

pub(super) fn relate_to_multisegment_segments<
    'a,
    Point: 'a + Hash + Ord,
    Scalar: Div<Output = Scalar> + Eq + Hash + PartialOrd,
    Segment: 'a,
>(
    segment: &'a Segment,
    multisegment_segments: impl Iterator<Item = &'a Segment>,
) -> Relation
where
    &'a Segment: Segmental<Endpoint = &'a Point>,
    for<'b> &'b Point: CrossMultiply<Output = Scalar>
        + Elemental<Coordinate = &'b Scalar>
        + Orient,
{
    segment_endpoints::relate_to_multisegment_segments(
        segment.endpoints(),
        multisegment_segments,
    )
}

pub(super) fn relate_to_region<
    const REVERSE_ORIENTATION: bool,
    Border,
    Point: PartialOrd,
    Scalar: PartialOrd,
    Segment,
>(
    segment: &Segment,
    border: &Border,
) -> Relation
where
    for<'a> &'a Border: Multisegmental<IndexSegment = Segment>,
    for<'a> &'a Point: Elemental<Coordinate = &'a Scalar> + Orient,
    for<'a> &'a Segment: Segmental<Endpoint = &'a Point>,
{
    segment_endpoints::relate_to_region::<
        REVERSE_ORIENTATION,
        Border,
        Point,
        Scalar,
        Segment,
    >(segment.endpoints(), border)
}
