mod x11;
use crate::*;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use winit::dpi::{Size, PhysicalSize};
use winit::event_loop::EventLoopWindowTarget;
use winit::window::{WindowId, Window as WinitWindow, WindowBuilder as WinitWindowBuilder};
// use raw_gl_context::{GlConfig, GlContext};

pub struct Window {
    pub controller: Box<dyn WindowController>,
    cursor_position: Option<Point>,
    winit_window: WinitWindow,
    buffer: Buffer,
    dimensions: Dimensions,
    /// The most recent render request for this window.
    render_hint: RenderHint,
    graphics_context: Box<dyn GraphicsContext>,
    // gl_context: GlContext,
}

impl Window {
    pub unsafe fn new(event_loop: &EventLoopWindowTarget<()>, controller: Box<dyn WindowController>) -> Self {
        let mut builder = WinitWindowBuilder::new();
        builder = builder.with_resizable(controller.resizable());
        builder = builder.with_inner_size({
            let dim = controller.initial_dimensions();
            Size::Physical(PhysicalSize::new(dim.width, dim.height))
        });
        if let Some(dim) = controller.minimum_dimensions() {
            let size = Size::Physical(PhysicalSize { width: dim.width, height: dim.height });
            builder = builder.with_min_inner_size(size);
        }
        if let Some(dim) = controller.maximum_dimensions() {
            let size = Size::Physical(PhysicalSize { width: dim.width, height: dim.height });
            builder = builder.with_max_inner_size(size);
        }
        let winit_window = builder.build(event_loop).unwrap();

        let graphics_context: Box<dyn GraphicsContext> = match winit_window.raw_window_handle() {
            RawWindowHandle::Xlib(xlib_handle) => Box::new(x11::X11GraphicsContext::new(xlib_handle)),
            _ => panic!("Unknown window handle type"),
        };
        // let gl_context = GlContext::create(&winit_window, GlConfig::default()).unwrap();
        // gl_context.make_current();
        // gl::load_with(|symbol| {
        //     println!("Loaded '{}'", symbol);
        //     gl_context.get_proc_address(symbol) as *const _
        // });
        Self {
            winit_window,
            controller,
            graphics_context,
            render_hint: RenderHint::Redraw,
            // gl_context,
            buffer: Buffer::new(Dimensions::ZERO),
            dimensions: Dimensions::ZERO,
            cursor_position: None,
        }
    }

    pub fn id(&self) -> WindowId {
        self.winit_window.id()
    }

    pub fn set_minimum_dimensions(&mut self, dimensions: Option<Dimensions>) {
        self.winit_window.set_min_inner_size(dimensions.map(|dim| {
            Size::Physical(PhysicalSize { width:dim.width, height:dim.height })
        }))
    }
    pub fn set_maximum_dimensions(&mut self, dimensions: Option<Dimensions>) {
        self.winit_window.set_max_inner_size(dimensions.map(|dim| {
            Size::Physical(PhysicalSize { width:dim.width, height:dim.height })
        }))
    }
    pub fn set_title(&mut self, title: &str) {
        self.winit_window.set_title(title);
    }

    /// Call to update the frame buffer to the new size of the window.
    pub fn resize_buffer(&mut self, dimensions: Dimensions) {
        if self.dimensions == dimensions { return }
        self.dimensions = dimensions;
        self.buffer.resize(dimensions);
        self.controller.on_resize(dimensions);
        self.render_hint = RenderHint::Redraw;
    }

    pub fn set_cursor_icon(&mut self, icon: Option<CursorIcon>) {
        match icon {
            Some(icon) => self.winit_window.set_cursor_icon(icon),
            None => self.winit_window.set_cursor_icon(CursorIcon::Default),
        };
    }

    /// Call this after a mouse click so that the cursor-hovering callbacks are
    /// rerun. This is useful where a click changes the UI layout and a new
    /// element that has an on-hover effect appears beneath the cursor.
    pub fn bump_mouse(&mut self) {
        if let Some(position) = self.cursor_position {
            self.controller.on_mouse_move(position)
        }
    }

    pub fn move_mouse(&mut self, position: Point) {
        if self.cursor_position != Some(position) {
            self.cursor_position = Some(position);
            self.controller.on_mouse_move(position);
        }
    }

    pub fn check_render_request(&mut self) {
        if let RenderRequest::Render(hint) = self.controller.render_request() {
            self.render_hint &= hint;
            self.winit_window.request_redraw();
        }
    }

    pub fn render(&mut self) {
        self.controller.render(&mut self.buffer, self.render_hint);
        unsafe { self.graphics_context.blit(&self.buffer); }
        // Reset the render_hint back to the lowest variant.
        self.render_hint = RenderHint::Update;
    }

    // pub fn render_gl(&mut self) {
    //     self.gl_context.make_current();
    //     self.controller.render_gl(&mut self.gl_context);
    //     self.gl_context.swap_buffers();
    //     self.gl_context.make_not_current();
    // }
}

trait GraphicsContext {
    /// Fill the graphics context with the contents of the provided buffer.
    unsafe fn blit(&mut self, buffer: &Buffer);
}