use std::{
    io::{self, Read},
    os::fd::{AsRawFd, RawFd},
    time::Duration,
};

use crate::io::{Poll, RawStdin, TerminalOptionsImpl};

pub struct TerminalInput {
    stdin: RawStdin,
    poll: Poll,
    buffer: [u8; 128],
    buffer_len: usize,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TermKey {
    Char(char),
    Eof,
    Up,
    Down,
    Right,
    Left,
    Home,
    End,
    Escape,
    PageUp,
    PageDown,
    Insert,
    Delete,
    F(u8),

    CtrlUp,
    CtrlDown,
    CtrlRight,
    CtrlLeft,
    CtrlHome,
    CtrlEnd,
    CtrlPageUp,
    CtrlPageDown,
    CtrlInsert,
    CtrlDelete,
    CtrlF(u8),

    MouseButton1Down(u32, u32),
    MouseButton2Down(u32, u32),
    MouseButton3Down(u32, u32),
    MouseButton4(u32, u32),
    MouseButton5(u32, u32),
    MouseButtonUp(u32, u32),

    Invalid, // Invalid UTF-8 sequence
}

impl From<char> for TermKey {
    fn from(value: char) -> Self {
        if value == '\x1B' {
            Self::Escape
        } else {
            Self::Char(value)
        }
    }
}

impl TerminalInput {
    pub fn open() -> io::Result<Self> {
        let stdin = RawStdin::open()?;
        let mut poll = Poll::new()?;
        poll.add(&stdin)?;
        Ok(Self {
            poll,
            stdin,
            buffer: [0; 128],
            buffer_len: 0,
        })
    }

    pub fn set_options(&mut self, options: TerminalOptionsImpl) -> io::Result<TerminalOptionsImpl> {
        self.stdin.set_options(options)
    }

    fn take(&mut self, count: usize) {
        self.buffer.copy_within(count..self.buffer_len, 0);
        self.buffer_len -= count;
    }

    fn parse_utf8_from_buffer(&mut self) -> Option<TermKey> {
        for count in 1..=4 {
            if count > self.buffer_len {
                break;
            }
            if let Ok(utf8) = std::str::from_utf8(&self.buffer[..count]) {
                let ch = utf8.chars().next().unwrap();
                self.take(count);
                return Some(TermKey::Char(ch));
            }
        }
        None
    }

    fn parse_escape(&mut self) -> Option<TermKey> {
        const MATCH_TABLE: &[(&[u8], TermKey)] = &[
            (b"\x04", TermKey::Eof),
            //
            (b"\x1BOA", TermKey::Up),
            (b"\x1BOB", TermKey::Down),
            //
            (b"\x1B[1;5A", TermKey::CtrlUp),
            (b"\x1B[1;5B", TermKey::CtrlDown),
            (b"\x1B[1;5C", TermKey::CtrlRight),
            (b"\x1B[1;5D", TermKey::CtrlLeft),
            (b"\x1B[2;5~", TermKey::CtrlInsert),
            (b"\x1B[3;5~", TermKey::CtrlDelete),
            (b"\x1B[1;5H", TermKey::CtrlHome),
            (b"\x1B[1;5F", TermKey::CtrlEnd),
            (b"\x1B[5;5~", TermKey::CtrlPageUp),
            (b"\x1B[6;5~", TermKey::CtrlPageDown),
            (b"\x1B[1;5P", TermKey::CtrlF(1)),
            (b"\x1B[1;5Q", TermKey::CtrlF(2)),
            (b"\x1B[1;5R", TermKey::CtrlF(3)),
            (b"\x1B[1;5S", TermKey::CtrlF(4)),
            (b"\x1B[15;5~", TermKey::CtrlF(5)),
            (b"\x1B[17;5~", TermKey::CtrlF(6)),
            (b"\x1B[18;5~", TermKey::CtrlF(7)),
            (b"\x1B[19;5~", TermKey::CtrlF(8)),
            (b"\x1B[20;5~", TermKey::CtrlF(9)),
            (b"\x1B[21;5~", TermKey::CtrlF(10)),
            (b"\x1B[23;5~", TermKey::CtrlF(11)),
            (b"\x1B[24;5~", TermKey::CtrlF(12)),
            //
            (b"\x1BOP", TermKey::F(1)),
            (b"\x1BOQ", TermKey::F(2)),
            (b"\x1BOR", TermKey::F(3)),
            (b"\x1BOS", TermKey::F(4)),
            (b"\x1B[15~", TermKey::F(5)),
            (b"\x1B[17~", TermKey::F(6)),
            (b"\x1B[18~", TermKey::F(7)),
            (b"\x1B[19~", TermKey::F(8)),
            (b"\x1B[20~", TermKey::F(9)),
            (b"\x1B[21~", TermKey::F(10)),
            (b"\x1B[23~", TermKey::F(11)),
            (b"\x1B[24~", TermKey::F(12)),
            (b"\x1B[2~", TermKey::Insert),
            (b"\x1B[3~", TermKey::Delete),
            (b"\x1B[5~", TermKey::PageUp),
            (b"\x1B[6~", TermKey::PageDown),
            (b"\x1B[A", TermKey::Up),
            (b"\x1B[B", TermKey::Down),
            (b"\x1B[C", TermKey::Right),
            (b"\x1B[D", TermKey::Left),
            (b"\x1B[H", TermKey::Home),
            (b"\x1B[F", TermKey::End),
            //
            (b"\x1B", TermKey::Escape),
        ];

        let data = &self.buffer[..self.buffer_len];
        if data.is_empty() {
            return None;
        }

        if data.len() >= 6 {
            let x = mouse_event_coord(data[4]);
            let y = mouse_event_coord(data[5]);

            let event: Option<fn(u32, u32) -> TermKey> = match &data[..4] {
                b"\x1B[M " => Some(TermKey::MouseButton1Down),
                b"\x1B[M\"" => Some(TermKey::MouseButton2Down),
                b"\x1B[M!" => Some(TermKey::MouseButton3Down),
                b"\x1B[M#" => Some(TermKey::MouseButtonUp),
                b"\x1B[M`" => Some(TermKey::MouseButton4),
                b"\x1B[Ma" => Some(TermKey::MouseButton5),
                _ => None,
            };

            if let Some(event) = event {
                self.take(6);
                return Some(event(x, y));
            }
        }

        for &(m, key) in MATCH_TABLE {
            if data.starts_with(m) {
                self.take(m.len());
                return Some(key);
            }
        }

        None
    }

