use std::sync::{Arc, Mutex};

use crate::{
    error::Error,
    event::{EventData, KeyEvent, KeyInput, WindowEvent},
    message::{ClientMessage, CreateWindowInfo},
    surface::{MappedSurface, Surface},
};

use super::{connection::Connection, Application};

pub trait OnCloseRequested = Fn() -> EventOutcome;
pub trait OnKeyInput = Fn(KeyInput) -> EventOutcome;
pub trait OnResized = Fn(u32, u32) -> EventOutcome;
pub trait OnFocusChanged = Fn(bool) -> EventOutcome;
pub trait OnKeyPressed = Fn(KeyEvent) -> EventOutcome;
pub trait OnKeyReleased = Fn(KeyEvent) -> EventOutcome;

pub trait OnRedrawRequested = Fn(&mut MappedSurface);

#[derive(Debug, PartialEq, Eq)]
pub enum EventOutcome {
    None,
    Redraw,
    Destroy,
}

pub struct Window {
    connection: Arc<Mutex<Connection>>,
    window_id: u32,
    surface: MappedSurface,
    width: u32,
    height: u32,
    focused: bool,

    on_close_requested: Box<dyn OnCloseRequested>,
    on_redraw_requested: Box<dyn OnRedrawRequested>,
    on_key_input: Box<dyn OnKeyInput>,
    on_resized: Box<dyn OnResized>,
    on_focus_changed: Box<dyn OnFocusChanged>,
    on_key_pressed: Box<dyn OnKeyPressed>,
    on_key_released: Box<dyn OnKeyReleased>,
}

impl Window {
    pub fn new_with_info(application: &Application, info: CreateWindowInfo) -> Result<Self, Error> {
        let mut connection = application.connection.lock().unwrap();

        connection.send(&ClientMessage::CreateWindow(info))?;

        let (create_info, surface_shm_fd) = connection.filter_events(|r| match r.data {
            EventData::NewWindowInfo(info) => {
                let file = r.file.expect("Server sent window info without shm fd");
                Ok((info, file))
            }
            _ => Err(r),
        })?;
        assert_eq!(create_info.surface_stride % 4, 0);

        let surface = MappedSurface::from_fd(
            surface_shm_fd,
            create_info.width,
            create_info.height,
            (create_info.width * create_info.height) as usize,
            // create_info.surface_mapping_size,
        )?;

        log::info!("Created window: #{}", create_info.window_id);

        Ok(Self {
            connection: application.connection.clone(),
            window_id: create_info.window_id,
            width: create_info.width,
            height: create_info.height,
            surface,

            focused: false,

            on_close_requested: Box::new(|| {
                // Do nothing
                EventOutcome::Destroy
            }),
            on_redraw_requested: Box::new(|surface| {
                surface.fill(0xFF888888);
            }),
            on_key_input: Box::new(|_ev| EventOutcome::None),
            on_key_pressed: Box::new(|_| EventOutcome::None),
            on_key_released: Box::new(|_| EventOutcome::None),
            on_resized: Box::new(|_w, _h| EventOutcome::Redraw),
            on_focus_changed: Box::new(|_| EventOutcome::None),
        })
    }

    pub fn new(application: &Application) -> Result<Self, Error> {
        Self::new_with_info(application, Default::default())
    }

    pub fn id(&self) -> u32 {
        self.window_id
    }

    pub fn set_on_key_input<H: OnKeyInput + 'static>(&mut self, handler: H) {
        self.on_key_input = Box::new(handler);
    }

    pub fn set_on_key_pressed<H: OnKeyPressed + 'static>(&mut self, handler: H) {
        self.on_key_pressed = Box::new(handler);
    }

    pub fn set_on_key_released<H: OnKeyReleased + 'static>(&mut self, handler: H) {
        self.on_key_released = Box::new(handler);
    }

    pub fn set_on_resized<H: OnResized + 'static>(&mut self, handler: H) {
        self.on_resized = Box::new(handler);
    }

    pub fn set_on_redraw_requested<H: OnRedrawRequested + 'static>(&mut self, handler: H) {
        self.on_redraw_requested = Box::new(handler);
    }

    pub fn set_on_focus_changed<H: OnFocusChanged + 'static>(&mut self, handler: H) {
        self.on_focus_changed = Box::new(handler);
    }

    pub fn width(&self) -> u32 {
        self.width
    }

    pub fn height(&self) -> u32 {
        self.height
    }

    pub fn redraw(&mut self) -> Result<(), Error> {
        (self.on_redraw_requested)(&mut self.surface);

        // Blit
        self.blit_rect(0, 0, self.width, self.height)
    }

    pub fn handle_event(&mut self, ev: WindowEvent) -> Result<EventOutcome, Error> {
        let outcome = match ev {
            WindowEvent::RedrawRequested => {
                self.redraw()?;
                EventOutcome::None
            }
            WindowEvent::Resized { width, height } => {
                self.surface.resize(width, height)?;
                self.width = width;
                self.height = height;

                (self.on_resized)(width, height)
            }
            WindowEvent::KeyPressed(key) => (self.on_key_pressed)(key),
            WindowEvent::KeyReleased(key) => (self.on_key_released)(key),
            WindowEvent::KeyInput(input) => (self.on_key_input)(input),
            WindowEvent::FocusChanged(focused) => {
                self.focused = focused;
                (self.on_focus_changed)(focused)
            }
            WindowEvent::CloseRequested => (self.on_close_requested)(),
            _ => EventOutcome::None,
        };

        if outcome == EventOutcome::Redraw {
            self.redraw()?;
        }

        Ok(outcome)
    }

    fn blit_rect(&mut self, x: u32, y: u32, w: u32, h: u32) -> Result<(), Error> {
        // Clip to self bounds
        let x = x.min(self.width);
        let y = y.min(self.height);
        let w = w.min(self.width - x);
        let h = h.min(self.height - y);

        if w == 0 || h == 0 {
            return Ok(());
        }

        let mut connection = self.connection.lock().unwrap();
        connection.send(&ClientMessage::BlitWindow {
            window_id: self.window_id,
            x,
            y,
            w,
            h,
        })?;

        Ok(())
    }
}
