use std::collections::VecDeque;

use libterm::escape::{
    CharacterAttribute, ControlSequence, CursorStyle, EraseInDisplay, EraseInLine, EscapeParser,
    TerminalInput,
};

use crate::{
    attr::{CellAttributes, Color},
    CONFIG,
};

#[derive(Clone, Copy, Debug)]
pub struct GridCell {
    pub char: char,
    pub attrs: CellAttributes,
}

#[derive(Clone)]
pub struct GridRow {
    cols: Vec<GridCell>,
    dirty: bool,
}

pub struct Buffer {
    scrollback: VecDeque<GridRow>,
    rows: Vec<GridRow>,
    width: usize,
    height: usize,
    scrollback_limit: usize,
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Cursor {
    pub row: usize,
    pub col: usize,
}

#[derive(Default)]
struct Utf8Decoder {
    buffer: [u8; 4],
    len: usize,
}

pub struct State {
    pub buffer: Buffer,

    utf8_decode: Utf8Decoder,
    escape: EscapeParser,

    scroll: usize,
    pub cursor: Cursor,
    pub cursor_visible: bool,
    pub alternate: bool,
    #[allow(unused)]
    saved_cursor: Option<Cursor>,

    pub cursor_style: CursorStyle,
    pub default_attributes: CellAttributes,
    pub attributes: CellAttributes,
    pub fg_index: Option<u32>,
    pub cursor_dirty: bool,
}

impl Utf8Decoder {
    pub fn push(&mut self, byte: u8) -> Option<char> {
        self.buffer[self.len] = byte;
        self.len += 1;
        if let Ok(str) = std::str::from_utf8(&self.buffer[..self.len]) {
            let ch = str.chars().next().unwrap();
            self.len = 0;
            Some(ch)
        } else if self.len == 4 {
            // Got 4 bytes and could not decode a single character
            self.len = 0;
            Some('\u{25A1}')
        } else {
            None
        }
    }
}

impl GridCell {
    pub fn new(char: char, attrs: CellAttributes) -> Self {
        Self { char, attrs }
    }

    pub fn empty(fg: Color, bg: Color) -> Self {
        Self {
            char: '\0',
            attrs: CellAttributes {
                fg,
                bg,
                inverse: false,
                bold: false,
                italic: false,
                underlined: false,
                strikethrough: false,
            },
        }
    }
}

impl GridRow {
    pub fn new(width: usize, fg: Color, bg: Color) -> Self {
        Self {
            cols: vec![GridCell::empty(fg, bg); width],
            dirty: true,
        }
    }

    pub fn set_cell(&mut self, col: usize, char: char, attrs: CellAttributes) {
        self.cols[col] = GridCell::new(char, attrs);
        self.dirty = true;
    }

    pub fn is_dirty(&self) -> bool {
        self.dirty
    }

    pub fn clear_dirty(&mut self) {
        self.dirty = false;
    }

    pub fn cells(&self) -> impl Iterator<Item = &GridCell> {
        self.cols.iter()
    }

    fn clear(&mut self, fg: Color, bg: Color) {
        self.cols.fill(GridCell::empty(fg, bg));
        self.dirty = true;
    }

    fn erase_to_right(&mut self, start: usize, fg: Color, bg: Color) {
        self.cols[start..].fill(GridCell::empty(fg, bg));
        self.dirty = true;
    }

    fn resize(&mut self, width: usize, fg: Color, bg: Color) {
        self.cols.resize(width, GridCell::empty(fg, bg));
        self.dirty = true;
    }
}

impl Buffer {
    pub fn new(width: usize, height: usize, fg: Color, bg: Color) -> Self {
        Self {
            rows: vec![GridRow::new(width, fg, bg); height],
            scrollback: VecDeque::new(),
            scrollback_limit: 1024,
            width,
            height,
        }
    }

    pub fn clear(&mut self, fg: Color, bg: Color) {
        self.rows.fill(GridRow::new(self.width, fg, bg));
    }

    pub fn iter_rows_mut<F: FnMut(usize, &mut GridRow)>(&mut self, scroll: usize, mut handler: F) {
        let scroll = scroll.min(self.scrollback.len());
        let non_scroll = self.height.saturating_sub(scroll);
        let scroll_end = self.height - non_scroll;

        for y in 0..scroll_end {
            let i = scroll - 1 - y;
            let row = &mut self.scrollback[i];
            handler(y, row);
        }
        for i in 0..non_scroll {
            let y = scroll + i;
            let row = &mut self.rows[i];
            handler(y, row);
        }
    }

