/*
 * Copyright (c) 2022, Able <able@ablecorp.us>
 *
 * SPDX-License-Identifier: MPL-2.0
 */
#![deny(missing_docs)]
//! The VTerm is a terminal with nice

use crate::{hardware::MOUSE, vga_e::VGAE};
use ab_glyph::{Font, FontRef, Glyph};
use logos::{Lexer, Logos};
use spin::Lazy;
use vga::{colors::Color16, writers::GraphicsWriter};

const TERM_MINUS_ONE_LINE: usize = 4720;
const CURSOR_COLOR: Color16 = Color16::Cyan;

#[derive(Debug)]
/// A VTerm
pub struct VTerm {
    dirty: bool,
    color: Color16,
    term: [(char, Color16); 80 * 60],
    back_buffer: [u8; 640 * 480],
    x: u8,
}
impl VTerm {
    /// Construct a new VTerm
    pub fn new() -> Self {
        let mode = VGAE.lock();
        mode.set_mode();
        // let fb = mode.get_frame_buffer();
        drop(mode);

        Self {
            dirty: false,
            x: 0,
            back_buffer: [0; 640 * 480],
            term: [('\0', Color16::DarkGrey); 80 * 60],
            color: Color16::White,
        }
    }
    /// Check the dirty state of the VTerm
    pub fn is_dirty(&self) -> bool {
        self.dirty
    }

    /// Set the dirty state of the VTerm
    pub fn set_dirty(&mut self, dirty: bool) {
        self.dirty = dirty
    }

    /// Append a &str to the VTerm
    pub fn print(&mut self, data: &str) {
        let mut lex = Token::lexer(data);

        for toke in lex {
            match toke {
                Token::Reset => {
                    self.color = Color16::White;
                }
                Token::Error => {}
                Token::TBlack => self.color = Color16::Black,
                Token::TBlue => self.color = Color16::Blue,
                Token::TGreen => self.color = Color16::Green,
                Token::TCyan => self.color = Color16::Cyan,
                Token::TRed => self.color = Color16::Red,
                Token::TMagenta => self.color = Color16::Magenta,
                Token::TBrown => self.color = Color16::Brown,
                Token::TLightGrey => self.color = Color16::LightGrey,
                Token::TDarkGrey => self.color = Color16::DarkGrey,
                Token::TLightBlue => self.color = Color16::LightBlue,
                Token::TLightGreen => self.color = Color16::LightGreen,
                Token::TLightCyan => self.color = Color16::LightCyan,
                Token::TLightRed => self.color = Color16::LightRed,
                Token::TPink => self.color = Color16::Pink,
                Token::TYellow => self.color = Color16::Yellow,
                Token::TWhite => self.color = Color16::White,
                // Token::Space => {
                //     self.term[TERM_MINUS_ONE_LINE + (self.x as usize)] = (' ', self.color);
                //     self.x += 1;
                // }
                Token::Text(st) => {
                    for c in st.chars() {
                        if self.x == 80 {
                            // trace!("X too big moving up");
                            self.move_up();
                        }
                        // trace!("C");
                        match c {
                            '\0' => {
                                self.term[TERM_MINUS_ONE_LINE + (self.x as usize)] =
                                    (c, self.color);
                                self.x += 1;
                            }

                            '\u{08}' => {
                                if self.x == 0 {
                                    trace!("IMPOSSIBLE BACKSPACE");
                                    return;
                                }
                                self.x -= 1;
                                self.term[TERM_MINUS_ONE_LINE + (self.x as usize)] =
                                    ('\0', Color16::LightGrey);
                            }
                            '\n' => {
                                self.move_up();
                                self.x = 0;
                            }

                            c => {
                                self.term[TERM_MINUS_ONE_LINE + (self.x as usize)] =
                                    (c, self.color);
                                self.x += 1;
                            }
                        }
                    }
                }
            }
        }
    }

    /// Move the VTerm up by one line
    pub fn move_up(&mut self) {
        self.term.rotate_left(80);
        for x in 0..80 {
            self.term[TERM_MINUS_ONE_LINE + x] = ('\0', Color16::DarkGrey);
        }
        self.x = 0;
    }

