use std::{
    cmp, fs, io,
    num::NonZero,
    ops::{Deref, DerefMut},
    rc::Rc,
};

use clap::Parser;
use libcolors::{event::KeyboardKeyEvent, input::Key, message::ClientMessage};
use softbuffer::{Buffer, Rect, SoftBufferError};
use winit::{
    application::ApplicationHandler,
    dpi::LogicalSize,
    error::{EventLoopError, OsError},
    event::{ElementState, WindowEvent},
    event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
    keyboard::{KeyCode, PhysicalKey},
    window::{Window, WindowId},
};

use super::{FromClient, Point, ServerSender, WindowServer};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
    #[error("Event loop error: {0}")]
    EventLoop(#[from] EventLoopError),
    #[error("OS error: {0}")]
    Os(#[from] OsError),
    #[error("Display error: {0}")]
    Softbuffer(#[from] SoftBufferError),
}

pub struct Backend<S: WindowServer> {
    tx: ServerSender,
    rx: Option<uipc::Receiver<ClientMessage>>,
    window: Option<Rc<Window>>,
    context: Option<softbuffer::Context<Rc<Window>>>,
    surface: Option<softbuffer::Surface<Rc<Window>, Rc<Window>>>,
    initial: bool,
    server: S,
    args: Args,
}

pub struct DisplaySurface<'a> {
    inner: Buffer<'a, Rc<Window>, Rc<Window>>,
    width: usize,
    height: usize,
}

#[derive(Debug, Parser)]
struct Args {
    #[clap(short, help = "Framebuffer width", default_value_t = 1024)]
    width: u32,
    #[clap(short, help = "Framebuffer height", default_value_t = 768)]
    height: u32,
}

impl DisplaySurface<'_> {
    pub fn width(&self) -> usize {
        self.width
    }

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

    pub fn blit_buffer(
        mut self,
        source: &[u32],
        dst: Point<usize>,
        src: Point<usize>,
        w: usize,
        h: usize,
        src_stride: usize,
    ) {
        let src_w = (self.width - src.0).min(w);
        let dst_w = (self.width - dst.0).min(w);
        let src_h = (self.height - src.1).min(h);
        let dst_h = (self.height - dst.1).min(h);
        let w = cmp::min(src_w, dst_w);
        let h = cmp::min(src_h, dst_h);

        for y in 0..h {
            let dst_offset = (y + src.1 + dst.1) * self.width + dst.0 + src.0;
            let src_offset = (y + src.1) * src_stride + src.0;

            let src_chunk = &source[src_offset..src_offset + w];
            let dst_chunk = &mut self[dst_offset..dst_offset + w];

            dst_chunk.copy_from_slice(src_chunk);
        }

        self.inner
            .present_with_damage(&[Rect {
                x: dst.0 as _,
                y: dst.1 as _,
                width: NonZero::new(w as _).unwrap(),
                height: NonZero::new(h as _).unwrap(),
            }])
            .unwrap();
    }

    pub fn present(self) {
        self.inner.present().unwrap();
    }
}

impl Deref for DisplaySurface<'_> {
    type Target = [u32];

    fn deref(&self) -> &Self::Target {
        self.inner.deref()
    }
}

impl DerefMut for DisplaySurface<'_> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.inner.deref_mut()
    }
}

impl<S: WindowServer> ApplicationHandler<FromClient> for Backend<S> {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        let window_attributes = Window::default_attributes()
            .with_title("colors")
            .with_resizable(false)
            .with_inner_size(LogicalSize::new(self.args.width, self.args.height));
        let window = event_loop
            .create_window(window_attributes)
            .expect("create the window");
        let window = Rc::new(window);
        let context = softbuffer::Context::new(window.clone()).expect("create draw context");
        let surface =
            softbuffer::Surface::new(&context, window.clone()).expect("create draw surface");

        self.window = Some(window);
        self.context = Some(context);
        self.surface = Some(surface);
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        _window_id: WindowId,
        event: WindowEvent,
    ) {
        let (Some(window), Some(surface)) = (self.window.as_ref(), self.surface.as_mut()) else {
            return;
        };

        match event {
            WindowEvent::CloseRequested => {
                event_loop.exit();
            }
            WindowEvent::RedrawRequested if self.initial => {
                let size = window.inner_size();
                surface
                    .resize(
                        NonZero::new(size.width).unwrap(),
                        NonZero::new(size.height).unwrap(),
                    )
                    .unwrap();
                self.server.handle_initial(DisplaySurface {
                    inner: surface.buffer_mut().unwrap(),
                    width: size.width as _,
                    height: size.height as _,
                });

                self.initial = false;
            }
            WindowEvent::Resized(size) => {
                let width = NonZero::new(size.width).unwrap();
                let height = NonZero::new(size.height).unwrap();
                surface.resize(width, height).unwrap();
                let display_surface = DisplaySurface {
                    inner: surface.buffer_mut().unwrap(),
                    width: size.width as usize,
                    height: size.height as usize,
                };
                self.server
                    .handle_display_resize(&mut self.tx, display_surface);
            }
            WindowEvent::KeyboardInput { event, .. } => {
                if let Some(event) = convert_key_event(event) {
                    let size = window.inner_size();
                    let display_surface = DisplaySurface {
                        inner: surface.buffer_mut().unwrap(),
                        width: size.width as usize,
                        height: size.height as usize,
                    };
                    self.server
                        .handle_keyboard_event(display_surface, &mut self.tx, event);
                }
            }
            _ => (),
        }
    }

    fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: FromClient) {
        let (Some(window), Some(surface)) = (self.window.as_ref(), self.surface.as_mut()) else {
            return;
        };

        let size = window.inner_size();
        let display_surface = DisplaySurface {
            inner: surface.buffer_mut().unwrap(),
            width: size.width as usize,
            height: size.height as usize,
        };
        self.server
            .handle_client_message(display_surface, &mut self.tx, event);
    }
}