    pub fn visible_rows_mut<F: FnMut(usize, &mut GridRow)>(
        &mut self,
        scroll: usize,
        mut handler: F,
    ) {
        self.iter_rows_mut(scroll, |y, row| {
            if row.dirty {
                row.dirty = false;
                handler(y, row);
            }
        });
    }

    pub fn resize(&mut self, width: usize, height: usize, fg: Color, bg: Color) {
        self.rows.resize(height, GridRow::new(width, fg, bg));
        for row in self.rows.iter_mut() {
            row.resize(width, fg, bg);
        }

        self.width = width;
        self.height = height;
    }

    pub fn set_cell(&mut self, cur: Cursor, cell: GridCell) {
        self.rows[cur.row].cols[cur.col] = cell;
        self.rows[cur.row].dirty = true;
    }

    pub fn cell(&self, row: usize, col: usize) -> &GridCell {
        &self.rows[row].cols[col]
    }

    pub fn scroll_once(&mut self, fg: Color, bg: Color) {
        self.scrollback.push_front(self.rows[0].clone());
        if self.scrollback.len() >= self.scrollback_limit {
            self.scrollback.pop_back();
        }

        for i in 1..self.height {
            self.rows[i - 1] = self.rows[i].clone();
            self.rows[i - 1].dirty = true;
        }
        self.rows[self.height - 1] = GridRow::new(self.width, fg, bg);
    }

    pub fn erase_row(&mut self, row: usize, fg: Color, bg: Color) {
        self.rows[row].clear(fg, bg);
    }

    pub fn set_row_dirty(&mut self, row: usize) {
        if row >= self.rows.len() {
            return;
        }
        self.rows[row].dirty = true;
    }

    pub fn invalidate_rows(&mut self, scroll: usize) {
        self.iter_rows_mut(scroll, |_, row| row.dirty = true);
    }
}

impl State {
    pub fn new(width: usize, height: usize) -> Self {
        let config = &*CONFIG;
        let default_attributes = CellAttributes {
            fg: config.colors.dim.white,
            bg: config.colors.dim.black,
            inverse: false,
            bold: false,
            italic: false,
            underlined: false,
            strikethrough: false,
        };

        Self {
            buffer: Buffer::new(width, height, default_attributes.fg, default_attributes.bg),
            utf8_decode: Utf8Decoder::default(),

            escape: EscapeParser::Normal,

            cursor: Cursor { row: 0, col: 0 },
            cursor_visible: true,
            alternate: false,
            scroll: 0,
            saved_cursor: None,

            default_attributes,
            attributes: default_attributes,
            fg_index: Some(7),
            cursor_style: CursorStyle::Block(true),
            cursor_dirty: false,
        }
    }

    fn update_fg_color(&mut self) {
        if let Some(fg_index) = self.fg_index {
            self.attributes.fg = Color::from_escape(&CONFIG, self.attributes.bold, fg_index)
                .unwrap_or(self.default_attributes.fg);
        }
    }

    pub fn clear(&mut self) {
        self.buffer
            .clear(self.default_attributes.fg, self.attributes.bg);
        self.cursor_dirty = true;
    }

    pub fn resize(&mut self, width: usize, height: usize) {
        self.buffer.resize(
            width,
            height,
            self.default_attributes.fg,
            self.default_attributes.bg,
        );
        self.scroll = 0;
        self.cursor_dirty = true;

        if self.cursor.row >= height {
            self.cursor.row = height - 1;
        }

        if self.cursor.col >= width {
            self.cursor.col = width - 1;
        }
    }

    fn adjust_cursor(&mut self) -> bool {
        let mut redraw = false;

        if self.alternate {
            self.cursor.col = self.cursor.col.min(self.buffer.width - 1);
            self.cursor.row = self.cursor.row.min(self.buffer.height - 1);
        }

        if self.cursor.col >= self.buffer.width {
            if self.cursor.row < self.buffer.height {
                self.buffer.rows[self.cursor.row].dirty = true;
            }

            self.cursor.row += 1;
            self.cursor.col = 0;
            redraw = true;
        }

        while self.cursor.row >= self.buffer.height {
            self.buffer
                .scroll_once(self.default_attributes.fg, self.default_attributes.bg);
            self.cursor.row -= 1;
            redraw = true;
        }

        redraw
    }