    /// Redraw the VTerm to the VGA buffer
    pub fn draw_term(&mut self) {
        if self.is_dirty() {
            // trace!("Redrawing");
            use Color16::*;
            let mode = VGAE.lock();
            mode.clear_screen(DarkGrey);
            let mut x = 0;
            let mut y = 0;

            for c in self.term {
                mode.draw_character(x * 8, y * 8, c.0, c.1);
                // mode.draw_unicode_char(x, y, c.0, c.1);
                if x == 79 {
                    y += 1;

                    x = 0;
                } else {
                    x += 1
                }
            }

            {
                let mouse_coord = x86_64::instructions::interrupts::without_interrupts(|| {
                    let cursor = MOUSE.lock();

                    (cursor.get_x() as usize, cursor.get_y() as usize)
                });

                mode.draw_line(
                    (mouse_coord.0 as isize + 0, mouse_coord.1 as isize + 0),
                    (mouse_coord.0 as isize + 10, mouse_coord.1 as isize + 10),
                    CURSOR_COLOR,
                );
                mode.draw_line(
                    (mouse_coord.0 as isize + 0, mouse_coord.1 as isize + 0),
                    (mouse_coord.0 as isize + 5, mouse_coord.1 as isize + 0),
                    CURSOR_COLOR,
                );
                mode.draw_line(
                    (mouse_coord.0 as isize + 0, mouse_coord.1 as isize + 0),
                    (mouse_coord.0 as isize + 0, mouse_coord.1 as isize + 5),
                    CURSOR_COLOR,
                );
            }

            drop(mode);

            /*
            let mut x = 0;
            let mut y = 0;
            for c in ['b'; 80 * 60]
            // "abcdefghijklmnopqrstuvwxyzabcdefghijk\nabcd jkjhgkjhgkjhgefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz12"
            // .chars()
            {
                if c == '\n' {
                    y += 1;
                    x = 0;
                    continue;
                }

                self.draw_char(x, y, c, Color16::Red);
                if x == 80 {
                    y += 1;
                    x = 1;
                } else {
                    x += 1;
                }
            }
            */
            self.set_dirty(false);
            // trace!("Finished drawing");
        }
    }
}

#[derive(Logos, Debug, Clone, PartialEq)]
enum Token {
    #[regex(r"", logos::skip)]
    #[error]
    Error,

    #[token("\0RESET\0", priority = 10)]
    Reset,

    #[token("\0BLACK\0", priority = 10)]
    TBlack,
    #[regex("\0BLUE\0", priority = 10)]
    TBlue,
    #[token("\0GREEN\0", priority = 10)]
    TGreen,
    #[token("\0CYAN\0", priority = 10)]
    TCyan,
    #[token("\0RED\0", priority = 10)]
    TRed,
    #[token("\0MAGENTA\0", priority = 10)]
    TMagenta,
    #[token("\0BROWN\0", priority = 10)]
    TBrown,
    #[token("\0LIGHTGREY\0", priority = 10)]
    TLightGrey,
    #[token("\0DARKGREY\0", priority = 10)]
    TDarkGrey,
    #[token("\0LIGHTBLUE\0", priority = 10)]
    TLightBlue,
    #[token("\0LIGHTGREEN\0", priority = 10)]
    TLightGreen,
    #[token("\0LIGHTCYAN\0", priority = 10)]
    TLightCyan,
    #[token("\0LIGHTRED\0", priority = 10)]
    TLightRed,
    #[token("\0PINK\0", priority = 10)]
    TPink,
    #[token("\0YELLOW\0", priority = 10)]
    TYellow,
    #[token("\0WHITE\0", priority = 10)]
    TWhite,

    // #[token(" ")]

    // Space,
    #[regex("[\t\n\u{8}█!-\u{10FFFF} ]+", text_lex, priority = 0)]
    /// A printable string
    Text(String),
}

fn text_lex(lex: &mut Lexer<Token>) -> String {
    lex.slice().into()
}
