#[allow(unused_imports)]
use approx::abs_diff_eq;
use boostvoronoi::prelude as BV;
use boostvoronoi::prelude::*;
#[allow(unused_imports)]
use boostvoronoi::try_cast;

#[allow(dead_code)]
pub fn almost_equal(x1: f64, x2: f64, y1: f64, y2: f64) -> bool {
    let epsilon = 0.000001;
    abs_diff_eq!(x1, x2, epsilon = epsilon) && abs_diff_eq!(y1, y2, epsilon = epsilon)
}

#[cfg(feature = "geo")]
fn geo_line_point_distance<T: geo::GeoFloat>(coord: geo::Coord<T>, line: geo::Line<T>) -> T {
    use geo::algorithm::closest_point::ClosestPoint;
    use geo::{Closest, Distance, Euclidean};
    let point: geo::Point<T> = coord.into();
    match line.closest_point(&point) {
        Closest::Intersection(c_point) | Closest::SinglePoint(c_point) => {
            Euclidean.distance(c_point, point)
        }
        Closest::Indeterminate => {
            unreachable!("Simple line segment should always have a determinate closest point")
        }
    }
}

#[cfg(feature = "geo")]
fn geo_point_point_distance<T: geo::GeoFloat>(point0: geo::Coord<T>, point1: geo::Coord<T>) -> T {
    use geo::{Distance, Euclidean};
    Euclidean.distance(point0, point1)
}

#[cfg(feature = "geo")]
#[allow(dead_code)]
/// A brute force-check to see if all the vertices really are at the midpoint
/// between (at least) two segments or points. O(v*(p+s))
pub fn diagram_sanity_check<I: InputType + geo::CoordNum>(
    diagram: &Diagram,
    points: &[BV::Point<I>],
    segments: &[BV::Line<I>],
    delta: f64,
) -> Result<(), BvError> {
    // check that delta has a sane value
    assert!(delta.is_sign_positive() && delta <= 0.0001);

    let coordinates: Vec<_> = points
        .iter()
        .map(|p| {
            Ok(geo::Coord::<f64>::from([
                try_cast::<I, f64>(p.x)?,
                try_cast::<I, f64>(p.y)?,
            ]))
        })
        .collect::<Result<Vec<_>, BvError>>()?;
    let lines: Vec<_> = segments
        .iter()
        .map(|l| {
            Ok(geo::Line::<f64>::from([
                (
                    try_cast::<I, f64>(l.start.x)?,
                    try_cast::<I, f64>(l.start.y)?,
                ),
                (try_cast::<I, f64>(l.end.x)?, try_cast::<I, f64>(l.end.y)?),
            ]))
        })
        .collect::<Result<Vec<_>, BvError>>()?;

    // this vec will contain distances of equal value, it will be cleared whenever a smaller
    // value is found.
    let mut heap: Vec<f64> = Vec::new();

    for v in diagram.vertices().iter() {
        let v = geo::Coord::from(v);
        for l in lines.iter() {
            let distance = geo_line_point_distance(v, *l);
            //print!("s{:?} -> v {:?} = {:?}", s, v, distance);
            if let Some(peek) = heap.first() {
                if distance <= *peek {
                    if *peek - distance > delta {
                        // this sample is smaller than anything before
                        heap.clear();
                    }
                } else if distance - *peek > delta {
                    // ignore this sample, get a new sample
                    continue;
                }
            }
            //println!();
            heap.push(distance);
        }
        for c in coordinates.iter() {
            let distance = geo_point_point_distance(v, *c);
            //print!("s{:?} -> v {:?} = {:?}", s, v, distance);
            if let Some(peek) = heap.first() {
                if distance <= *peek {
                    if *peek - distance > delta {
                        // this sample is smaller than anything before
                        heap.clear();
                    }
                } else if distance - *peek > delta {
                    // ignore this sample, get a new sample
                    continue;
                }
            }
            //println!();
            heap.push(distance);
        }
        if heap.len() < 2 {
            let err_msg = format!(
                "Got a vertex with only one close neighbour: {:?}, dist:{:?}",
                v,
                heap.first()
            );

            eprintln!("{}", err_msg);
            return Err(BvError::InternalError(err_msg));
        }
        heap.clear();
    }
    Ok(())
}

#[allow(dead_code)]
pub fn retrieve_point<T: InputType>(
    point_data_: &[Point<T>],
    segment_data_: &[Line<T>],
    source: (BV::SourceIndex, SourceCategory),
) -> Point<T> {
    let source_index: usize = source.0.usize();
    match source.1 {
        SourceCategory::SinglePoint => point_data_[source_index],
        SourceCategory::SegmentStart => segment_data_[source_index - point_data_.len()].start,
        SourceCategory::Segment | SourceCategory::SegmentEnd => {
            segment_data_[source_index - point_data_.len()].end
        }
    }
}

#[allow(dead_code)]
pub fn to_points<I: InputType>(points: &[[I; 2]]) -> Vec<Point<I>> {
    points.iter().map(|p| p.into()).collect()
}

#[allow(dead_code)]
pub fn to_segments<I: InputType>(segments: &[[I; 4]]) -> Vec<Line<I>> {
    segments.iter().map(|l| l.into()).collect()
}
