use super::background::BackgroundWidget;
use super::foreground::ForegroundWidget;
use super::WindowCoord;
use crate::coords::proj::Mercator;
use crate::coords::Mapper;
use femtovg::{Color, Paint, Path, FontId};
use gtk::glib;
use gtk::subclass::prelude::*;
use gtk::traits::{GLAreaExt, WidgetExt};
use ndarray::Array2;
use std::cell::RefCell;
use std::num::NonZeroU32;

#[derive(Debug, Default, Clone)]
pub struct RenderConfig {
    pub dim1: Option<Array2<f64>>,
    pub dim2: Option<Array2<f64>>,
    pub scale: f32,
    pub pointer_location: WindowCoord,
    pub transform: WindowCoord,
}

struct Fonts {
    sans: FontId,
    bold: FontId,
    light: FontId,
}

pub struct Render {
    pub(super) background: RefCell<BackgroundWidget>,
    pub(super) foreground: RefCell<ForegroundWidget>,
    pub config: RefCell<RenderConfig>,
    pub mapper: RefCell<Mapper>,
    canvas: RefCell<Option<femtovg::Canvas<femtovg::renderer::OpenGl>>>,
    canvas_width: RefCell<f32>,
    canvas_height: RefCell<f32>,
}

impl Default for Render {
    fn default() -> Self {
        Self {
            background: RefCell::new(BackgroundWidget::default()),
            foreground: RefCell::new(ForegroundWidget::default()),
            config: RefCell::new(RenderConfig::default()),
            mapper: RefCell::new(Mercator::new().into()),
            canvas: RefCell::new(None),
            canvas_height: RefCell::new(0.0),
            canvas_width: RefCell::new(0.0),
        }
    }
}

#[glib::object_subclass]
impl ObjectSubclass for Render {
    const NAME: &'static str = "MyRender";
    type Type = super::Render;
    type ParentType = gtk::GLArea;
}

// Trait shared by all GObjects
impl ObjectImpl for Render {
    fn constructed(&self) {
        self.parent_constructed();
        let area = self.obj();
        area.set_has_stencil_buffer(true);
    }
}

// Trait shared by all widgets
impl WidgetImpl for Render {}

impl GLAreaImpl for Render {
    fn resize(&self, width: i32, height: i32) {
        self.ensure_canvas();
        let mut canvas = self.canvas.borrow_mut();
        let canvas = canvas.as_mut().unwrap();
        canvas.set_size(
            width as u32,
            height as u32,
            self.obj().scale_factor() as f32,
        );
    }

    fn render(&self, context: &gtk::gdk::GLContext) -> bool {
        self.ensure_canvas();
        let mut canvas = self.canvas.borrow_mut();
        let canvas = canvas.as_mut().unwrap();

        let dpi = self.obj().scale_factor();
        let w = canvas.width();
        let h = canvas.height();
        let configs = self.config.borrow();

        canvas.clear_rect(
            0,
            0,
            (w as i32 * dpi) as u32,
            (h as i32 * dpi) as u32,
            Color::rgba(0, 0, 0, 255),
        );

        let mapper = self.mapper.borrow();
        let (lon_range, lat_range) = mapper.range.clone();

        {
            let background_widget = self.background.borrow_mut();
            background_widget.set_lat_lines(lat_range.key_points(9));
        }
        {
            let background_widget = self.background.borrow_mut();
            background_widget.set_lon_lines(lon_range.key_points(20));
        }

        self.foreground
            .borrow()
            .draw(canvas, configs.scale, dpi, self.mapper.borrow());

        self.background.borrow().draw(
            canvas,
            configs.scale,
            dpi,
            self.mapper.borrow(),
            (lon_range, lat_range),
        );

        canvas.flush();

        true
    }
}

impl Render {
    fn ensure_canvas(&self) {
        use femtovg::{renderer, Canvas};
        use glow::HasContext;

        if self.canvas.borrow().is_some() {
            return;
        }

        let widget = self.obj();
        widget.attach_buffers();

        static LOAD_FN: fn(&str) -> *const std::ffi::c_void =
            |s| epoxy::get_proc_addr(s) as *const _;
        // SAFETY: Need to get the framebuffer id that gtk expects us to draw into, so femtovg
        // knows which framebuffer to bind. This is safe as long as we call attach_buffers
        // beforehand. Also unbind it here just in case, since this can be called outside render.
        let (mut renderer, fbo) = unsafe {
            let renderer =
                renderer::OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer");
            let ctx = glow::Context::from_loader_function(LOAD_FN);
            let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32)
                .expect("No GTK provided framebuffer binding");
            ctx.bind_framebuffer(glow::FRAMEBUFFER, None);
            (renderer, glow::NativeFramebuffer(id))
        };
        renderer.set_screen_target(Some(fbo));
        let mut canvas = Canvas::new(renderer).expect("Cannot create canvas");
        canvas.add_font_dir("/Users/tsuki/projects/radar-g/src/assets").unwrap();
        self.canvas.replace(Some(canvas));
    }
}
