//! A basic console driver, using the VGA text-mode buffer.

use core::fmt;
use core::ptr::Unique;
use spin::Mutex;
use volatile::Volatile;
use x86::shared::io;

pub static WRITER: Mutex<Writer> = Mutex::new(Writer {
    column_pos: 0, row_pos: 0,
    style: CharStyle::new(Color::Cyan, Color::DarkGray),
    buffer: unsafe {Unique::new_unchecked(0xb8000 as *mut _)},
});

#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
#[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,
}

/// Foreground + background color pair for a VGA console cell.
#[derive(Clone, Copy, Debug)]
pub struct CharStyle(u8);
impl CharStyle {
    pub const fn new(foreground: Color, background: Color) -> CharStyle {
        CharStyle((background as u8) << 4 | (foreground as u8))
    }
}

/// Character/style pair.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
struct VgaChar {
    ascii_char: u8,
    style: CharStyle,
}

const BUFFER_HEIGHT: usize = 25;
const BUFFER_WIDTH: usize = 80;

/// Shadows the actual VGA text-mode buffer at `0xb8000`.
struct Buffer {
    chars: [[Volatile<VgaChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}

/// Console-oriented abstraction layer used to write into the VGA text buffer.
/// Maintains a cursor and current style.
pub struct Writer {
    /// Current position of the 'write cursor'
    row_pos: usize, column_pos: usize,
    /// Current style we're writing with
    style: CharStyle,
    /// This is set up on initialization to shadow `0xb8000`, the VGA text-mode buffer.
    buffer: Unique<Buffer>,
}

#[allow(dead_code)]
impl Writer {
    /// Write a single byte into the VGA buffer at the cursor location.
    /// Increments the cursor location and wraps to the next line if necessary.
    pub fn write_byte(&mut self, byte: u8) {
        match byte {
            b'\n' => self.new_line(),
            byte => {
                if self.column_pos >= BUFFER_WIDTH {self.new_line();}

                let row = self.row_pos;
                let col = self.column_pos;
                let style = self.style;

                self.buffer().chars[row][col].write(VgaChar {
                    ascii_char: byte,
                    style: style,
                });

                self.column_pos += 1;
            }
        }
    }

    /// Write a single byte at (row, col).
    pub fn write_byte_at(&mut self, byte: u8, row: usize, col: usize) {
        self.row_pos = row; self.column_pos = col;
        let style = self.style;

        self.buffer().chars[row][col].write(VgaChar {ascii_char: byte, style: style});
    }

    /// Clear the VGA buffer.
    pub fn clear_screen(&mut self) {
        let clear_style = VgaChar {ascii_char:  b' ', style: self.style};
        for row in 0..BUFFER_HEIGHT {
            for col in 0..BUFFER_WIDTH {
                self.buffer().chars[row][col].write(clear_style);
            }
        }
    }

    pub fn set_style(&mut self, style: CharStyle) {
        self.style = style;
    }

    pub fn get_style(&self) -> CharStyle {
        self.style
    }

    /// Move the _console cursor_ (blinky bar) to (row, col).
    fn move_cursor(&mut self, row: usize, col: usize) {
        let pos: u16 = ((row * 80) + col) as u16;
        // Lovingly ripped off from wiki.osdev.org/Text_Mode_Cursor
        unsafe {
            io::outb(0x3d4, 0x0F);
            io::outb(0x3d5, (pos & 0xff) as u8);

            io::outb(0x3d4, 0x0e);
            io::outb(0x3d5, ((pos>>8) & 0xff) as u8);
        }
    }

    /// Move the internal cursor to a new line, scrolling if necessary.
    fn new_line(&mut self) {
        self.column_pos = 0;
        self.row_pos += 1;

        if self.row_pos >= BUFFER_HEIGHT {
            self.scroll();
        }
    }

    /// Clear a `row` of the VGA buffer.
    fn clear_row(&mut self, row: usize) {
        let clear_style = VgaChar {ascii_char:  b' ', style: self.style};
        for col in 0..BUFFER_WIDTH {
            self.buffer().chars[row][col].write(clear_style);
        }
    }

    /// Scroll the buffer up by one row.
    fn scroll(&mut self) {
        for row in 0..(BUFFER_HEIGHT - 1) {
            for col in 0..BUFFER_WIDTH {
                let c = self.buffer().chars[row + 1][col].read();
                self.buffer().chars[row][col].write(c);
            }
        }

        self.clear_row(BUFFER_HEIGHT - 1);
        self.column_pos = 0;
        self.row_pos = BUFFER_HEIGHT - 1;
    }

    /// Get the underlying buffer.
    fn buffer(&mut self) -> &mut Buffer {
        unsafe { self.buffer.as_mut() }
    }
}

impl fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for byte in s.bytes() {
            self.write_byte(byte);
        }

        let row = self.row_pos;
        let col = self.column_pos;
        self.move_cursor(row, col);

        Ok(())
    }
}