    fn parse_from_buffer(&mut self) -> Option<TermKey> {
        if let Some(key) = self.parse_escape() {
            return Some(key);
        }
        self.parse_utf8_from_buffer()
    }

    pub fn read_cursor_position_report(&mut self) -> io::Result<Option<(u32, u32)>> {
        #[derive(Clone, Copy)]
        enum State {
            ReadRow,
            ReadColumn,
        }

        // CSI r ; c R
        let mut ch_buffer = [0];

        let mut state = State::ReadRow;
        let mut row = 0;
        let mut column = 0;

        self.stdin.read_exact(&mut ch_buffer)?;
        if ch_buffer[0] != b'\x1B' {
            return Ok(None);
        }
        self.stdin.read_exact(&mut ch_buffer)?;
        if ch_buffer[0] != b'[' {
            return Ok(None);
        }

        loop {
            self.stdin.read_exact(&mut ch_buffer)?;
            let ch = ch_buffer[0];

            match (state, ch) {
                (State::ReadRow, b';') => {
                    state = State::ReadColumn;
                }
                (State::ReadRow, b'0'..=b'9') => {
                    row *= 10;
                    row += (ch - b'0') as u32;
                }
                (State::ReadColumn, b'R') => {
                    return Ok(Some((row, column)));
                }
                (State::ReadColumn, b'0'..=b'9') => {
                    column *= 10;
                    column += (ch - b'0') as u32;
                }
                (_, _) => {
                    return Ok(None);
                }
            }
        }
    }

    pub fn read_key(&mut self) -> io::Result<TermKey> {
        while self.buffer_len < self.buffer.len() {
            if let Some(key) = self.parse_from_buffer() {
                return Ok(key);
            }
            if self.buffer_len > 0 && self.buffer[0] != b'\x1B' {
                break;
            }
            if self.buffer_len != 0 && self.poll.wait(Some(Duration::from_millis(10)))?.is_none() {
                break;
            }
            let count = self.stdin.read(&mut self.buffer[self.buffer_len..])?;
            self.buffer_len += count;
        }

        if let Some(key) = self.parse_from_buffer() {
            return Ok(key);
        }

        todo!("Buffer full, but no key read")
    }
}

impl AsRawFd for TerminalInput {
    fn as_raw_fd(&self) -> RawFd {
        self.poll.as_raw_fd()
    }
}

fn mouse_event_coord(x: u8) -> u32 {
    x.saturating_sub(32) as u32
}