    fn handle_character_attribute(&mut self, attr: CharacterAttribute) {
        match attr {
            CharacterAttribute::Reset => {
                self.attributes = self.default_attributes;
                self.fg_index = Some(7);
            }
            CharacterAttribute::Bold => {
                self.attributes.bold = true;
                self.update_fg_color();
            }
            CharacterAttribute::Faint | CharacterAttribute::Normal => {
                self.attributes.bold = false;
                self.update_fg_color();
            }
            CharacterAttribute::Hidden(_set) => (),
            CharacterAttribute::Inverse(set) => {
                self.attributes.inverse = set;
            }
            CharacterAttribute::Blinking(_set) => (),
            CharacterAttribute::Italicized(set) => {
                self.attributes.italic = set;
            }
            CharacterAttribute::Underlined(set) => {
                self.attributes.underlined = set;
            }
            CharacterAttribute::CrossedOut(set) => {
                self.attributes.strikethrough = set;
            }

            CharacterAttribute::DefaultFgBg => todo!(),
            CharacterAttribute::FgIndex(index) => {
                self.fg_index = Some(index.unwrap_or(7) as u32);
                self.update_fg_color();
            }

            CharacterAttribute::BgIndex(index) => {
                self.attributes.bg = Color::from_escape(&CONFIG, false, index.unwrap_or(0) as u32)
                    .unwrap_or(self.default_attributes.bg);
            }
            CharacterAttribute::ForegroundRgb(r, g, b) => {
                self.fg_index = None;
                self.attributes.fg = Color::new(r, g, b);
                self.update_fg_color();
            }
            CharacterAttribute::BackgroundRgb(r, g, b) => {
                self.attributes.bg = Color::new(r, g, b);
            }
        }
    }

    fn handle_control_sequence(&mut self, ctlseq: ControlSequence) -> bool {
        match ctlseq {
            ControlSequence::SetCursorPosition(row, col) => {
                let row = row.clamp(1, self.buffer.height as u32) - 1;
                let col = col.clamp(1, self.buffer.width as u32) - 1;

                self.buffer.set_row_dirty(self.cursor.row);
                self.cursor = Cursor {
                    row: row as _,
                    col: col as _,
                };
                self.cursor_dirty = true;

                true
            }
            ControlSequence::MoveCursorDown(times) => {
                log::warn!("MoveCursorDown {times}");
                false
            }
            ControlSequence::MoveCursorUp(times) => {
                log::warn!("MoveCursorUp {times}");
                false
            }
            ControlSequence::MoveCursorForward(times) => {
                log::warn!("MoveCursorForward {times}");
                false
            }
            // TODO Move cursor back
            // if self.cursor.col > 0 {
            //     self.buffer.set_row_dirty(self.cursor.row);
            //     self.cursor.col -= 1;
            //     true
            // } else {
            //     false
            // }
            ControlSequence::SetCursorStyle(style) => {
                self.cursor_style = style;
                true
            }
            ControlSequence::SetApplicationCursor(_set) => false,
            ControlSequence::SetScrollingRegion(top, bottom) => {
                log::warn!("SetScrollingRegion {top}, {bottom}");
                false
            }

            ControlSequence::SetCharacterAttribute(attr) => {
                self.handle_character_attribute(attr);
                false
            }
            ControlSequence::InsertLines(times) => {
                log::warn!("InsertLines {times}");
                false
            }
            ControlSequence::DeleteLines(times) => {
                log::warn!("DeleteLines {times}");
                false
            }
            ControlSequence::EraseInLine(erase) => match erase {
                EraseInLine::ToRight => {
                    self.buffer.rows[self.cursor.row].erase_to_right(
                        self.cursor.col,
                        self.default_attributes.fg,
                        self.attributes.bg,
                    );
                    true
                }
                EraseInLine::All => {
                    self.buffer.erase_row(
                        self.cursor.row,
                        self.default_attributes.fg,
                        self.attributes.bg,
                    );
                    true
                }
                _ => false,
            },
            ControlSequence::EraseInDisplay(erase) => match erase {
                EraseInDisplay::All => {
                    self.buffer
                        .clear(self.default_attributes.fg, self.attributes.bg);
                    self.cursor_dirty = true;
                    true
                }
                EraseInDisplay::Below => false,
                EraseInDisplay::Above => false,
                EraseInDisplay::SavedLines => false,
            },
            ControlSequence::EraseCharacters(times) => {
                log::warn!("EraseCharacters {times}");
                false
            }

            ControlSequence::SetAlternateKeypad(_set) => false,
            ControlSequence::SetAlternateMode(set) => {
                if self.alternate && !set {
                    // Leave alternate mode
                    self.clear();
                    self.cursor = Cursor { col: 0, row: 0 };
                    self.cursor_dirty = true;
                }
                self.alternate = set;

                true
            }
            ControlSequence::SetBracketedPasteMode(_set) => false,

            ControlSequence::SetWindowParameter(param, value) => {
                log::warn!("SetWindowParameter {param:?} {value}");
                false
            }

            ControlSequence::SendDeviceAttributes(query) => {
                log::warn!("SendDeviceAttributes {query:#x?}");
                false
            }
            ControlSequence::QueryKeyModifiers(query) => {
                log::warn!("QueryKeyModifiers {query:?}");
                false
            }
            ControlSequence::Decrqm => {
                log::warn!("Decrqm");
                false
            }
            ControlSequence::DeviceStatusReport => {
                log::warn!("DeviceStatusReport");
                false
            }
            ControlSequence::ReportCursorPosition => {
                log::warn!("ReportCursorPosition");
                false
            }
        }
    }

