use core::fmt::{self, Write};
use spin::{Lazy, Mutex};
use x86_64::instructions::interrupts;

use crate::arch::x86_64::drivers::framebuffer::{
    colour::Colour, display::FRAMEBUFFER_WRITER,
};

use crate::resources::font::{FONT_SPLEEN_8X16, Font};
use crate::std::maths::geometry::Vec2;

static FONT_WIDTH: u32 = 8;
static FONT_HEIGHT: u32 = 16;

pub static WRITER: Lazy<Mutex<Writer>> =
    Lazy::new(|| Mutex::new(Writer::new()));

pub fn screensize_chars() -> (u32, u32) {
    let writer = WRITER.lock();
    (writer.screen_width, writer.screen_height)
}

pub struct Writer {
    font: &'static Font,
    /// Measured in chars not pixels.
    screen_width: u32,
    /// Measured in chars not pixels.
    screen_height: u32,
    text_line: u32,
    text_col: u32,
    fg_color: Colour,
    bg_color: Colour,
}

impl Default for Writer {
    fn default() -> Self {
        Self::new()
    }
}

impl Writer {
    pub fn new() -> Self {
        FRAMEBUFFER_WRITER.lock().as_mut().map_or_else(
            || {
                panic!("Framebuffer writer not initialized.");
            },
            |writer| Self {
                font: &FONT_SPLEEN_8X16,
                screen_width: writer.width() / 8,
                screen_height: writer.height() / 16,
                text_line: 0,
                text_col: 0,
                fg_color: Colour::White,
                bg_color: Colour::Black,
            },
        )
    }

    pub const fn set_font(&mut self, font: &'static Font) {
        self.font = font;
    }

    pub const fn set_pos(&mut self, coords: Vec2<u32>) {
        self.text_col = coords.x();
        self.text_line = coords.y();
    }

    pub const fn pos(&self) -> Vec2<u32> {
        Vec2::new(self.text_col, self.text_line)
    }

    /// This is sent when the user types a backspace.
    const BACKSPACE: u8 = 8;

    pub fn write_glyph(&mut self, c: u8) {
        if c == b'\n' {
            self.newline();
            return;
        } else if c == Self::BACKSPACE {
            self.backspace();
            return;
        }

        // Get the character data from the font array. -- each byte is a row of
        // pixels
        let data: &[u8] = self.font.glyph_for(c as u16);

        if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
            for (row, line) in data.iter().enumerate().take(16) {
                for col in 0..8 {
                    let pixel_x: u32 = self.text_col * FONT_WIDTH + col;
                    let pixel_y: u32 =
                        self.text_line * FONT_HEIGHT + row as u32;

                    if line & (0x80 >> col) != 0 {
                        // Write the foreground color
                        writer.write_pixel(
                            pixel_x as usize,
                            pixel_y as usize,
                            self.fg_color,
                        );
                    } else {
                        // Write the background color
                        writer.write_pixel(
                            pixel_x as usize,
                            pixel_y as usize,
                            self.bg_color,
                        );
                    }
                }
            }
        }

        // Go to next position
        if self.text_col + 1 >= self.screen_width {
            self.newline();
        } else {
            self.text_col += 1;
        }
    }

    pub const fn dimensions(&self) -> (u32, u32) {
        (self.screen_width, self.screen_height)
    }

    pub const fn next_char(&mut self) {
        self.text_col += 1;
    }

    pub const fn newline(&mut self) {
        self.text_col = 0;

        if self.text_line + 1 >= self.screen_height {
            self.text_line = 0;
        } else {
            self.text_line += 1;
        }
    }

    /// Handles the backspace character. TODO: Implement VT-100 style terminal
    /// control codes alongside a shell. Not simple.
    pub fn backspace(&mut self) {
        if self.text_col > 0 {
            self.text_col -= 1;
            // Blank out the previous char.
            self.write_glyph(b' ');
            self.text_col -= 1;
        }
    }

    pub fn write_string(&mut self, s: &str) {
        for c in s.chars() {
            self.write_glyph(c as u8);
        }
    }

    pub const fn set_colour(&mut self, fg: Colour, bg: Colour) {
        self.fg_color = fg;
        self.bg_color = bg;
    }

    pub const fn reset_colour(&mut self) {
        self.fg_color = Colour::White;
        self.bg_color = Colour::Black;
    }
}

impl fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        self.write_string(s);
        Ok(())
    }
}

fn write(args: fmt::Arguments, fg: Colour, bg: Colour) {
    use core::fmt::Write;

    interrupts::without_interrupts(|| {
        let mut writer = WRITER.lock();
        writer.set_colour(fg, bg);
        writer.write_fmt(args).unwrap();
        writer.reset_colour();
    });
}

pub fn _print(args: fmt::Arguments) {
    interrupts::without_interrupts(|| {
        write(args, Colour::White, Colour::Black);
    })
}

pub fn _print_err(args: fmt::Arguments) {
    interrupts::without_interrupts(|| {
        write(args, Colour::Red, Colour::Black);
    })
}

pub fn _print_log(args: fmt::Arguments) {
    interrupts::without_interrupts(|| {
        write(args, Colour::Yellow, Colour::Black);
    })
}

pub fn clear_screen() {
    interrupts::without_interrupts(|| {
        let mut writer = WRITER.lock();
        writer.text_line = 0;
        writer.text_col = 0;

        if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
            writer.clear();
        }
    });
}

/// Prints a string at a given `coords` before returning to original position.
///
/// These are 0 based indices and refer to character position, not pixel
/// positions.
pub fn _print_oneshot(
    fg: Colour,
    bg: Colour,
    coords: Vec2<u32>,
    args: fmt::Arguments,
) {
    interrupts::without_interrupts(|| {
        let mut writer = WRITER.lock();

        // Save the old positions of `text_col` and `text_line`.
        let old_pos = writer.pos();

        // Also save the colors.
        let old_fg_color = writer.fg_color;
        let old_bg_color = writer.bg_color;

        writer.set_pos(coords);
        writer.set_colour(fg, bg);
        writer.write_fmt(args).unwrap();
        writer.set_colour(old_fg_color, old_bg_color);
        writer.set_pos(old_pos);
    });
}

pub fn reset_cursor() {
    interrupts::without_interrupts(|| {
        let mut writer = WRITER.lock();
        writer.text_line = 0;
        writer.text_col = 0;
    });
}

#[macro_export]
/// Prints a coloured string at a position before restoring previous position
/// and colour.
macro_rules! print_oneshot {
    ($position:expr, $fg:expr, $bg:expr, $($arg:tt)*) => {
        $crate::arch::x86_64::drivers::ascii::_print_oneshot($fg, $bg, $position, format_args!($($arg)*));
    };
}

#[macro_export]
macro_rules! println_log {
	() => ($crate::print_log!("\n"));
	($($arg:tt)*) => ($crate::print_log!("{}\n", format_args!($($arg)*)));
}

#[macro_export]
macro_rules! print_log {
	($($arg:tt)*) => ($crate::prelude::_print_log(format_args!($($arg)*)));
}

#[macro_export]
macro_rules! println {
	() => ($crate::print!("\n"));
	($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}

#[macro_export]
macro_rules! print {
	($($arg:tt)*) => ($crate::prelude::_print(format_args!($($arg)*)));
}

#[macro_export]
macro_rules! eprintln {
    () => ($crate::printerr!("\n"));
    ($($arg:tt)*) => ($crate::eprint!("{}\n", format_args!($($arg)*)));
}

#[macro_export]
macro_rules! eprint {
    ($($arg:tt)*) => ($crate::prelude::_print_err(format_args!($($arg)*)));
}
