use core::{
    mem::MaybeUninit,
    sync::atomic::{AtomicBool, Ordering},
    task::{Context, Poll},
};

use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use device_api::device::Device;
use libk_util::{
    ring::{LossyRingQueue, RingBuffer},
    sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
};
use yggdrasil_abi::{
    error::Error,
    io::{
        device::{self, TerminalRequestVariant},
        KeyboardKey, KeyboardKeyEvent, TerminalInputOptions, TerminalLineOptions, TerminalOptions,
        TerminalOutputOptions, TerminalSize,
    },
    option::RequestValue,
    process::{ProcessGroupId, Signal},
};

use crate::{device::char::CharDevice, task::process::Process};

use super::FileReadiness;

pub struct Terminal<O: TerminalOutput> {
    input: TerminalInput,
    output: O,
    config: IrqSafeRwLock<TerminalOptions>,
}

// Keyboard, PTY slave, terminal, etc. -> program
pub struct TerminalInput {
    buffer: IrqSafeSpinlock<InputBuffer>,
    ready_ring: LossyRingQueue<u8>,
    signal_pgroup: IrqSafeRwLock<Option<ProcessGroupId>>,
}

// Program -> screen, PTY master, terminal, etc.
pub trait TerminalOutput: Sync + Send {
    fn write(&self, byte: u8) -> Result<(), Error>;

    fn notify_readers(&self) {}

    fn size(&self) -> TerminalSize {
        TerminalSize {
            rows: 24,
            columns: 80,
        }
    }
    fn set_size(&self, size: TerminalSize) -> Result<(), Error> {
        let _ = size;
        Err(Error::InvalidOperation)
    }

    fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
        let mut written = 0;
        for &byte in bytes {
            self.write(byte)?;
            written += 1;
        }
        Ok(written)
    }

    fn baud_rate(&self) -> u32 {
        115200
    }
    fn set_baud_rate(&self, baud: u32) -> Result<(), Error> {
        if baud == 115200 {
            Ok(())
        } else {
            log::warn!(
                "[this] TerminalOutput impl does not support baud rate changes (want {baud})"
            );
            Err(Error::NotImplemented)
        }
    }
}

struct InputBuffer {
    pending: Box<[MaybeUninit<u8>]>,
    position: usize,
}

impl<O: TerminalOutput> Terminal<O> {
    pub fn from_parts(mut config: TerminalOptions, input: TerminalInput, output: O) -> Self {
        config.baud_rate = output.baud_rate();
        Self {
            input,
            output,
            config: IrqSafeRwLock::new(config),
        }
    }

    pub fn input(&self) -> &TerminalInput {
        &self.input
    }

    pub fn output(&self) -> &O {
        &self.output
    }

    pub fn config(&self) -> TerminalOptions {
        *self.config.read()
    }

    #[inline]
    pub async fn read_from_input(&self, buffer: &mut [u8]) -> Result<usize, Error> {
        let eof = {
            let config = self.config.read();
            config.is_canonical().then_some(config.chars.eof)
        };
        self.input.read(buffer, eof).await
    }

    #[inline]
    pub fn read_from_input_nonblocking(&self, buffer: &mut [u8]) -> Result<usize, Error> {
        let eof = {
            let config = self.config.read();
            config.is_canonical().then_some(config.chars.eof)
        };
        self.input.read_nonblocking(buffer, eof)
    }

    pub fn putc_to_output(&self, byte: u8) -> Result<(), Error> {
        self.output.write(byte)
    }

    pub fn write_to_output(&self, buffer: &[u8]) -> Result<usize, Error> {
        // TODO handle options
        self.output.write_multiple(buffer)
    }

