use core::{
    arch::asm,
    fmt::{self, Write},
};

use crate::{read_address, write_address};

const BAUD: u32 = 115200;
const UART_CLK: u32 = 48_000_000;

const UART0_DR: u32 = 0x3F20_1000;

const UART0_FR: u32 = 0x3F20_1018;
const UART0_FR_TXFF: u32 = 1 << 5;

const UART0_IBRD: u32 = 0x3F20_1024;
const UART0_FBRD: u32 = 0x3F20_1028;

const UART0_CR: u32 = 0x3F20_1030;
const UART0_CR_UARTEN: u32 = 1 << 0;

const UART0_CR_TXE: u32 = 1 << 8;
const UART0_CR_RXE: u32 = 1 << 9;

const UART0_LCRH: u32 = 0x3F20_102C;
const UART0_LCRH_FEN: u32 = 1 << 4;

const UART0_IMSC: u32 = 0x3F20_1038;
const UART0_IMSC_RXIM: u32 = 1 << 4;

const UART0_ICR: u32 = 0x3F20_1044;

pub struct Uart;

impl Write for Uart {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        for byte in s.bytes() {
            while (unsafe { read_address(UART0_FR) } & UART0_FR_TXFF) != 0 {
                unsafe { asm!("nop") }
            }
            unsafe { write_address(UART0_DR, byte as u32) };
        }
        // wait till uart is not busy anymore
        while ((unsafe { read_address(UART0_FR) } >> 3) & 0b1) != 0 {}
        Ok(())
    }
}

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

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

pub fn _print(args: fmt::Arguments) {
    let _ = Uart.write_fmt(args);
}

/// Initialize UART peripheral
pub fn uart_init() {
    let baud_div_times_64 = (UART_CLK * 4) / BAUD;

    let ibrd = baud_div_times_64 / 64;
    let fbrd = baud_div_times_64 % 64;

    uart_enable(false);
    uart_fifo_enable(true);

    unsafe {
        write_address(UART0_IBRD, ibrd);
        write_address(UART0_FBRD, fbrd);
    }

    uart_enable_rx_interrupt();
    uart_set_lcrh(0b11, true);

    // Enable transmit, receive and uart
    let mut cr = unsafe { read_address(UART0_CR) };
    cr |= UART0_CR_UARTEN | UART0_CR_TXE | UART0_CR_RXE;

    unsafe { write_address(UART0_CR, cr) };
}

/// Enable UARTEN
fn uart_enable(enable: bool) {
    let mut cr = unsafe { read_address(UART0_CR) };

    if enable {
        cr |= UART0_CR_UARTEN;
    } else {
        cr &= !UART0_CR_UARTEN;
    }

    unsafe { write_address(UART0_CR, cr) };
}

/// Enable UART FIFO
fn uart_fifo_enable(enable: bool) {
    let mut lcrh = unsafe { read_address(UART0_LCRH) };

    if enable {
        lcrh |= UART0_LCRH_FEN;
    } else {
        lcrh &= !UART0_LCRH_FEN;
    }

    unsafe { write_address(UART0_LCRH, lcrh) };
}

#[inline(always)]
fn uart_enable_rx_interrupt() {
    unsafe { write_address(UART0_IMSC, UART0_IMSC_RXIM) };
}

/// Set UART word length and set FIFO status
#[inline(always)]
fn uart_set_lcrh(wlen: u32, enable_fifo: bool) {
    let mut value = (wlen & 0b11) << 5;
    if enable_fifo {
        value |= UART0_LCRH_FEN;
    }
    unsafe { write_address(UART0_LCRH, value) };
}

#[inline(always)]
pub fn read_uart_data() -> char {
    (unsafe { read_address(UART0_DR) } & 0xFF) as u8 as char
}

#[inline(always)]
pub fn clear_uart_interrupt_state() {
    unsafe {
        write_address(UART0_ICR, 1 << 4);
    }
}