impl<S: WindowServer> Backend<S> {
    pub fn new(server: S) -> Result<Self, Error> {
        let args = Args::parse();

        fs::remove_file(libcolors::CHANNEL_NAME).ok();
        let listener = uipc::Channel::bind(libcolors::CHANNEL_NAME).unwrap();
        let (tx, rx) = listener.split();
        let tx = ServerSender { sender: tx };

        Ok(Self {
            args,
            server,
            tx,
            rx: Some(rx),
            context: None,
            window: None,
            surface: None,
            initial: true,
        })
    }

    fn io_worker(mut rx: uipc::Receiver<ClientMessage>, event_proxy: EventLoopProxy<FromClient>) {
        loop {
            let (message, file, peer) = rx.receive_with_file_from().unwrap();
            let message = FromClient {
                message,
                file,
                peer,
            };
            event_proxy.send_event(message).unwrap();
        }
    }

    pub fn run(mut self) -> Result<(), Error> {
        let event_loop = EventLoop::<FromClient>::with_user_event()
            .build()
            .expect("create event loop");
        let event_proxy = event_loop.create_proxy();
        event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait);

        let rx = self.rx.take().expect("ipc rx channel");
        std::thread::spawn(move || {
            Self::io_worker(rx, event_proxy);
        });

        event_loop.run_app(&mut self).map_err(Error::from)
    }
}

fn convert_key_event(raw: winit::event::KeyEvent) -> Option<KeyboardKeyEvent> {
    let PhysicalKey::Code(code) = raw.physical_key else {
        return None;
    };

    let key = match code {
        KeyCode::ShiftLeft => Key::LShift,
        KeyCode::ShiftRight => Key::RShift,
        KeyCode::ControlLeft => Key::LControl,
        KeyCode::ControlRight => Key::RControl,
        KeyCode::Backspace => Key::Backspace,
        KeyCode::Space => Key::Char(b' '),
        KeyCode::Enter => Key::Enter,
        KeyCode::Escape => Key::Escape,
        KeyCode::Minus => Key::Char(b'-'),
        KeyCode::AltLeft => Key::LAlt,
        KeyCode::AltRight => Key::RAlt,
        KeyCode::KeyQ => Key::Char(b'q'),
        KeyCode::KeyW => Key::Char(b'w'),
        KeyCode::KeyE => Key::Char(b'e'),
        KeyCode::KeyR => Key::Char(b'r'),
        KeyCode::KeyT => Key::Char(b't'),
        KeyCode::KeyY => Key::Char(b'y'),
        KeyCode::KeyU => Key::Char(b'u'),
        KeyCode::KeyI => Key::Char(b'i'),
        KeyCode::KeyO => Key::Char(b'o'),
        KeyCode::KeyP => Key::Char(b'p'),
        KeyCode::KeyA => Key::Char(b'a'),
        KeyCode::KeyS => Key::Char(b's'),
        KeyCode::KeyD => Key::Char(b'd'),
        KeyCode::KeyF => Key::Char(b'f'),
        KeyCode::KeyG => Key::Char(b'g'),
        KeyCode::KeyH => Key::Char(b'h'),
        KeyCode::KeyJ => Key::Char(b'j'),
        KeyCode::KeyK => Key::Char(b'k'),
        KeyCode::KeyL => Key::Char(b'l'),
        KeyCode::KeyZ => Key::Char(b'z'),
        KeyCode::KeyX => Key::Char(b'x'),
        KeyCode::KeyC => Key::Char(b'c'),
        KeyCode::KeyV => Key::Char(b'v'),
        KeyCode::KeyB => Key::Char(b'b'),
        KeyCode::KeyN => Key::Char(b'n'),
        KeyCode::KeyM => Key::Char(b'm'),
        KeyCode::Slash => Key::Char(b'/'),
        KeyCode::Period => Key::Char(b'.'),
        KeyCode::Comma => Key::Char(b','),
        KeyCode::Backslash => Key::Char(b'\\'),
        KeyCode::BracketLeft => Key::Char(b'['),
        KeyCode::BracketRight => Key::Char(b']'),
        KeyCode::Digit0 => Key::Char(b'0'),
        KeyCode::Digit1 => Key::Char(b'1'),
        KeyCode::Digit2 => Key::Char(b'2'),
        KeyCode::Digit3 => Key::Char(b'3'),
        KeyCode::Digit4 => Key::Char(b'4'),
        KeyCode::Digit5 => Key::Char(b'5'),
        KeyCode::Digit6 => Key::Char(b'6'),
        KeyCode::Digit7 => Key::Char(b'7'),
        KeyCode::Digit8 => Key::Char(b'8'),
        KeyCode::Digit9 => Key::Char(b'9'),
        KeyCode::Backquote => Key::Char(b'`'),
        KeyCode::Equal => Key::Char(b'='),
        KeyCode::Quote => Key::Char(b'\''),
        KeyCode::Semicolon => Key::Char(b';'),
        _ => return None,
    };
    let state = match raw.state {
        ElementState::Pressed => true,
        ElementState::Released => false,
    };

    Some(KeyboardKeyEvent { key, state })
}
