use core::error::Error;
use core::fmt;
use core::fmt::{Display, Formatter, Write};
use crate::drivers::tty::vga::VGATTYDriver;
use crate::ttys;

pub fn _print(args: fmt::Arguments) {
    ttys.lock().write_fmt(args).unwrap();
    ttys.lock().update().unwrap();
}

pub struct TTY<const C: usize> {
    pub ttys: [VirtualTTY; C],
    pub selected: usize,
    driver: VGATTYDriver
}
impl<const C: usize> TTY<C> {
    pub fn new() -> TTY<C> {
        Self {
            ttys: [
                VirtualTTY::new(); C
            ],
            selected: 0,
            driver: VGATTYDriver {}
        }
    }

    pub fn select(&mut self, new: usize) { self.selected = new; }

    pub fn update(&self) -> Result<(), TTYDriverError> {
        let selected_tty = &self.ttys[self.selected];
        for (row_num, row) in selected_tty.screen_buffer.iter().enumerate() {
            for (col_num, col) in row.iter().enumerate() {
                self.driver.set_char(row_num, col_num, *col)?;
            }
        }
        Ok(())
    }

    pub fn handle(&self) -> &VirtualTTY {
        &self.ttys[self.selected]
    }

    pub fn handle_mut(&mut self) -> &mut VirtualTTY {
        &mut self.ttys[self.selected]
    }

    pub fn select_and_update(&mut self, new: usize) -> Result<(), TTYDriverError> {
        self.select(new);
        self.update()
    }
}


#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Color {
    Black = 0,
    Blue = 1,
    Green = 2,
    Cyan = 3,
    Red = 4,
    Magenta = 5,
    Brown = 6,
    LightGray = 7,
    DarkGray = 8,
    LightBlue = 9,
    LightGreen = 10,
    LightCyan = 11,
    LightRed = 12,
    Pink = 13,
    Yellow = 14,
    White = 15,
}

#[derive(Copy, Clone)]
pub struct TTYCharacter {
    pub format: u8,
    pub char: char
}

impl TTYCharacter {
    pub fn blank() -> Self { Self { format: 0u8, char: ' ' } }
    pub fn from(char: char) -> Self { Self { format: 0x0fu8, char }}
    pub fn new(char: char, fg: Color, bg: Color) -> Self {
        Self {
            format: (bg as u8) << 4 | fg as u8,
            char
        }
    }
}

pub const ROWS_ON_SCREEN: usize = 25;
pub const COLS: usize = 80;

pub const ROWS_IN_TTY: usize = 80;

#[derive(Copy, Clone)]
pub struct VirtualTTY {
    full_buffer: VirtualTTYBuffer<ROWS_IN_TTY, COLS>,
    full_buffer_copy: VirtualTTYBuffer<ROWS_IN_TTY, COLS>,
    screen_buffer: VirtualTTYBuffer<ROWS_ON_SCREEN, COLS>,
    line_offset: usize,
    cursor_col: usize,
    cursor_row: usize,
    current_color_fg: Color,
    current_color_bg: Color
}
impl Default for VirtualTTY {
    fn default() -> Self {
        Self {
            full_buffer: [[TTYCharacter::blank(); COLS]; ROWS_IN_TTY],
            full_buffer_copy: [[TTYCharacter::blank(); COLS]; ROWS_IN_TTY],
            screen_buffer: [[TTYCharacter::blank(); COLS]; ROWS_ON_SCREEN],
            line_offset: ROWS_IN_TTY - ROWS_ON_SCREEN, // assume we are scrolled all the way down
            cursor_row: ROWS_IN_TTY - ROWS_ON_SCREEN,
            current_color_fg: Color::White,
            cursor_col: 0,
            current_color_bg: Color::Black,
        }
    }
}
impl VirtualTTY {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn set_color(&mut self, fg: Color, bg: Color) {
        self.current_color_bg = bg;
        self.current_color_fg = fg;
    }

    pub fn setcur(&mut self, cursor_x: usize, cursor_y: usize) {
        self.cursor_row = cursor_y;
        self.cursor_col = cursor_x;
    }

    pub fn putchar(&mut self, character: char) {
        match character {
            '\n' => {
                self.print_newline();
                self.cursor_col = 0;
                self.cursor_row += 1;
            },
            _ => {
                if self.cursor_col > COLS {
                    self.print_newline();
                    self.cursor_col = 0;
                    self.cursor_row += 1;
                }
                self._putchar(character);
                self.cursor_col += 1;
            }
        }
    }

    pub fn putstr(&mut self, str: &str) {
        for char in str.chars() {
            self.putchar(char);
        }
    }

    fn _putchar(&mut self, character: char) {
        self.full_buffer[self.cursor_row][self.cursor_col] = TTYCharacter::new(character, self.current_color_fg, self.current_color_bg);
        self.update_screenbuf()
    }

    // shifts everything up one
    fn print_newline(&mut self) {
        for (line_no, line) in self.full_buffer.iter().skip(1).enumerate() {
            self.full_buffer_copy[line_no] = *line;
        }
        self.full_buffer = self.full_buffer_copy;
    }

    pub fn set_screen_offset(&mut self, new_offset: usize) -> Result<(), ()> {
        if new_offset > (ROWS_IN_TTY - ROWS_ON_SCREEN) {
            return Err(())
        }
        self.line_offset = new_offset;
        self.update_screenbuf();
        Ok(())
    }

    fn update_screenbuf(&mut self) {
        for (line_no, line) in self.full_buffer.iter().skip(self.line_offset).enumerate() {
            self.screen_buffer[line_no] = *line;
        }
    }
}

type VirtualTTYBuffer<const R: usize, const C: usize> = [[TTYCharacter; C]; R];

#[derive(Debug)]
pub enum TTYDriverError {
    RowOutOfBounds { got: usize },
    ColOutOfBounds { got: usize },
    CannotScrollTooFar
}
impl Display for TTYDriverError {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::RowOutOfBounds { got } => write!(f, "Character write out of bounds: row {} too high", got),
            Self::ColOutOfBounds { got } => write!(f, "Character write out of bounds: col {} too high", got),
            Self::CannotScrollTooFar => write!(f, "Cannot scroll outside line buffer")
        }
    }
}
impl Error for TTYDriverError {}

pub trait TTYDriver {
    fn set_char(&self, row: usize, col: usize, char: TTYCharacter) -> Result<(), TTYDriverError>;
}

impl<const C: usize> Write for TTY<C> {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.handle_mut().putstr(s);
        Ok(())
    }
}