use super::demo_data;
use crate::cast;
use crate::{ColorFlag, DrawFilterFlag, Example, FH, FW, SharedData};
use boostvoronoi::prelude::*;
use boostvoronoi::try_cast;
use boostvoronoi::utils::visual_utils::{Aabb2, SimpleAffine};
use fltk::{dialog, draw, enums};
use geo::Intersects;
use num_traits::AsPrimitive;

/// struct to help deal with the voronoi diagram input and output
pub struct VoronoiVisualizer<I: InputType> {
    screen_aabb: Aabb2,
    pub(crate) diagram: Diagram,
    points_aabb: Aabb2,

    pub(crate) point_data_: Vec<Point<I>>,
    pub(crate) segment_data_: Vec<Line<I>>,
    pub(crate) affine: SimpleAffine,
}

impl<I: InputType> Default for VoronoiVisualizer<I> {
    fn default() -> Self {
        Self {
            screen_aabb: Aabb2::new_from_i32::<I>(0, 0, FW, FH),
            diagram: Diagram::default(),
            points_aabb: Aabb2::default(),
            point_data_: Vec::<Point<I>>::new(),
            segment_data_: Vec::<Line<I>>::new(),
            affine: SimpleAffine::default(),
        }
    }
}

impl<I: InputType> VoronoiVisualizer<I>
where
    Line<I>: From<[i32; 4]>,
    Point<I>: From<[i32; 2]>,
    i32: AsPrimitive<I>,
    i64: AsPrimitive<I>,
{
    /// recalculates the affine transformation, this should not be done every time
    /// the diagram is re-calculated or the screen will move around when adding new edges and points.
    pub fn re_calculate_affine(&mut self) -> Result<(), BvError> {
        self.affine = SimpleAffine::new::<I>(&self.points_aabb, &self.screen_aabb)?;

        // Flip the z axis because fltk uses quadrant 4 when drawing
        self.affine.scale[1] *= -1.0;
        Ok(())
    }

    pub fn build(&mut self) -> Result<String, BvError> {
        if false {
            // This generates Rust test data
            println!(
                "Running voronoi with this input (in case of a crash, copy&paste and make a test case)"
            );
            print!("  let points:[[i32;2];{}]=[", self.point_data_.len());
            for p in self.point_data_.iter() {
                print!("[{},{}],", p.x, p.y)
            }
            println!("];");
            print!("  let segments:[[i32;4];{}]=[", self.segment_data_.len());
            for s in self.segment_data_.iter() {
                print!("[{},{},{},{}],", s.start.x, s.start.y, s.end.x, s.end.y)
            }
            println!("];");
        }
        if false {
            // This generates C++ test data
            print!("  int INPUT_PTS[{}][2]={{", self.point_data_.len());
            for p in self.point_data_.iter() {
                print!("{{{},{}}},", p.x, p.y)
            }
            println!("}};");

            print!("  int INPUT_SGS[{}][4]={{", self.segment_data_.len());
            for s in self.segment_data_.iter() {
                print!("{{{},{},{},{}}},", s.start.x, s.start.y, s.end.x, s.end.y)
            }
            println!("}};");
        }
        self.diagram = Builder::<I>::default()
            .with_vertices(self.point_data_.iter())?
            .with_segments(self.segment_data_.iter())?
            // Construct voronoi diagram.
            .build()?;
        println!("Result: found {} vertices", self.diagram.vertices().len());
        self.points_aabb = {
            let mut aabb = Aabb2::default();
            for p in self.point_data_.iter() {
                aabb.update_point(p);
            }
            for l in self.segment_data_.iter() {
                aabb.update_line(l);
            }
            aabb.grow_percent::<I>(20);
            aabb
        };

        // Color exterior edges.
        self.diagram
            .color_exterior_edges(ColorFlag::EXTERNAL.bits());

        // Color infinite edges
        for edge_idx in self.diagram.decoupled_edges_iter() {
            let should_mark = {
                self.diagram.edge_is_infinite(edge_idx)? || {
                    let twin = self.diagram.edge_get_twin(edge_idx)?;
                    self.diagram.edge_is_infinite(twin)?
                }
            };

            if should_mark {
                self.diagram
                    .edge_or_color(edge_idx, ColorFlag::INFINITE.bits())?;
            }
        }

        Result::Ok("".to_string())
    }

    // returns true if l intersects with any of the lines in self.segment_data_
    pub(crate) fn self_intersecting_check(&self, l: &Line<I>) -> bool {
        let l_ = Self::line_i_to_f64(l);
        for s in self.segment_data_.iter() {
            // allow end point intersection
            if (s.start.x == l.start.x && s.start.y == l.start.y)
                && (s.end.x != l.end.x && s.end.y != l.end.y)
            {
                continue;
            }
            if (s.start.x == l.end.x && s.start.y == l.end.y)
                && (s.end.x != l.start.x && s.end.y != l.start.y)
            {
                continue;
            }
            if (s.end.x == l.end.x && s.end.y == l.end.y)
                && (s.start.x != l.start.x && s.start.y != l.start.y)
            {
                continue;
            }
            if (s.end.x == l.start.x && s.end.y == l.start.y)
                && (s.start.x != l.end.x && s.start.y != l.end.y)
            {
                continue;
            }

            let s_ = Self::line_i_to_f64(s);

            if l_.intersects(&s_) {
                return true;
            }
        }
        false
    }

    pub(crate) fn draw(&self, config: &SharedData) -> Result<(), BvError> {
        draw::set_line_style(draw::LineStyle::Solid, 1);

        draw::set_draw_color(enums::Color::Red);
        if config.draw_flag.contains(DrawFilterFlag::INPUT_POINT) {
            self.draw_input_points(&self.affine);
        }
        if config.draw_flag.contains(DrawFilterFlag::INPUT_SEGMENT) {
            self.draw_input_segments(&self.affine);
        }
        if config.draw_flag.contains(DrawFilterFlag::EDGES) {
            draw::set_draw_color(enums::Color::Green);
            self.draw_edges(config, &self.affine)?;
        }
        if config.draw_flag.contains(DrawFilterFlag::VERTICES) {
            draw::set_draw_color(enums::Color::Blue);
            self.draw_vertices(config, &self.affine);
        }
        Ok(())
    }

    /// Draw input points and endpoints of the input segments.
    fn draw_input_points(&self, affine: &SimpleAffine) {
        let draw = |point: [f64; 2]| {
            draw::draw_circle(point[0], point[1], 2.0);
        };

        for i in self.point_data_.iter() {
            draw(affine.transform_p(i));
        }

        for i in self.segment_data_.iter() {
            draw(affine.transform_p(&i.start));
            draw(affine.transform_p(&i.end));
        }
    }

    /// Draw input segments.
    fn draw_input_segments(&self, affine: &SimpleAffine) {
        for i in self.segment_data_.iter() {
            let sp = affine.transform_p(&i.start);
            let ep = affine.transform_p(&i.end);
            draw::draw_line(sp[0].as_(), sp[1].as_(), ep[0].as_(), ep[1].as_());
        }
    }

    /// Draw voronoi vertices aka circle events.
    fn draw_vertices(&self, config: &SharedData, affine: &SimpleAffine) {
        let draw = |x: f64, y: f64| {
            draw::draw_circle(x, y, 1.0);
        };
        let draw_external = config.draw_flag.contains(DrawFilterFlag::EXTERNAL);
        let draw_site_vertex = config.draw_flag.contains(DrawFilterFlag::SITE_VERTEX);

        for it in self.diagram.vertices().iter().enumerate() {
            let vertex = it.1;

            if (!draw_site_vertex) && vertex.is_site_point() {
                continue;
            }
            if (!draw_external)
                && ColorFlag::from_bits(vertex.get_color())
                    .unwrap()
                    .contains(ColorFlag::EXTERNAL)
            {
                continue;
            }

            draw(
                affine.transform_x(vertex.x()),
                affine.transform_y(vertex.y()),
            );
        }
    }

    /// Draw voronoi edges.
    fn draw_edges(&self, config: &SharedData, affine: &SimpleAffine) -> Result<(), BvError> {
        let draw_external = config.draw_flag.contains(DrawFilterFlag::EXTERNAL);
        let draw_primary = config.draw_flag.contains(DrawFilterFlag::PRIMARY);
        let draw_secondary = config.draw_flag.contains(DrawFilterFlag::SECONDARY);
        let draw_curved = config.draw_flag.contains(DrawFilterFlag::CURVE);
        let draw_curved_as_line = config.draw_flag.contains(DrawFilterFlag::CURVE_LINE);
        let draw_infinite_edges = config.draw_flag.contains(DrawFilterFlag::INFINITE);

        let mut already_drawn = {
            let len = self.diagram.edges().len();
            let mut vb = vob::Vob::<u32>::new_with_storage_type(len);
            vb.resize(len, false);
            vb
        };

        for it in self.diagram.edges().iter() {
            draw::set_draw_color(enums::Color::DarkGreen);

            let edge = it;
            let edge_id = edge.id();
            if already_drawn.get(edge_id.usize()).unwrap_or(false) {
                //  the twin of this edge is already draw
                continue;
            }
            // no point in setting current edge as drawn, the edge id will not repeat
            // already_drawn.set_bit(edge_id.0, true);
            let twin = self.diagram.edge_get_twin(edge_id)?;
            if twin > edge_id {
                already_drawn.set(twin.usize(), true);
            }

            //#[allow(unused_assignments)]
            if (!draw_primary) && edge.is_primary() {
                continue;
            }
            if edge.is_secondary() && (!draw_secondary) {
                continue;
            }
            if ColorFlag::from_bits(edge.get_color())
                .unwrap()
                .contains(ColorFlag::INFINITE)
            {
                if !draw_infinite_edges {
                    continue;
                } else {
                    draw::set_draw_color(enums::Color::Green);
                }
            }

            if ColorFlag::from_bits(edge.get_color())
                .unwrap()
                .contains(ColorFlag::EXTERNAL)
            {
                if !draw_external {
                    continue;
                } else {
                    draw::set_draw_color(enums::Color::Green);
                }
            }

            // the coordinates in samples must be 'screen' coordinates, i.e. affine transformed
            let mut samples = Vec::<[f64; 2]>::new();
            if self.diagram.edge_is_infinite(edge_id)? {
                let a = self.clip_infinite_edge(affine, edge_id, &mut samples);
                if let Err(err) = a {
                    println!("Ignoring error : {:?}", err);
                }
            } else {
                // edge is finite, so vertex0 & vertex1 must exists -> unwrap is safe
                let vertex0 = self.diagram.vertex(edge.vertex0().unwrap())?;

                samples.push(affine.transform(vertex0.x(), vertex0.y()));
                let vertex1 = self.diagram.edge_get_vertex1(edge_id)?.unwrap();
                let vertex1 = self.diagram.vertex(vertex1).unwrap();

                samples.push(affine.transform(vertex1.x(), vertex1.y()));
                if edge.is_curved() {
                    if draw_curved_as_line {
                        for i in 0..samples.len() - 1 {
                            if let Ok(x1) = try_cast::<f64, i32>(samples[i][0]) {
                                if let Ok(y1) = try_cast::<f64, i32>(samples[i][1]) {
                                    if let Ok(x2) = try_cast::<f64, i32>(samples[i + 1][0]) {
                                        if let Ok(y2) = try_cast::<f64, i32>(samples[i + 1][1]) {
                                            draw::draw_line(x1, y1, x2, y2);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if draw_curved {
                        self.sample_curved_edge(affine, edge_id, &mut samples)?;
                    } else {
                        continue;
                    }
                }
            }
            if samples.len() > 1 {
                for i in 0..samples.len() - 1 {
                    let x1 = try_cast::<f64, i32>(samples[i][0]);
                    if x1.is_err() {
                        break;
                    }
                    let y1 = try_cast::<f64, i32>(samples[i][1]);
                    if y1.is_err() {
                        break;
                    }
                    let x2 = try_cast::<f64, i32>(samples[i + 1][0]);
                    if x2.is_err() {
                        break;
                    }
                    let y2 = try_cast::<f64, i32>(samples[i + 1][1]);
                    if y2.is_err() {
                        break;
                    }

                    draw::draw_line(x1.unwrap(), y1.unwrap(), x2.unwrap(), y2.unwrap());
                }
            }
        }
        Ok(())
    }

    fn clip_infinite_edge(
        &self,
        affine: &SimpleAffine,
        edge_id: EdgeIndex,
        clipped_edge: &mut Vec<[f64; 2]>,
    ) -> Result<(), BvError> {
        let edge = self.diagram.edge(edge_id)?;
        //const cell_type& cell1 = *edge.cell();
        let cell1_id = self.diagram.edge_get_cell(edge_id)?;
        let cell1 = self.diagram.cell(cell1_id)?;
        //const cell_type& cell2 = *edge.twin()->cell();
        let cell2_id = {
            let twin = self.diagram.edge_get_twin(edge_id)?;
            self.diagram.edge_get_cell(twin)?
        };
        let cell2 = self.diagram.cell(cell2_id)?;

        let mut origin = [0.0_f64, 0.0];
        let mut direction = [0.0_f64, 0.0];
        // Infinite edges could not be created by two segment sites.
        if cell1.contains_point() && cell2.contains_point() {
            let p1 = self.retrieve_point(cell1_id)?;
            let p2 = self.retrieve_point(cell2_id)?;
            origin[0] = (cast::<I, f64>(p1.x) + cast::<I, f64>(p2.x)) * 0.5;
            origin[1] = (cast::<I, f64>(p1.y) + cast::<I, f64>(p2.y)) * 0.5;
            direction[0] = cast::<I, f64>(p1.y) - cast::<I, f64>(p2.y);
            direction[1] = cast::<I, f64>(p2.x) - cast::<I, f64>(p1.x);
        } else {
            origin = if cell1.contains_segment() {
                let p = self.retrieve_point(cell2_id)?;
                [(p.x.as_()), (p.y.as_())]
            } else {
                let p = self.retrieve_point(cell1_id)?;
                [(p.x.as_()), (p.y.as_())]
            };
            let segment = if cell1.contains_segment() {
                self.retrieve_segment(cell1_id)?
            } else {
                self.retrieve_segment(cell2_id)?
            };
            let dx = segment.end.x - segment.start.x;
            let dy = segment.end.y - segment.start.y;
            if ([cast::<I, f64>(segment.start.x), segment.start.y.as_()] == origin)
                ^ cell1.contains_point()
            {
                direction[0] = dy.as_();
                direction[1] = (-dx).as_();
            } else {
                direction[0] = (-dy).as_();
                direction[1] = dx.as_();
            }
        }

        let side: f64 = {
            let high = affine.reverse_transform_fx(self.screen_aabb.get_high().unwrap()[0]);
            let low = affine.reverse_transform_fx(self.screen_aabb.get_low().unwrap()[0]);
            (high - low).abs()
        };
        // absolute value is taken in case the affine transform flips one coordinate
        let coefficient = side / (direction[0].abs().max(direction[1].abs()));

        if let Some(vertex0) = edge.vertex0() {
            let vertex0 = self.diagram.vertex(vertex0)?;
            clipped_edge.push([
                affine.transform_x(vertex0.x()),
                affine.transform_y(vertex0.y()),
            ]);
        } else {
            clipped_edge.push([
                affine.transform_x(origin[0] - direction[0] * coefficient),
                affine.transform_y(origin[1] - direction[1] * coefficient),
            ]);
        }

        if let Some(vertex1) = self.diagram.edge_get_vertex1(edge_id)? {
            let vertex1 = self.diagram.vertex(vertex1)?;
            clipped_edge.push([
                affine.transform_x(vertex1.x()),
                affine.transform_y(vertex1.y()),
            ]);
        } else {
            clipped_edge.push([
                affine.transform_x(origin[0] + direction[0] * coefficient),
                affine.transform_y(origin[1] + direction[1] * coefficient),
            ]);
        }
        Ok(())
    }

    /// Important: sampled_edge should contain both edge endpoints initially.
    /// sampled_edge should be 'screen' coordinates, i.e. affine transformed from voronoi output
    fn sample_curved_edge(
        &self,
        affine: &SimpleAffine,
        edge_id: EdgeIndex,
        sampled_edge: &mut Vec<[f64; 2]>,
    ) -> Result<(), BvError> {
        let max_dist = 1E-3
            * (self.screen_aabb.get_high().unwrap()[0] - self.screen_aabb.get_low().unwrap()[0]);

        let cell_id = self.diagram.edge_get_cell(edge_id)?;
        let cell = self.diagram.cell(cell_id)?;
        let twin_id = self.diagram.edge_get_twin(edge_id)?;
        let twin_cell_id = self.diagram.edge_get_cell(twin_id)?;

        let point = if cell.contains_point() {
            self.retrieve_point(cell_id)?
        } else {
            self.retrieve_point(twin_cell_id)?
        };
        let segment = if cell.contains_point() {
            self.retrieve_segment(twin_cell_id)?
        } else {
            self.retrieve_segment(cell_id)?
        };
        VoronoiVisualUtils::discretize::<I>(&point, segment, max_dist, affine, sampled_edge);
        Ok(())
    }

    /// Retrieves a point from the voronoi input in the order it was presented to
    /// the voronoi builder
    fn retrieve_point(&self, cell_id: CellIndex) -> Result<Point<I>, BvError> {
        let (index, cat) = self.diagram.cell(cell_id)?.source_index_2();

        match cat {
            SourceCategory::SinglePoint => {
                let idx = index.usize();
                if self.point_data_.len() < idx {
                    return Err(BvError::ValueError(
                        format!(
                            "point_data_.len():{:?} < index:{:?}",
                            self.point_data_.len(),
                            index
                        )
                        .to_string(),
                    ));
                }
                Ok(self.point_data_[idx])
            }
            SourceCategory::SegmentStart => {
                let iindex: usize = index.usize();
                if iindex < self.point_data_.len() {
                    return Err(BvError::ValueError(
                        format!("index out of range :{index:?} {}", self.point_data_.len())
                            .to_string(),
                    ));
                }
                Ok(self.segment_data_[index.usize() - self.point_data_.len()].start)
            }
            SourceCategory::Segment | SourceCategory::SegmentEnd => {
                let iindex = index.usize();
                if iindex < self.point_data_.len() {
                    return Err(BvError::ValueError(
                        format!("index out of range :{index:?} {}", self.point_data_.len())
                            .to_string(),
                    ));
                }
                Ok(self.segment_data_[iindex - self.point_data_.len()].end)
            }
        }
    }

    /// Retrieves a segment from the voronoi input in the order it was presented to
    /// the voronoi builder
    fn retrieve_segment(&self, cell_id: CellIndex) -> Result<&Line<I>, BvError> {
        let cell = self.diagram.cell(cell_id)?;
        let source_index: usize = cell.source_index().usize();
        if source_index < self.point_data_.len() {
            return Err(BvError::ValueError(
                format!(
                    "cell.source_index() out of range :{source_index} {} contains segment:{}",
                    self.point_data_.len(),
                    cell.contains_segment()
                )
                .to_string(),
            ));
        }

        Ok(&self.segment_data_[source_index - self.point_data_.len()])
    }

    #[allow(unused_assignments)]
    pub(crate) fn read_data(&mut self, example: Example) -> String {
        self.segment_data_.clear();
        self.point_data_.clear();
        self.diagram.clear();
        let mut rv = String::new();

        // Preparing Input Geometries.
        let (new_points, new_segments) = match example {
            Example::Simple => {
                rv = "Simple example".to_string();
                (
                    demo_data::NO_POINTS
                        .into_iter()
                        .map(|x| x.cast::<I>())
                        .collect(),
                    demo_data::SIMPLE_SEGMENTS
                        .into_iter()
                        .map(|l| Line::<i32>::from(l).cast::<I>())
                        .collect(),
                )
            }
            Example::Complex => {
                rv = "Rust logo".to_string();
                (
                    demo_data::NO_POINTS
                        .into_iter()
                        .map(|x| x.cast::<I>())
                        .collect(),
                    demo_data::COMPLEX_SEGMENTS
                        .into_iter()
                        .map(|l| Line::<i32>::from(l))
                        .map(|x| x.cast::<I>())
                        .collect(),
                )
            }
            Example::Clean => {
                rv = "Clean".to_string();
                (
                    demo_data::NO_POINTS
                        .into_iter()
                        .map(|x| x.cast::<I>())
                        .collect(),
                    demo_data::NO_SEGMENTS
                        .into_iter()
                        .map(|x| x.cast::<I>())
                        .collect(),
                )
            }
            Example::File => {
                let mut chooser = dialog::NativeFileChooser::new(dialog::FileDialogType::BrowseDir);

                let _ = chooser.set_directory(&std::path::PathBuf::from("examples"));
                chooser.set_title("select your input data");
                chooser.set_filter("*.txt");
                chooser.show();
                if let Some(filename) = chooser.filenames().first() {
                    if let Ok(file_parse_result) = read_boost_input_file::<I>(filename.as_path()) {
                        rv = filename.to_str().unwrap().to_string();
                        file_parse_result
                    } else {
                        rv = "Failed to read file".to_string();
                        (
                            demo_data::NO_POINTS
                                .into_iter()
                                .map(|x| x.cast::<I>())
                                .collect(),
                            demo_data::NO_SEGMENTS
                                .into_iter()
                                .map(|x| x.cast::<I>())
                                .collect(),
                        )
                    }
                } else {
                    rv = "Failed to read file".to_string();
                    (
                        demo_data::NO_POINTS
                            .into_iter()
                            .map(|x| x.cast::<I>())
                            .collect(),
                        demo_data::NO_SEGMENTS
                            .into_iter()
                            .map(|x| x.cast::<I>())
                            .collect(),
                    )
                }
            }
        };
        self.point_data_ = new_points;
        self.segment_data_ = new_segments;
        rv
    }

    #[inline(always)]
    /// converts from Line to geo::Line.
    fn line_i_to_f64(value: &Line<I>) -> geo::Line<f64> {
        let ps = geo::Coord {
            x: (value.start.x.as_()),
            y: (value.start.y.as_()),
        };
        let pe = geo::Coord {
            x: (value.end.x.as_()),
            y: (value.end.y.as_()),
        };
        geo::Line::<f64>::new(ps, pe)
    }
}
