use core::{
    fmt,
    sync::atomic::{AtomicUsize, Ordering},
};
use spin::{Lazy, Mutex};

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

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

use crate::arch::x86_64::drivers::port::{inb, outb};

use x86_64::instructions::interrupts;

pub fn _serial_write(args: fmt::Arguments) {
    use core::fmt::Write;

    interrupts::without_interrupts(|| {
        if let Some(writer) = WRITER.lock().as_mut() {
            writer.write_fmt(args).unwrap();
        }
    })
}

pub fn serial_read() -> &'static str {
    serial_println!("getting value!");

    interrupts::without_interrupts(|| {
        if let Some(reader) = READER.lock().as_mut() {
            serial_println!("stuff happnin.");
            reader.read_str_to_buffer();
        } else {
            serial_println!("failed to get writer");
        }
    });

    serial_println!("eee");

    let i = BUFFER_LEN.load(Ordering::SeqCst);

    unsafe {
        if i != 0 {
            core::str::from_utf8(&BUFFER[..i - 1]).unwrap()
        } else {
            serial_println!("empty string");
            ""
        }
    }
}

static PORT: u16 = 0x3f8;
static mut BUFFER: [u8; 256] = [0; 256];
static BUFFER_LEN: AtomicUsize = AtomicUsize::new(0);

static READER: Lazy<Mutex<Option<Reader>>> = Lazy::new(|| Mutex::new(None));
static WRITER: Lazy<Mutex<Option<Writer>>> = Lazy::new(|| Mutex::new(None));

struct Reader;

struct Writer;

impl fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            self.write_byte(c as u8);
        }
        Ok(())
    }
}

impl Writer {
    unsafe fn write_success(&self) -> bool {
        inb(PORT + 5) & 0x20 != 0
    }

    pub fn write_byte(&self, data: u8) {
        unsafe {
            while !self.write_success() {}
            outb(PORT, data);
        }
    }
}

pub fn init() -> Result<(), &'static str> {
    test()?;

    if READER.lock().is_none() {
        *READER.lock() = Some(Reader);
    }

    if WRITER.lock().is_none() {
        *WRITER.lock() = Some(Writer);
    }

    Ok(())
}

pub fn test() -> Result<(), &'static str> {
    outb(PORT + 1, 0x00); // Disable all interrupts
    outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
    outb(PORT, 0x03); // Set divisor to 3 (lo byte) 38400 baud
    outb(PORT + 1, 0x00); //                  (hi byte)
    outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
    outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-bytethreshold
    outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
    outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
    outb(PORT, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)

    if inb(PORT) != 0xAE {
        return Err("serial test failed");
    }

    outb(PORT + 4, 0x0F);

    Ok(())
}

impl Reader {
    pub fn read_str_to_buffer(&mut self) {
        unsafe {
            while !self.read_ready() {}

            BUFFER_LEN.store(0, Ordering::SeqCst);

            while BUFFER_LEN.load(Ordering::SeqCst) < 256 {
                let c = self.read();
                BUFFER[BUFFER_LEN.load(Ordering::SeqCst)] = c;
                if c as char == '\r' {
                    break;
                }
                BUFFER_LEN.fetch_add(1, Ordering::SeqCst);
            }

            serial_println!("returning")
        }
    }

    unsafe fn read_ready(&self) -> bool {
        inb(PORT + 5) & 1 != 0
    }

    pub fn read(&self) -> u8 {
        unsafe {
            while !self.read_ready() {}
            inb(PORT)
        }
    }
}
