use freya_common::EventMessage;
use freya_engine::prelude::*;
use gl::{types::*, *};
use glutin::context::GlProfile;
use glutin::context::NotCurrentGlContext;
use glutin::prelude::PossiblyCurrentGlContext;
use glutin::{
    config::{ConfigTemplateBuilder, GlConfig},
    context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext},
    display::{GetGlDisplay, GlDisplay},
    prelude::GlSurface,
    surface::{Surface as GlutinSurface, SurfaceAttributesBuilder, WindowSurface},
};
use glutin_winit::DisplayBuilder;
use raw_window_handle::HasRawWindowHandle;
use std::ffi::CString;
use std::num::NonZeroU32;
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::{
    event_loop::EventLoop,
    window::{Window, WindowBuilder},
};
use crate::config::WindowConfig;
pub struct WindowEnv<State: Clone> {
    pub(crate) gr_context: DirectContext,
    pub(crate) surface: Surface,
    pub(crate) gl_surface: GlutinSurface<WindowSurface>,
    pub(crate) gl_context: PossiblyCurrentContext,
    pub(crate) window: Window,
    pub(crate) fb_info: FramebufferInfo,
    pub(crate) num_samples: usize,
    pub(crate) stencil_size: usize,
    pub(crate) window_config: WindowConfig<State>,
}
impl<T: Clone> Drop for WindowEnv<T> {
    fn drop(&mut self) {
        if !self.gl_context.is_current() && self.gl_context.make_current(&self.gl_surface).is_err()
        {
            self.gr_context.abandon();
        }
    }
}
impl<T: Clone> WindowEnv<T> {
    pub fn new(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 {
            window_builder = (with_window_builder)(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");
        window.set_ime_allowed(true);
        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 canvas(&mut self) -> &Canvas {
        self.surface.canvas()
    }
    pub fn clear(&mut self) {
        let canvas = self.surface.canvas();
        canvas.clear(self.window_config.background);
    }
    pub fn finish_render(&mut self) {
        self.window.pre_present_notify();
        self.gr_context.flush_and_submit();
        self.gl_surface.swap_buffers(&self.gl_context).unwrap();
    }
    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)(&mut 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)(&mut 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")
}