use std::{
    cmp,
    fs::{File, OpenOptions},
    io::{self, Read},
    ops::{Deref, DerefMut},
    os::fd::{AsRawFd, RawFd},
    path::{Path, PathBuf},
};

use clap::Parser;
use cross::{io::Poll, mem::FileMapping};
use libcolors::{event::KeyboardKeyEvent, input::Key, message::ClientMessage};
use runtime::rt::io::device;
use uipc::{Channel, Receiver};
use yggdrasil_abi::io::KeyboardKey;

use crate::sys::FromClient;

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

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
    #[error("IPC error: {0}")]
    Ipc(#[from] uipc::Error),
}

pub struct Backend<'a, S: WindowServer> {
    poll: Poll,
    rx: Receiver<ClientMessage>,
    tx: ServerSender,

    display: Display<'a>,
    input: KeyboardInput,

    server: S,
}

struct Display<'a> {
    #[allow(unused)]
    mapping: FileMapping,
    data: &'a mut [u32],

    width: usize,
    height: usize,

    stride: usize,
    _size: usize,
}

struct KeyboardInput(File);

pub struct DisplaySurface<'a, 'd> {
    display: &'a mut Display<'d>,
}

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

    pub fn height(&self) -> usize {
        self.display.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.display.width - src.0).min(w);
        let dst_w = (self.display.width - dst.0).min(w);
        let src_h = (self.display.height - src.1).min(h);
        let dst_h = (self.display.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.display.stride + 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.present();
    }

    pub fn present(self) {
        self.display.flush();
    }
}

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

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

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

#[derive(Debug, Parser)]
struct Args {
    framebuffer: PathBuf,
}

impl<S: WindowServer> Backend<'_, S> {
    pub fn new(server: S) -> Result<Self, Error> {
        let args = Args::parse();
        let channel = Channel::bind(libcolors::CHANNEL_NAME)?;
        let (tx, rx) = channel.split();
        let tx = ServerSender { sender: tx };
        let input = KeyboardInput::open()?;

        let mut poll = Poll::new()?;
        poll.add(&rx)?;
        poll.add(&input)?;
        let display = Display::open(args.framebuffer)?;

        Ok(Self {
            poll,
            tx,
            rx,

            display,
            input,

            server,
        })
    }

    pub fn run(mut self) -> Result<(), Error> {
        self.server.handle_initial(DisplaySurface {
            display: &mut self.display,
        });

        loop {
            let fd = self.poll.wait(None)?.unwrap();

            if fd == self.rx.as_raw_fd() {
                let (message, file, peer) = self.rx.receive_with_file_from()?;
                let event = FromClient {
                    message,
                    file,
                    peer,
                };
                let surface = DisplaySurface {
                    display: &mut self.display,
                };
                self.server
                    .handle_client_message(surface, &mut self.tx, event);
            } else if fd == self.input.as_raw_fd() {
                let event = self.input.read_event()?;
                if let Some(event) = convert_key_event(event) {
                    let surface = DisplaySurface {
                        display: &mut self.display,
                    };
                    self.server
                        .handle_keyboard_event(surface, &mut self.tx, event);
                }
            }
        }
    }
}

impl Display<'_> {
    pub fn open(framebuffer: impl AsRef<Path>) -> Result<Self, Error> {
        let framebuffer = framebuffer.as_ref();
        let file = OpenOptions::new().open(framebuffer)?;

        let mut buffer = [0; 128];
        device::device_request::<device::AcquireDevice>(file.as_raw_fd(), &mut buffer, &())
            .map_err(std::io::Error::from)?;
        let framebuffer = device::device_request::<device::GetActiveFramebuffer>(
            file.as_raw_fd(),
            &mut buffer,
            &(),
        )
        .map_err(std::io::Error::from)?;

        let width = framebuffer.width as usize;
        let height = framebuffer.height as usize;
        let stride = framebuffer.stride / size_of::<u32>();

        let mut mapping = FileMapping::map(file, framebuffer.size, true)?;
        let data = unsafe {
            std::slice::from_raw_parts_mut(mapping.as_mut_ptr() as *mut u32, height * stride)
        };

        Ok(Self {
            mapping,
            data,

            width,
            height,
            stride,
            _size: framebuffer.size / size_of::<u32>(),
        })
    }

    pub fn flush(&mut self) {
        let mut buffer = [0; 0];
        device::device_request::<device::FlushDisplay>(self.mapping.as_raw_fd(), &mut buffer, &())
            .ok();
    }
}

impl KeyboardInput {
    pub fn open() -> Result<Self, Error> {
        let file = File::open("/dev/kbd")?;
        Ok(Self(file))
    }

    pub fn read_event(&mut self) -> Result<yggdrasil_abi::io::KeyboardKeyEvent, Error> {
        let mut buf = [0; 4];
        let len = self.0.read(&mut buf)?;

        if len == 4 {
            Ok(yggdrasil_abi::io::KeyboardKeyEvent::from_bytes(buf))
        } else {
            todo!()
        }
    }
}

impl AsRawFd for KeyboardInput {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

fn convert_key_event(raw: yggdrasil_abi::io::KeyboardKeyEvent) -> Option<KeyboardKeyEvent> {
    let (key, state) = raw.split();

    let key = match key {
        KeyboardKey::Char(ch) => Key::Char(ch),
        KeyboardKey::Backspace => Key::Backspace,
        KeyboardKey::Enter => Key::Enter,
        KeyboardKey::Home => Key::Home,
        KeyboardKey::End => Key::End,
        KeyboardKey::PageUp => Key::PageUp,
        KeyboardKey::PageDown => Key::PageDown,
        KeyboardKey::Escape => Key::Escape,
        KeyboardKey::Up => Key::Up,
        KeyboardKey::Down => Key::Down,
        KeyboardKey::Left => Key::Left,
        KeyboardKey::Right => Key::Right,
        KeyboardKey::LAlt => Key::LAlt,
        KeyboardKey::RAlt => Key::RAlt,
        KeyboardKey::LShift => Key::LShift,
        KeyboardKey::RShift => Key::RShift,
        KeyboardKey::LControl => Key::LControl,
        KeyboardKey::RControl => Key::RControl,
        KeyboardKey::Insert => return None,
        KeyboardKey::Delete => return None,
        KeyboardKey::Unknown => return None,
        KeyboardKey::CapsLock => return None,
        KeyboardKey::Tab => Key::Tab,
        KeyboardKey::F(_) => return None,
    };

    Some(KeyboardKeyEvent { key, state })
}
