/// VGA text buffer module.

pub mod color;

#[cfg(test)]
mod test;

pub use self::color::Color;
pub use self::color::VgaColorCode;

pub use core::ptr::Unique;
pub use spin::Mutex;

/// Height of the VGA text buffer.
pub const BUFFER_HEIGHT: usize = 25;
/// Width of the VGA text buffer.
pub const BUFFER_WIDTH: usize = 80;
/// The location of the VGA text buffer.
pub const VGA_BUFFER_LOCATION: *mut Buffer = (0xB8000 as *mut Buffer);

/// Represents a VGA screen character.
///
/// Each VGA screen character is a 2-byte value which is broken down into two 1-byte halves:
///     - The top byte is for the ASCII character.
///     - The bottom byte is for the VGA color code.
#[repr(C)]
#[derive(Clone, Copy, PartialEq)]
pub struct ScreenChar {
    ascii_character: u8,
    color_code: VgaColorCode,
}

/// Represents a VGA buffer.
///
/// Even though the VGA display is a two-dimensional `BUFFER_WIDTH` x `BUFFER_HEIGHT` char display,
/// the VGA text buffer is just a one-dimensional array of size `BUFFER_WIDTH` x `BUFFER_HEIGHT`.
pub struct Buffer {
    chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT],
}

/// VGA text buffer writer. Use this struct for rudimentary writes to the VGA text buffer.
///
/// This struct keeps track of the current row and current column of the cursor, as well as the
/// current color code. It also owns the pointer to the VGA text buffer at `0xB8000`.
///
/// This struct is not threadsafe.
pub struct Writer {
    row_position: usize,
    column_position: usize,
    color_code: VgaColorCode,
    buffer: Unique<Buffer>,
}

impl Writer {
    /// Writes a byte with the current color code to the current row and column of the VGA text
    /// buffer.
    ///
    /// Once a byte is written, `self.column_position` is incremented. If `self.column_position` is
    /// greater than the width of our buffer, we set it to zero and increment `self.row_position.`
    /// If `self.row_position` is greater than the height of our buffer, we move all rows up by one
    /// (destroying the data in the first row) and set `self.row_position` to the last row and set
    /// `self.column_position` to 0.
    ///
    /// Handles standard alphanumeric characters as well as symbols.
    /// Handles \n and \r.
    ///
    /// # Arguments
    ///
    /// * `byte` - The character to be written to the buffer at the current color, row, and column.
    ///
    /// # Invariants
    ///
    /// * 0 <= `self.row_position` < `BUFFER_HEIGHT`
    /// * 0 <= `self.column_position` < `BUFFER_WIDTH`
    /// * `pre.self.column_position` == `post.self.column_position`
    ///     || `post.self.column_position` == 0
    /// * 0 <= `post.self.row_position` - `pre.self.row_position` <= 1
    pub fn write_byte(&mut self, byte: u8) {
        match byte {
            // For newlines, just increment the `row_position.`
            // If it goes off screen, then `scroll()` instead.
            b'\n' => {
                // This looks weird, but parse this as "if the next line is greater than the last
                // line".
                if self.row_position + 1 > BUFFER_HEIGHT - 1 {
                    self.scroll();
                } else {
                    self.row_position += 1;
                }
            },
            // Carriage returns.
            b'\r' => self.column_position = 0,
            // Everything else. Just write the char and increment `column_position.`
            // Increment `row_position` if necessary. Scroll if necessary after that.
            _  => {
                if self.column_position > BUFFER_WIDTH - 1 {
                    self.row_position += 1;
                    self.column_position = 0;
                }

                if self.row_position > BUFFER_HEIGHT - 1 {
                    self.scroll();
                }

                // Write the char.
                self.buffer().chars[self.row_position][self.column_position] = ScreenChar {
                    ascii_character: byte,
                    color_code: self.color_code,
                };
                self.column_position += 1;
            }
        }
    }

