use dioxus_native_core::NodeId;
use freya_common::EventMessage;
use freya_core::prelude::*;
use freya_dom::prelude::FreyaDOM;
use freya_engine::prelude::*;
use glutin::prelude::{PossiblyCurrentContextGlSurfaceAccessor, PossiblyCurrentGlContext};
use std::ffi::CString;
use std::num::NonZeroU32;
use torin::geometry::{Area, Size2D};
use gl::{types::*, *};
use glutin::context::GlProfile;
use glutin::{
    config::{ConfigTemplateBuilder, GlConfig},
    context::{
        ContextApi, ContextAttributesBuilder, NotCurrentGlContextSurfaceAccessor,
        PossiblyCurrentContext,
    },
    display::{GetGlDisplay, GlDisplay},
    prelude::GlSurface,
    surface::{Surface as GlutinSurface, SurfaceAttributesBuilder, WindowSurface},
};
use glutin_winit::DisplayBuilder;
use raw_window_handle::HasRawWindowHandle;
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::{
    event_loop::EventLoop,
    window::{Window, WindowBuilder},
};
use crate::config::WindowConfig;
use crate::renderer::render_skia;
use crate::HoveredNode;
pub struct WindowEnv<T: Clone> {
    gr_context: DirectContext,
    surface: Surface,
    gl_surface: GlutinSurface<WindowSurface>,
    gl_context: PossiblyCurrentContext,
    pub(crate) window: Window,
    fb_info: FramebufferInfo,
    num_samples: usize,
    stencil_size: usize,
    pub(crate) window_config: WindowConfig<T>,
}
impl<T: Clone> Drop for WindowEnv<T> {
    fn drop(&mut self) {
        if !self.gl_context.is_current() {
            if self.gl_context.make_current(&self.gl_surface).is_err() {
                self.gr_context.abandon();
            }
        }
    }
}
impl<T: Clone> WindowEnv<T> {
    pub fn from_config(
        mut window_config: WindowConfig<T>,
        event_loop: &EventLoop<EventMessage>,
    ) -> Self {
        let mut window_builder = WindowBuilder::new()
            .with_visible(false)
            .with_title(window_config.title)
            .with_decorations(window_config.decorations)
            .with_transparent(window_config.transparent)
            .with_window_icon(window_config.icon.take())
            .with_inner_size(LogicalSize::<f64>::new(
                window_config.width,
                window_config.height,
            ));
        if let Some(min_size) = window_config.min_width.zip(window_config.min_height) {
            window_builder = window_builder.with_min_inner_size(LogicalSize::<f64>::from(min_size))
        }
        if let Some(max_size) = window_config.max_width.zip(window_config.max_height) {
            window_builder = window_builder.with_max_inner_size(LogicalSize::<f64>::from(max_size))
        }
        if let Some(with_window_builder) = &window_config.window_builder_hook {
            (with_window_builder)(&mut window_builder);
        }
        let template = ConfigTemplateBuilder::new()
            .with_alpha_size(8)
            .with_transparency(window_config.transparent);
        let display_builder = DisplayBuilder::new().with_window_builder(Some(window_builder));
        let (window, gl_config) = display_builder
            .build(event_loop, template, |configs| {
                configs
                    .reduce(|accum, config| {
                        let transparency_check = config.supports_transparency().unwrap_or(false)
                            & !accum.supports_transparency().unwrap_or(false);
                        if transparency_check || config.num_samples() < accum.num_samples() {
                            config
                        } else {
                            accum
                        }
                    })
                    .unwrap()
            })
            .unwrap();
        let mut window = window.expect("Could not create window with OpenGL context");
        let raw_window_handle = window.raw_window_handle();
        let context_attributes = ContextAttributesBuilder::new()
            .with_profile(GlProfile::Core)
            .build(Some(raw_window_handle));
        let fallback_context_attributes = ContextAttributesBuilder::new()
            .with_profile(GlProfile::Core)
            .with_context_api(ContextApi::Gles(None))
            .build(Some(raw_window_handle));
        let not_current_gl_context = unsafe {
            gl_config
                .display()
                .create_context(&gl_config, &context_attributes)
                .unwrap_or_else(|_| {
                    gl_config
                        .display()
                        .create_context(&gl_config, &fallback_context_attributes)
                        .expect("failed to create context")
                })
        };
        let (width, height): (u32, u32) = window.inner_size().into();
        let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
            raw_window_handle,
            NonZeroU32::new(width).unwrap(),
            NonZeroU32::new(height).unwrap(),
        );
        let gl_surface = unsafe {
            gl_config
                .display()
                .create_window_surface(&gl_config, &attrs)
                .expect("Could not create gl window surface")
        };
        let gl_context = not_current_gl_context
            .make_current(&gl_surface)
            .expect("Could not make GL context current when setting up skia renderer");
        load_with(|s| {
            gl_config
                .display()
                .get_proc_address(CString::new(s).unwrap().as_c_str())
        });
        let interface = Interface::new_load_with(|name| {
            if name == "eglGetCurrentDisplay" {
                return std::ptr::null();
            }
            gl_config
                .display()
                .get_proc_address(CString::new(name).unwrap().as_c_str())
        })
        .expect("Could not create interface");
        let mut gr_context =
            DirectContext::new_gl(Some(interface), None).expect("Could not create direct context");
        let fb_info = {
            let mut fboid: GLint = 0;
            unsafe { GetIntegerv(FRAMEBUFFER_BINDING, &mut fboid) };
            FramebufferInfo {
                fboid: fboid.try_into().unwrap(),
                format: Format::RGBA8.into(),
                ..Default::default()
            }
        };
        let num_samples = gl_config.num_samples() as usize;
        let stencil_size = gl_config.stencil_size() as usize;
        let mut surface = create_surface(
            &mut window,
            fb_info,
            &mut gr_context,
            num_samples,
            stencil_size,
        );
        let sf = window.scale_factor() as f32;
        surface.canvas().scale((sf, sf));
        WindowEnv {
            surface,
            gl_surface,
            gl_context,
            gr_context,
            fb_info,
            num_samples,
            stencil_size,
            window,
            window_config,
        }
    }
    pub fn process_layout(
        &mut self,
        rdom: &FreyaDOM,
        font_collection: &mut FontCollection,
    ) -> (Layers, Viewports) {
        let window_size = self.window.inner_size();
        let scale_factor = self.window.scale_factor() as f32;
        process_layout(
            rdom,
            Area::from_size(Size2D::from((
                window_size.width as f32,
                window_size.height as f32,
            ))),
            font_collection,
            scale_factor,
        )
    }
    pub fn canvas(&mut self) -> &Canvas {
        self.surface.canvas()
    }
    pub fn start_render(
        &mut self,
        layers: &Layers,
        viewports: &Viewports,
        font_collection: &mut FontCollection,
        hovered_node: &HoveredNode,
        rdom: &FreyaDOM,
    ) {
        let canvas = self.surface.canvas();
        canvas.clear(self.window_config.background);
        let mut matrices: Vec<(Matrix, Vec<NodeId>)> = Vec::default();
        let mut opacities: Vec<(f32, Vec<NodeId>)> = Vec::default();
        process_render(
            viewports,
            rdom,
            font_collection,
            layers,
            &mut (canvas, &mut matrices, &mut opacities),
            |dom, node_id, area, font_collection, viewports, (canvas, matrices, opacities)| {
                let render_wireframe = if let Some(hovered_node) = &hovered_node {
                    hovered_node
                        .lock()
                        .unwrap()
                        .map(|id| id == *node_id)
                        .unwrap_or_default()
                } else {
                    false
                };
                if let Some(dioxus_node) = dom.rdom().get(*node_id) {
                    render_skia(
                        canvas,
                        area,
                        &dioxus_node,
                        font_collection,
                        viewports,
                        render_wireframe,
                        matrices,
                        opacities,
                    );
                }
            },
        );
    }
    pub fn finish_render(&mut self) {
        self.gr_context.flush_and_submit();
        self.gl_surface.swap_buffers(&self.gl_context).unwrap();
    }
    pub fn window(&mut self) -> &mut Window {
        &mut self.window
    }
    pub fn resize(&mut self, size: PhysicalSize<u32>) {
        self.surface = create_surface(
            &mut self.window,
            self.fb_info,
            &mut self.gr_context,
            self.num_samples,
            self.stencil_size,
        );
        let (width, height): (u32, u32) = size.into();
        self.gl_surface.resize(
            &self.gl_context,
            NonZeroU32::new(width.max(1)).unwrap(),
            NonZeroU32::new(height.max(1)).unwrap(),
        );
        self.window.request_redraw();
    }
    pub fn run_on_setup(&mut self) {
        let on_setup = self.window_config.on_setup.clone();
        if let Some(on_setup) = on_setup {
            (on_setup)(self.window())
        }
    }
    pub fn run_on_exit(&mut self) {
        let on_exit = self.window_config.on_exit.clone();
        if let Some(on_exit) = on_exit {
            (on_exit)(self.window())
        }
    }
}
fn create_surface(
    window: &mut Window,
    fb_info: FramebufferInfo,
    gr_context: &mut DirectContext,
    num_samples: usize,
    stencil_size: usize,
) -> Surface {
    let size = window.inner_size();
    let size = (
        size.width.try_into().expect("Could not convert width"),
        size.height.try_into().expect("Could not convert height"),
    );
    let backend_render_target =
        backend_render_targets::make_gl(size, num_samples, stencil_size, fb_info);
    wrap_backend_render_target(
        gr_context,
        &backend_render_target,
        SurfaceOrigin::BottomLeft,
        ColorType::RGBA8888,
        None,
        None,
    )
    .expect("Could not create skia surface")
}