    pub fn write_to_input(&self, mut byte: u8) {
        let config = self.config.read();
        let mut buffer = self.input.buffer.lock();

        if byte == b'\r' && config.input.contains(TerminalInputOptions::CR_TO_NL) {
            byte = b'\n';
        }

        if config.is_canonical() {
            // Canonical line processing

            // Echo back
            if byte == config.chars.erase {
                let echo =
                    buffer.erase_pending() && config.line.contains(TerminalLineOptions::ECHO_ERASE);

                if echo {
                    self.output.write_multiple(b"\x1B[D \x1B[D").ok();
                }

                return;
            }

            if byte == b'\n' {
                // TODO NL_TO_CRNL
                if config.is_echo_newline() {
                    if config.output.contains(TerminalOutputOptions::NL_TO_CRNL) {
                        self.output.write(b'\r').ok();
                    }
                    self.output.write(byte).ok();
                }
            } else if byte.is_ascii_control() {
                if config.line.contains(TerminalLineOptions::ECHO) {
                    self.output.write_multiple(&[b'^', byte + 0x40]).ok();
                }
            } else if config.line.contains(TerminalLineOptions::ECHO) {
                self.output.write(byte).ok();
            }

            if byte == config.chars.interrupt {
                if config.line.contains(TerminalLineOptions::SIGNAL) {
                    self.output.notify_readers();

                    let pgrp = *self.input.signal_pgroup.read();
                    log::info!("Send terminal SIGINT to {pgrp:?}");
                    if let Some(group_id) = pgrp {
                        Process::signal_group(None, group_id, Signal::Interrupted);
                        self.input.ready_ring.notify_all();
                        return;
                    }
                } else {
                    buffer.write_pending(byte);
                }
            } else {
                buffer.write_pending(byte);
            }

            if byte == b'\n' || byte == config.chars.eof {
                let data = buffer.flush();
                self.input.ready_ring.write_multiple(data);
            }
        } else {
            // Raw line processing
            self.input.ready_ring.write(byte);
        }
    }

    pub fn handle_device_request(
        &self,
        option: u32,
        buffer: &mut [u8],
        len: usize,
    ) -> Result<usize, Error> {
        let input = &buffer[..len];
        let option = TerminalRequestVariant::try_from(option)?;
        match option {
            TerminalRequestVariant::SetTerminalGroup => {
                let pgroup = device::SetTerminalGroup::load_request(input)?;
                *self.input.signal_pgroup.write() = Some(pgroup);
                device::SetTerminalGroup::store_response(&(), buffer)
            }
            TerminalRequestVariant::SetTerminalSize => {
                // TODO SIGWINCH?
                let size = device::SetTerminalSize::load_request(input)?;
                self.output.set_size(size)?;
                device::SetTerminalSize::store_response(&(), buffer)
            }
            TerminalRequestVariant::GetTerminalSize => {
                device::GetTerminalSize::store_response(&self.output.size(), buffer)
            }
            TerminalRequestVariant::SetTerminalOptions => {
                let options = device::SetTerminalOptions::load_request(input)?;
                self.input.flush();
                let need_baud_change = {
                    let mut cfg = self.config.write();
                    let need_baud_change = cfg.baud_rate != options.baud_rate;
                    *cfg = options;
                    need_baud_change
                };
                if need_baud_change {
                    self.output.set_baud_rate(options.baud_rate)?;
                }
                device::SetTerminalOptions::store_response(&(), buffer)
            }
            TerminalRequestVariant::GetTerminalOptions => {
                device::GetTerminalOptions::store_response(&*self.config.read(), buffer)
            }
            TerminalRequestVariant::IsTerminal => device::IsTerminal::store_response(&(), buffer),
        }
    }

    pub async fn consume_keyboard(
        self: Arc<Self>,
        keyboard: Arc<dyn CharDevice>,
        stop: Option<&AtomicBool>,
    ) {
        let mut buf = [0; 4];
        let mut lshift = false;
        let mut rshift = false;
        let mut lctrl = false;
        let mut rctrl = false;

        loop {
            if stop.map(|r| r.load(Ordering::Acquire)).unwrap_or(false) {
                break;
            }

            keyboard.read(&mut buf).await.unwrap();
            let (key, pressed) = KeyboardKeyEvent::from_bytes(buf).split();
            let ctrl = lctrl || rctrl;
            let shift = lshift || rshift;

            match key {
                KeyboardKey::LShift => lshift = pressed,
                KeyboardKey::RShift => rshift = pressed,
                KeyboardKey::LControl => lctrl = pressed,
                KeyboardKey::RControl => rctrl = pressed,
                KeyboardKey::Char(ch) if ctrl && pressed => match ch {
                    b'c' => self.write_to_input(self.config().chars.interrupt),
                    b'd' => self.write_to_input(self.config().chars.eof),
                    _ => (),
                },
                KeyboardKey::Char(ch) if shift && pressed => {
                    let ch = match ch {
                        ch if ch.is_ascii_lowercase() => ch.to_ascii_uppercase(),
                        b'`' => b'~',
                        b'1' => b'!',
                        b'2' => b'@',
                        b'3' => b'#',
                        b'4' => b'$',
                        b'5' => b'%',
                        b'6' => b'^',
                        b'7' => b'&',
                        b'8' => b'*',
                        b'9' => b'(',
                        b'0' => b')',
                        b'-' => b'_',
                        b'=' => b'+',
                        b';' => b':',
                        b'\'' => b'"',
                        b'\\' => b'|',
                        b',' => b'<',
                        b'.' => b'>',
                        b'/' => b'?',
                        _ => continue,
                    };
                    self.write_to_input(ch);
                }
                KeyboardKey::Char(ch) if pressed => {
                    // TODO shift/ctrl
                    self.write_to_input(ch);
                }
                KeyboardKey::Enter if pressed => {
                    self.write_to_input(b'\n');
                }
                KeyboardKey::Backspace if pressed => {
                    self.write_to_input(b'\x7F');
                }
                KeyboardKey::Escape if pressed => {
                    self.write_to_input(b'\x1B');
                }
                _ => (),
            }
        }
    }
}