    /// Sets the position of the cursor. If the arguments are outside of the VGA buffer bounds
    /// (`BUFFER_HEIGHT` and `BUFFER_WIDTH` are the respective max values, while 0 is the min value
    /// for both), then we just default to the closest valid value.
    ///
    /// # Arguments
    ///
    /// * `row_position` - The row position to be set to.
    /// * `column_position` - The column position to be set to.
    ///
    /// # Invariants
    ///
    /// * 0 <= `self.row_position` < `BUFFER_HEIGHT`
    /// * 0 <= `self.column_position` < `BUFFER_WIDTH`
    pub fn set_position(&mut self, row_position: usize, column_position: usize) {
        // TODO(mgazzola) Write some min/max macros.
        if row_position >= BUFFER_HEIGHT {
            self.row_position = BUFFER_HEIGHT - 1;
        } else {
            self.row_position = row_position;
        }

        if column_position >= BUFFER_WIDTH {
            self.column_position = BUFFER_WIDTH - 1;
        } else {
            self.column_position = column_position;
        }
    }

    /// Clears the entire screen. Just calls `clear_row` for all rows. Then, sets the cursor
    /// position to (0, 0).
    ///
    /// # Invariants
    ///
    /// * `post.self.row_position` == `post.self.column_position` == 0
    pub fn clear_screen(&mut self) {
        for row in 0..BUFFER_HEIGHT {
            self.clear_row(row);
        }
        self.set_position(0, 0);
    }

    /// `write_byte()`'s each byte in a specified string.
    ///
    /// # Arguments
    ///
    /// * 's' - The string to write to the screen.
    pub fn write_str(&mut self, s: &str) {
        for byte in s.bytes() {
            self.write_byte(byte);
        }
    }

    pub fn set_color(&mut self, fg: Color, bg: Color) {
        self.color_code = VgaColorCode::new(fg, bg);
    }

    /// Returns the mutable buffer pointer.
    fn buffer(&mut self) -> &mut Buffer 


    /// Scrolls the entire screen by setting each row to be the row below it. This destroys the
    /// first 80 characters of data that existed before this function was called. This function
    /// also clears the final row of the text buffer (the last 80 characters of data). Afterwards,
    /// we set the `column_position` to 0 and the `row_position` to the final row.
    ///
    /// # Invariants
    ///
    /// * `post.self.column_position` = 0
    /// * `post.self.row_position` = `BUFFER_HEIGHT` - 1
    fn scroll(&mut self) {
        for row in 0..(BUFFER_HEIGHT - 1) {
            self.buffer().chars[row] = self.buffer().chars[row + 1];
        }

        self.clear_row(BUFFER_HEIGHT - 1);
        self.column_position = 0;
        self.row_position = BUFFER_HEIGHT - 1;
    }

    /// Sets all data in row `row` to just be whitespace. Uses the current color code.
    ///
    /// # Arguments
    ///
    /// * `row` - The row to clear.
    fn clear_row(&mut self, row: usize) {
        let blank_char = ScreenChar {
            ascii_character: b' ',
            color_code: self.color_code,
        };
        self.buffer().chars[row] = [blank_char; BUFFER_WIDTH];
    }
}

/// Implementation of the `Write` trait for the `Writer` struct.
impl ::core::fmt::Write for Writer {
    /// Required.
    ///
    /// Implements the `Write` `write_str` method.
    fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
        self.write_str(s);
        Ok(())
    }
}

/// Defines the *one-and-only* writer that should exist.
/// This method is both public and thread-safe. Use this to access the writer.
/// Defaults the writer to Light Green foreground text and Black background.
pub static WRITER: Mutex<Writer> = Mutex::new(Writer {
    row_position: 0,
    column_position: 0,
    color_code: VgaColorCode::new(Color::LightGreen, Color::Black),
    buffer: unsafe {
        Unique::new(VGA_BUFFER_LOCATION)
    },
});

/// Macro for `print`.
macro_rules! print {
    ($($arg:tt)*) => ({
        use core::fmt::Write;
        let mut writer = $crate::drivers::vga::WRITER.lock();
        writer.write_fmt(format_args!($($arg)*)).unwrap();
    })
}

/// Macro for `println`.
macro_rules! println {
    ($fmt:expr) => (print!(concat!($fmt, "\n\r")));
    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n\r"), $($arg)*));
}

{
        unsafe {
            self.buffer.get_mut()
        }
    }