mod cms;
mod exterior;
mod imp;
mod interior;
pub mod predefined;
mod renders;
use self::cms::CMS;
pub use self::imp::{RenderConfig, RenderMotion, RenderStatus};
use crate::coords::Mapper;
use crate::RUNTIME;
use adw::prelude::{GLAreaExt, GestureDragExt};
use geo_types::LineString;
use glib::clone;
pub use glib::subclass::prelude::*;
use gtk::traits::WidgetExt;
use gtk::{EventControllerScrollFlags, Inhibit};
pub use interior::{Layer, LayerImpl, Target};
use relm4::once_cell::sync::Lazy;
use std::cell::{Ref, RefCell, RefMut};
use std::sync::{Arc, Mutex};
use tokio::runtime::Runtime;

pub(super) type WindowCoord = (f32, f32);

glib::wrapper! {
    pub struct Render(ObjectSubclass<imp::Render>)
    @extends gtk::GLArea, gtk::Widget;
}

impl Default for Render {
    fn default() -> Self {
        Self::new(None, RenderConfig { padding: [10.0; 4] })
    }
}

impl Render {
    pub fn new(mapper: Option<Mapper>, cfg: RenderConfig) -> Self {
        let this: Self = glib::Object::new();
        {
            let mut status = this.imp().status.borrow_mut();
            status.scale = 1.0;
        }
        this.imp().config.replace(cfg);
        if let Some(mapper) = mapper {
            this.set_mapper(mapper);
        }

        let pointer_location_detecture = gtk::EventControllerMotion::new();
        pointer_location_detecture.connect_motion(
            clone!( @weak this as r => move |_context, x, y| {
                    r.update_status(|s| {
                        let dpi = r.scale_factor();
                        let (_,h) = s.window_size;
                        s.pointer_location = (x as f32 * dpi as f32, h as f32 - y as f32 * dpi as f32);
                    });
                }
            ),
        );

        let scale_detecture = gtk::EventControllerScroll::new(EventControllerScrollFlags::VERTICAL);
        scale_detecture.connect_scroll(clone!(
                @weak this as r  => @default-panic,move |_context, _x, y| {
                    r.update_status(|status|{
                        status.scale = y as f32;
                        status.motion = RenderMotion::Scale;
                    });
                    r.queue_render();
                    Inhibit(false)
                }
        ));

        let drag_detecture = gtk::GestureDrag::new();
        drag_detecture.connect_drag_update(clone!(
        @weak this as r => move |this, _, _| {
            let (ox, oy) = this.offset().unwrap_or((0.0,0.0));
            r.update_status(|s| {
                s.translate = Some((-ox as f32 * 1.35, oy as f32 * 1.35));
                s.motion = RenderMotion::Translate;
            });
            r.queue_render();
        }));

        drag_detecture.connect_drag_end(clone!(
            @weak this as r => move |_,_,_|{
                r.update_status(|cfg| {
                    cfg.translate = None;
                    cfg.motion = RenderMotion::Translate;
                })
            }
            r.queue_render();
        ));

        this.set_hexpand(true);
        this.set_vexpand(true);
        this.add_controller(pointer_location_detecture);
        this.add_controller(scale_detecture);
        this.add_controller(drag_detecture);

        this
    }

    pub fn change_cfg<F>(&self, mut f: F)
    where
        F: FnMut(&mut RenderConfig),
    {
        let mut cfg = self.imp().config.borrow_mut();
        f(&mut cfg);
    }

    pub fn get_canvas(&self) -> RefMut<'_, Option<femtovg::Canvas<femtovg::renderer::OpenGl>>> {
        self.imp().canvas.borrow_mut()
    }

    pub fn set_cfg(&self, cfg: RenderConfig) {
        self.imp().config.replace(cfg);
    }

    pub fn update_status<F>(&self, mut f: F)
    where
        F: FnMut(&mut RenderStatus),
    {
        let mut status = self.imp().status.borrow_mut();
        f(&mut status);
    }

    pub fn set_mapper(&self, mapper: Mapper) {
        self.imp().mapper.replace(mapper);
    }

    pub fn set_interior_layers(&self, layers: Vec<Layer>) {
        self.imp().interior_layers.replace(layers);
        self.queue_render();
    }

    pub fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> {
        let foremapped = self.get_mapper().map(loc).unwrap();
        return self.imp().map(foremapped);
    }

    pub fn inverse_map(&self, loc: (f32, f32)) -> Option<(f64, f64)> {
        let foremapped = self.imp().inverse_map(loc);
        foremapped.and_then(|foremapped| self.get_mapper().inverse_map(foremapped).ok())
    }

    pub fn ring_map(&self, ring: &LineString<f64>) -> Option<LineString<f32>> {
        Some(
            ring.points()
                .into_iter()
                .map(|p| self.map((p.x(), p.y())))
                .filter(|p| p.is_some())
                .collect::<Option<Vec<_>>>()
                .unwrap()
                .into(),
        )
    }

    pub fn point_in_bound(&self, loc: (f64, f64)) -> bool {
        self.get_mapper().point_in_bound(loc)
    }

    fn get_mapper(&self) -> Ref<Mapper> {
        self.imp().mapper.borrow()
    }

    pub fn window_size(&self) -> (i32, i32) {
        self.imp().window_size().unwrap()
    }

    pub fn create_drawer<F>(&self, range: (f64, f64, f64, f64), window_size: (f32, f32), mut f: F)
    where
        F: FnMut(&CMS),
    {
        let (lon1, lon2, lat1, lat2) = range;
        let mut mapper = self.get_mapper().clone();
        mapper.set_lat_range(lat1..lat2);
        mapper.set_lon_range(lon1..lon2);
        let cms = CMS::new(mapper, window_size);
        f(&cms);
    }

    pub fn pointer_loc(&self, transed: bool) -> (f64, f64) {
        let raw = self.imp().status.borrow().pointer_location.clone();
        if transed {
            self.inverse_map(raw).unwrap()
        } else {
            (raw.0 as f64, raw.1 as f64)
        }
    }

    pub fn render_range(&self) -> ((f64, f64), (f64, f64)) {
        self.imp().window_range().unwrap()
    }

    pub fn create_cms(&self) -> CMS {
        let borrowed_mapper = &*self.imp().mapper.borrow();
        let new_mapper = borrowed_mapper.clone();
        CMS::new(new_mapper, (500.0, 500.0))
    }
}