impl TerminalInput {
    pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
        Ok(Self {
            buffer: IrqSafeSpinlock::new(InputBuffer::with_capacity(capacity)),
            ready_ring: LossyRingQueue::try_with_capacity(capacity)?,
            signal_pgroup: IrqSafeRwLock::new(None),
        })
    }

    pub async fn read(&self, buffer: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
        let mut lock = self.ready_ring.read_lock().await;
        Ok(read_all(&mut lock, buffer, eof))
    }

    pub fn read_nonblocking(&self, buffer: &mut [u8], eof: Option<u8>) -> Result<usize, Error> {
        let mut lock = self.ready_ring.try_read_lock().ok_or(Error::WouldBlock)?;
        Ok(read_all(&mut lock, buffer, eof))
    }

    pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<()> {
        self.ready_ring.poll_readable(cx)
    }

    pub fn flush(&self) {
        let mut lock = self.buffer.lock();
        let data = lock.flush();
        self.ready_ring.write_multiple(data);
    }
}

impl InputBuffer {
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            pending: Box::new_uninit_slice(capacity),
            position: 0,
        }
    }

    pub fn write_pending(&mut self, byte: u8) {
        if self.position == self.pending.len() {
            todo!();
        }

        self.pending[self.position].write(byte);
        self.position += 1;
    }

    pub fn erase_pending(&mut self) -> bool {
        if self.position != 0 {
            self.position -= 1;
            true
        } else {
            false
        }
    }

    pub fn flush(&mut self) -> &[u8] {
        let data = unsafe { self.pending[..self.position].assume_init_ref() };
        self.position = 0;
        data
    }
}

#[async_trait]
impl<O: TerminalOutput> CharDevice for Terminal<O> {
    async fn write(&self, buf: &[u8]) -> Result<usize, Error> {
        self.write_to_output(buf)
    }

    fn write_nonblocking(&self, buf: &[u8]) -> Result<usize, Error> {
        self.write_to_output(buf)
    }

    async fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
        self.read_from_input(buf).await
    }

    fn read_nonblocking(&self, buf: &mut [u8]) -> Result<usize, Error> {
        self.read_from_input_nonblocking(buf)
    }

    fn is_readable(&self) -> bool {
        true
    }

    fn is_writeable(&self) -> bool {
        true
    }

    fn is_terminal(&self) -> bool {
        true
    }

    fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
        self.handle_device_request(option, buffer, len)
    }
}

impl<O: TerminalOutput> FileReadiness for Terminal<O> {
    fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        self.input().poll_read(cx).map(Ok)
    }
}

impl<O: TerminalOutput> Device for Terminal<O> {
    fn display_name(&self) -> &str {
        "Terminal"
    }
}

pub fn read_all(source: &mut RingBuffer<u8>, target: &mut [u8], eof: Option<u8>) -> usize {
    let mut pos = 0;
    while pos < target.len()
        && let Some(ch) = source.try_read()
    {
        if eof.map(|eof| eof == ch).unwrap_or(false) {
            break;
        }
        target[pos] = ch;
        pos += 1;
    }
    pos
}
