use crate::coords::Range;
use std::borrow::BorrowMut;
use std::path::Path;

use crate::coords::Mapper;
use crate::render::{Layer, LayerImpl, Render};
use femtovg::renderer::OpenGl;
use femtovg::{Canvas, Paint};
use geo_types::LineString;
use geojson::GeoJson;

impl Layer {
    pub fn map_layer_with_geojson_by_path(path: impl AsRef<Path>) -> Self {
        let geojson = std::fs::read_to_string(path).unwrap();
        let json = geojson.parse::<GeoJson>().unwrap();
        Self::map_layer_with_geojson(json)
    }

    pub fn map_layer_with_geojson(json: GeoJson) -> Self {
        let mut layer = Layer::new(
            true,
            |s, render, _| {
                if let Some(renderer) = s.get_imp().as_ref() {
                    renderer.draw(render.clone());
                }
            },
            "Map".into(),
            Some(|_, _| async {}),
            Some(GeoJsonMapImpl::new(json)),
        );
        layer
    }

    pub fn meshgrid_layer() -> Self {
        let mut layer = Layer::new(
            true,
            |s, render, _| {
                if let Some(renderer) = s.get_imp().as_ref() {
                    renderer.draw(render.clone());
                }
            },
            "MeshGrid".into(),
            Some(|_, _| async {}),
            Some(MeshGridImpl),
        );

        layer
    }
}

#[derive(Debug)]
struct GeoJsonMapImpl {
    geojson: GeoJson,
}

impl GeoJsonMapImpl {
    fn new(json: GeoJson) -> Self {
        Self { geojson: json }
    }
}

impl LayerImpl for GeoJsonMapImpl {
    fn draw(&self, render: Render) -> Option<crate::render::Target> {
        let paint = Paint::color(femtovg::Color::rgb(255, 255, 255));
        let geojson = &self.geojson;

        let mut canvas = render.get_canvas();
        let canvas = canvas.as_mut().unwrap();

        if let GeoJson::FeatureCollection(ref feature_collection) = geojson {
            for feature in &feature_collection.features {
                feature
                    .geometry
                    .as_ref()
                    .iter()
                    .for_each(|geometry| match geometry.value {
                        geojson::Value::Polygon(ref polygon) => {
                            let mut path = femtovg::Path::new();
                            let polygon = &polygon[0];
                            for (i, point) in polygon.iter().enumerate() {
                                let _point = (point[0], point[1]);
                                if render.point_in_bound(_point) {
                                    let (x, y) = render.map(_point).unwrap();
                                    if i == 0 {
                                        path.move_to(x, y);
                                    } else {
                                        path.line_to(x, y);
                                    }
                                }
                            }
                        }
                        geojson::Value::MultiPolygon(ref multi_polygon) => {
                            let mut path = femtovg::Path::new();
                            for polygon in multi_polygon {
                                let out_ring = &polygon[0];
                                let out_ring_line: LineString = out_ring
                                    .iter()
                                    .map(|x| (x[0], x[1]))
                                    .collect::<Vec<_>>()
                                    .into();
                                let out_ring = render.ring_map(&out_ring_line).unwrap();

                                for (i, point) in out_ring.points().enumerate() {
                                    let (x, y) = (point.x(), point.y());
                                    if i == 0 {
                                        path.move_to(x, y);
                                    } else {
                                        path.line_to(x, y);
                                    }
                                }
                            }
                            canvas.stroke_path(&mut path, &paint);
                        }
                        _ => println!("Unknown geometry type: {:?}", geometry.value),
                    });
            }
        }
        None
    }
}

#[derive(Debug)]
struct MeshGridImpl;

impl LayerImpl for MeshGridImpl {
    fn draw(&self, render: Render) -> Option<crate::render::Target> {
        let mut canvas = render.get_canvas();
        let canvas = canvas.as_mut().unwrap();

        let (lon_range, lat_range) = render.render_range();
        let (lon_range, lat_range): (Range, Range) = (lon_range.into(), lat_range.into());

        let lon_keypoints = lon_range.key_points(10);
        let lat_keypoints = lat_range.key_points(5);

        for lon in lon_keypoints.iter() {
            let mut path = femtovg::Path::new();
            render
                .map((*lon, lat_range.0))
                .map(|(x, y)| path.move_to(x, y));
            render
                .map((*lon, lat_range.1))
                .map(|(x, y)| path.line_to(x, y));
            canvas.stroke_path(&mut path, &Paint::color(femtovg::Color::rgb(255, 255, 255)));
        }
        for lat in lat_keypoints.iter() {
            let mut path = femtovg::Path::new();
            render
                .map((lon_range.0, *lat))
                .map(|(x, y)| path.move_to(x, y));
            render
                .map((lon_range.1, *lat))
                .map(|(x, y)| path.line_to(x, y));
            canvas.stroke_path(&mut path, &Paint::color(femtovg::Color::rgb(255, 255, 255)));
        }

        None
    }
}
