mod imp;
use crate::coords::{Mapper, Range};
use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path};
use geo_types::{line_string, LineString, Point};
use glib::subclass::types::ObjectSubclassIsExt;
use std::cell::Ref;

pub use self::imp::BackgroundConfig;

glib::wrapper! {
    pub struct BackgroundWidget(ObjectSubclass<imp::BackgroundWidget>);
}

impl Default for BackgroundWidget {
    fn default() -> Self {
        Self::new(BackgroundConfig::default())
    }
}

impl BackgroundWidget {
    pub fn new(config: BackgroundConfig) -> Self {
        let this: Self = glib::Object::new();
        let imp = this.imp();
        imp.config.replace(config);
        this
    }

    fn draw_lines<V: IntoIterator<Item = LineString<f32>>>(
        &self,
        canvas: &mut Canvas<OpenGl>,
        line_painter: &Paint,
        p: V,
    ) {
        p.into_iter().for_each(|line| {
            let mut path = Path::new();
            let points: Vec<Point<f32>> = line.points().collect();
            path.move_to(points[0].x() as f32, points[0].y() as f32);
            points[1..].into_iter().for_each(|p| {
                path.line_to(p.x() as f32, p.y() as f32);
            });
            canvas.stroke_path(&mut path, line_painter);
        });
    }

    fn mesh_lines(
        &self,
        canvas: &mut Canvas<OpenGl>,
        canvas_width: f32,
        canvas_height: f32,
        mapper: Ref<'_, Mapper>,
        bound: (Range, Range),
    ) {
        let imp = self.imp();
        let line_painter = &imp.config.borrow().painter;
        let (left, right) = (bound.0 .0, bound.0 .1);
        let (bottom, top) = (bound.1 .0, bound.1 .1);
        let config = imp.config.borrow();

        if config.show_lat_lines {
            let r = config.lat_lines.iter().map(|lat| {
                let line = LineString::new(vec![(left, *lat).into(), (right, *lat).into()]);
                let result = mapper.map_line(&line).unwrap();
                LineString::new(
                    result
                        .points()
                        .map(|p| (p.x() as f32 * canvas_width, p.y() as f32 * canvas_height).into())
                        .collect(),
                )
            });

            self.draw_lines(canvas, line_painter, r);
        }

        if config.show_lat_lines {
            config.lat_lines.iter().for_each(|lat| {
                let mut paint = Paint::color(Color::white());
                paint.set_font_size(25.0);
                paint.set_line_width(1.0);
                let text_location = mapper.map((left, *lat)).unwrap();

                let _ = canvas.stroke_text(
                    text_location.0 as f32 * canvas_width,
                    text_location.1 as f32 * canvas_height,
                    format!("{:.2} N", lat),
                    &paint,
                );
            });
        }

        if config.show_lon_lines {
            config.lon_lines.iter().for_each(|lon| {
                let mut paint = Paint::color(Color::white());
                paint.set_font_size(25.0);
                paint.set_line_width(1.0);
                let text_location = mapper.map((*lon, top + 0.1)).unwrap();

                let _ = canvas.stroke_text(
                    text_location.0 as f32 * canvas_width,
                    text_location.1 as f32 * canvas_height,
                    format!("{:.2}", lon),
                    &paint,
                );
            });
        }

        if imp.config.borrow().show_lon_lines {
            let r = config.lon_lines.iter().map(|lon| {
                let line = LineString::new(vec![(*lon, bottom).into(), (*lon, top).into()]);
                let result = mapper.map_line(&line).unwrap();
                LineString::new(
                    result
                        .points()
                        .map(|p| (p.x() as f32 * canvas_width, p.y() as f32 * canvas_height).into())
                        .collect(),
                )
            });

            self.draw_lines(canvas, line_painter, r);
        }
    }

    pub fn draw(
        &self,
        canvas: &mut Canvas<OpenGl>,
        scale: f32,
        dpi: i32,
        mapper: Ref<'_, Mapper>,
        bound: (Range, Range),
    ) {
        let canvas_width = canvas.width();
        let canvas_height = canvas.height();

        let config = self.imp().config.borrow();

        self.mesh_lines(canvas, canvas_width, canvas_height, mapper, bound);
    }

    pub fn set_lat_lines(&self, lat_lines: Vec<f64>) {
        println!("set lat lines {:?}", lat_lines);
        let imp = self.imp();
        imp.config.borrow_mut().lat_lines = lat_lines;
    }

    pub fn set_lon_lines(&self, lon_lines: Vec<f64>) {
        println!("set lon lines {:?}", lon_lines);
        let imp = self.imp();
        imp.config.borrow_mut().lon_lines = lon_lines;
    }
}