    pub fn handle_shell_output(&mut self, ch: u8) -> bool {
        let Some(ch) = self.utf8_decode.push(ch) else {
            return false;
        };
        let Some(input) = self.escape.push(ch) else {
            return false;
        };

        let redraw = match input {
            TerminalInput::Bell => false,
            TerminalInput::Delete => {
                log::warn!("TODO: Delete");
                false
            }
            TerminalInput::Backspace => {
                if self.cursor.col > 0 {
                    self.cursor.col -= 1;
                } else if self.cursor.row > 0 {
                    self.cursor.row -= 1;
                    self.cursor.col = 0;
                }
                true
            }
            TerminalInput::NewPage => {
                if !self.alternate {
                    self.clear();
                }
                !self.alternate
            }
            TerminalInput::NewLine | TerminalInput::VerticalTab => {
                self.buffer.rows[self.cursor.row].dirty = true;
                self.cursor.row += 1;
                self.cursor.col = 0;
                true
            }
            TerminalInput::CarriageReturn => {
                self.buffer.rows[self.cursor.row].dirty = true;
                self.cursor.col = 0;
                true
            }
            TerminalInput::Tab => {
                // TODO
                false
            }
            TerminalInput::Character(ch) => {
                self.buffer
                    .set_cell(self.cursor, GridCell::new(ch, self.attributes));
                self.cursor.col += 1;
                true
            }
            TerminalInput::Control(ctlseq) => self.handle_control_sequence(ctlseq),
        };

        self.adjust_cursor() | redraw
    }

    pub fn invalidate_current_viewport(&mut self) {
        self.buffer.invalidate_rows(self.scroll);
    }

    pub fn adjust_scroll(&mut self) -> usize {
        if self.scroll > self.buffer.scrollback.len() {
            self.scroll = self.buffer.scrollback.len();
        }

        self.scroll
    }

    pub fn scroll_up(&mut self) -> bool {
        let max = self.buffer.scrollback.len();
        if max == 0 {
            self.scroll = 0;
            return true;
        }
        let amount = max.min(8);
        if amount != 0 {
            self.scroll += amount;
            self.invalidate_current_viewport();
            true
        } else {
            false
        }
    }

    pub fn scroll_down(&mut self) -> bool {
        let amount = self.scroll.min(8);
        if amount != 0 {
            self.scroll -= amount;
            self.invalidate_current_viewport();
            true
        } else {
            false
        }
    }

    pub fn scroll_home(&mut self) -> bool {
        if self.scroll != self.buffer.scrollback.len() {
            self.scroll = self.buffer.scrollback.len();
            self.invalidate_current_viewport();
            true
        } else {
            false
        }
    }

    pub fn scroll_end(&mut self) -> bool {
        if self.scroll != 0 {
            self.scroll = 0;
            self.invalidate_current_viewport();
            true
        } else {
            false
        }
    }
}
