use std::{
    ffi::CString,
    num::NonZeroU32,
};
use freya_engine::prelude::{
    backend_render_targets,
    direct_contexts,
    wrap_backend_render_target,
    ColorType,
    DirectContext,
    Format,
    FramebufferInfo,
    Interface,
    Surface as SkiaSurface,
    SurfaceOrigin,
};
use gl::{
    types::*,
    *,
};
use glutin::{
    config::{
        ConfigTemplateBuilder,
        GlConfig,
    },
    context::{
        ContextApi,
        ContextAttributesBuilder,
        GlProfile,
        NotCurrentGlContext,
        PossiblyCurrentContext,
    },
    display::{
        GetGlDisplay,
        GlDisplay,
    },
    prelude::{
        GlSurface,
        PossiblyCurrentGlContext,
    },
    surface::{
        Surface as GlutinSurface,
        SurfaceAttributesBuilder,
        SwapInterval,
        WindowSurface,
    },
};
use glutin_winit::DisplayBuilder;
use raw_window_handle::HasWindowHandle;
use winit::{
    dpi::PhysicalSize,
    event_loop::ActiveEventLoop,
    window::{
        Window,
        WindowAttributes,
    },
};
use crate::{
    size::WinitSize,
    LaunchConfig,
};
pub struct OpenGLDriver {
    pub(crate) gr_context: DirectContext,
    pub(crate) gl_surface: GlutinSurface<WindowSurface>,
    pub(crate) gl_context: PossiblyCurrentContext,
    pub(crate) fb_info: FramebufferInfo,
    pub(crate) num_samples: usize,
    pub(crate) stencil_size: usize,
}
impl Drop for OpenGLDriver {
    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 OpenGLDriver {
    pub fn new<State: Clone + 'static>(
        event_loop: &ActiveEventLoop,
        window_attributes: WindowAttributes,
        config: &LaunchConfig<State>,
    ) -> (Self, Window, SkiaSurface) {
        let template = ConfigTemplateBuilder::new()
            .with_alpha_size(8)
            .with_transparency(config.window_config.transparent);
        let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes));
        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 window = window.expect("Could not create window with OpenGL context");
        let window_handle = window.window_handle().unwrap();
        let context_attributes = ContextAttributesBuilder::new()
            .with_profile(GlProfile::Core)
            .build(Some(window_handle.as_raw()));
        let fallback_context_attributes = ContextAttributesBuilder::new()
            .with_profile(GlProfile::Core)
            .with_context_api(ContextApi::Gles(None))
            .build(Some(window_handle.as_raw()));
        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(
            window_handle.as_raw(),
            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");
        gl_surface
            .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
            .ok();
        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 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 size = window.inner_size();
        let mut gr_context =
            direct_contexts::make_gl(interface, None).expect("Could not create direct context");
        let render_target =
            backend_render_targets::make_gl(size.to_skia(), num_samples, stencil_size, fb_info);
        let skia_surface = wrap_backend_render_target(
            &mut gr_context,
            &render_target,
            SurfaceOrigin::BottomLeft,
            ColorType::RGBA8888,
            None,
            None,
        )
        .expect("Could not create skia surface");
        let driver = OpenGLDriver {
            gl_context,
            gl_surface,
            gr_context,
            num_samples,
            stencil_size,
            fb_info,
        };
        (driver, window, skia_surface)
    }
    pub fn make_current(&mut self) {
        self.gl_context.make_current(&self.gl_surface).unwrap();
    }
    pub fn resize(&mut self, size: PhysicalSize<u32>) -> (SkiaSurface, SkiaSurface) {
        let render_target = backend_render_targets::make_gl(
            size.to_skia(),
            self.num_samples,
            self.stencil_size,
            self.fb_info,
        );
        let mut surface = wrap_backend_render_target(
            &mut self.gr_context,
            &render_target,
            SurfaceOrigin::BottomLeft,
            ColorType::RGBA8888,
            None,
            None,
        )
        .expect("Could not create skia surface");
        let dirty_surface = surface.new_surface_with_dimensions(size.to_skia()).unwrap();
        self.gl_surface
            .resize(&self.gl_context, size.as_gl_width(), size.as_gl_height());
        (surface, dirty_surface)
    }
}