// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: MIT

#![allow(dead_code)]

use enumflags2::{BitFlag, BitFlags};
use intbits::Bits;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use x86_64::instructions::port::Port;

use crate::arch::asm::nop;

/// Represents an x86 port-mapped serial port.
pub struct SerialPort {
    base_port: Port<u8>,
    interrupt_enable: Port<u8>,
    interrupt_id_fifo_control: Port<u8>,
    line_control: Port<u8>,
    modem_control: Port<u8>,
    line_status: Port<u8>,
    modem_status: Port<u8>,
    scratch: Port<u8>,
    fifo_register: u8,
    pub crlf: Crlf,
    pub wait_tx_clear: bool,
}

#[derive(PartialEq, Eq)]
pub enum Crlf {
    Lf,
    Crlf,
}

#[derive(FromPrimitive)]
#[repr(u8)]
pub enum DataBits {
    Five = 0,
    Six = 1,
    Seven = 2,
    Eight = 3,
}

#[derive(FromPrimitive)]
#[repr(u8)]
pub enum ParityBits {
    None = 0b000,
    Odd = 0b001,
    Even = 0b011,
    Mark = 0b101,
    Space = 0b111,
}

#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum InterruptEnable {
    ModemStatus = 0b1000,
    ReceiverLineStatus = 0b100,
    TransmitterHoldingRegisterEmpty = 0b10,
    ReceivedDataAvail = 0b1,
}

#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ModemControl {
    DataTerminalReady = 0b00000001,
    RequestToSend = 0b00000010,
    Out1 = 0b00000100,
    Out2 = 0b00001000,
    Loop = 0b00010000,
}

#[repr(u8)]
#[derive(FromPrimitive)]
pub enum TriggerLevel {
    OneByte = 0b00,
    FourBytes = 0b01,
    EightBytes = 0b10,
    FourteenBytes = 0b11,
}

#[repr(u8)]
#[derive(FromPrimitive, PartialEq, Eq)]
pub enum InterruptState {
    ModemStatus = 0b00,
    TransmitterEmpty = 0b01,
    ReceivedDataAvail = 0b10,
    ReceiverLineStatus = 0b11,
}

#[repr(u8)]
#[derive(FromPrimitive)]
pub enum FifoState {
    NoFifo = 0b00,
    FifoEnabledUnusable = 0b01,
    FifoEnabled = 0b10,
}

#[repr(u8)]
#[enumflags2::bitflags]
#[derive(Clone, Copy, Debug)]
pub enum LineStatus {
    DataReady = 0b00000001,
    OverrunError = 0b00000010,
    ParityError = 0b00000100,
    FramingError = 0b00001000,
    BreakIndicator = 0b00010000,
    TransmitterHoldingRegisterEmpty = 0b00100000,
    TransmitterEmpty = 0b01000000,
    ImpendingError = 0b10000000,
}

#[repr(u8)]
#[enumflags2::bitflags]
#[derive(Clone, Copy, Debug)]
pub enum ModemStatus {
    DeltaClearToSend = 0b00000001,
    DeltaDataSetReady = 0b00000010,
    TrailingEdgeRingIndicator = 0b00000100,
    DeltaDataCarrierDetect = 0b00001000,
    ClearToSend = 0b00010000,
    DataSetReady = 0b00100000,
    RingIndicator = 0b01000000,
    DataCarrierDetect = 0b10000000,
}

impl SerialPort {
    pub fn log_write(&mut self, msg: &str) {
        if self.crlf == Crlf::Crlf {
            for c in msg.chars() {
                if c == '\n' {
                    self.write_char('\r');
                }
                self.write_char(c);
            }
        } else {
            for c in msg.chars() {
                self.write_char(c);
            }
        }
    }

    /// Get whether the transmit buffer is empty.
    pub fn is_transmit_empty(&mut self) -> bool {
        self.get_line_status()
            .contains(LineStatus::TransmitterEmpty)
    }

    fn block_until_transmit_empty(&mut self) {
        while !self.is_transmit_empty() {
            nop();
        }
    }

    /// Write a message with a newline appended to the serial port.
    pub fn log_writeln(&mut self, msg: &str) {
        self.log_write(msg);
        self.log_write("\n");
    }

    /// Create a new port-mapped serial port with the base address `port_addr`.
    pub unsafe fn from_port_unchecked(port_addr: u16) -> Self {
        let mut port = SerialPort {
            base_port: Port::new(port_addr),
            interrupt_enable: Port::new(port_addr + 1),
            interrupt_id_fifo_control: Port::new(port_addr + 2),
            line_control: Port::new(port_addr + 3),
            modem_control: Port::new(port_addr + 4),
            line_status: Port::new(port_addr + 5),
            modem_status: Port::new(port_addr + 6),
            scratch: Port::new(port_addr + 7),
            fifo_register: 0,
            crlf: Crlf::Crlf,
            wait_tx_clear: true,
        };
        // ensure this is false
        port.set_dlab(false);
        port
    }

    pub fn try_from_port(port_addr: u16) -> Option<Self> {
        let mut port = unsafe { SerialPort::from_port_unchecked(port_addr) };

        // Assert that port scratch register is writable
        unsafe { port.scratch.write(192) };
        if unsafe { port.scratch.read() } != 192 {
            return None;
        }

        // Enable loopback mode
        let mut port_modem = port.get_modem_control();
        port_modem.insert(ModemControl::Loop);
        port.set_modem_control(port_modem);
        port.write_char(0xae as char);

        // Assert that loopback mode worked
        if port.read_char() != (0xae_u8) {
            return None;
        }

        // Disable loopback mode
        port_modem.remove(ModemControl::Loop);
        port.set_modem_control(port_modem);

        Some(port)
    }

    fn get_line_control(&mut self) -> u8 {
        unsafe { self.line_control.read() }
    }
    fn set_line_control(&mut self, value: u8) {
        unsafe { self.line_control.write(value) };
    }

    fn get_dlab(&mut self) -> bool {
        self.get_line_control().bit(7)
    }
    fn set_dlab(&mut self, dlab: bool) {
        let mut new_lcr = unsafe { self.line_control.read() };
        new_lcr.set_bit(7, dlab);
        unsafe { self.line_control.write(new_lcr) };
    }

    /// Read a single character from the RX buffer.
    pub fn read_char(&mut self) -> u8 {
        unsafe { self.base_port.read() }
    }
    /// Write a single character to the TX buffer.
    pub fn write_char(&mut self, c: char) {
        // Wait for the TX Buffer to be empty
        if self.wait_tx_clear {
            self.block_until_transmit_empty();
        }
        unsafe { self.base_port.write(c as u8) };
    }

    /// Get the baud rate divisor.
    pub fn get_divisor(&mut self) -> u16 {
        self.set_dlab(true);
        let result: u16 =
            unsafe { self.base_port.read() as u16 | ((self.interrupt_enable.read() as u16) << 8) };
        self.set_dlab(false);
        result
    }
    /// Set the baud rate divisor.
    pub fn set_divisor(&mut self, divisor: u16) {
        self.set_dlab(true);
        unsafe {
            self.base_port.write((divisor & 0b11111111) as u8);
            self.interrupt_enable
                .write(((divisor & 0b1111111100000000) >> 8) as u8);
        }
        self.set_dlab(false);
    }

    /// Get the number of bits in a character.
    pub fn get_data_bits(&mut self) -> DataBits {
        DataBits::from_u8(self.get_line_control().bits(0..=1)).unwrap()
    }
    /// Set the number of bits in a character.
    pub fn set_data_bits(&mut self, bits: DataBits) {
        let new_value = self.get_line_control().bits(2..=7) | bits as u8;
        self.set_line_control(new_value);
    }

    /// Get the  number of bits sent after each character.
    /// A value of False means only 1 bit will be sent.
    /// A value of True means 2 bits will be sent.
    /// If the character length is 5 bits, a value of True means 1.5 bits will be sent.
    pub fn get_stop_bits(&mut self) -> bool {
        self.get_line_control().bit(2)
    }
    /// Set the  number of bits sent after each character.
    /// A value of False means only 1 bit will be sent.
    /// A value of True means 2 bits will be sent.
    /// If the character length is 5 bits, a value of True means 1.5 bits will be sent.
    pub fn set_stop_bits(&mut self, bit: bool) {
        let mut new_value = self.get_line_control();
        new_value.set_bit(2, bit);
        self.set_line_control(new_value);
    }

    /// Get the parity strategy.
    pub fn get_parity_bits(&mut self) -> ParityBits {
        ParityBits::from_u8(self.get_line_control().bits(3..=5) >> 3).unwrap()
    }
    /// Set the parity strategy.
    pub fn set_parity_bits(&mut self, parity: ParityBits) {
        let mut new_value = self.get_line_control();
        new_value.set_bits(3..=5, parity as u8);
        self.set_line_control(new_value);
    }

    /// Get the Interrupt Enable control register.
    pub fn get_interrupt_enable(&mut self) -> BitFlags<InterruptEnable> {
        InterruptEnable::from_bits(unsafe { self.interrupt_enable.read().bits(0..=3) }).unwrap()
    }
    /// Set the Interrupt Enable control register.
    pub fn set_interrupt_enable(&mut self, interrupt: InterruptEnable) {
        unsafe { self.interrupt_enable.write(interrupt as u8) };
    }

    /// Set the FIFO control register.
    pub fn set_fifo_control(&mut self, value: u8) {
        unsafe { self.interrupt_id_fifo_control.write(value) };
    }
    /// Clear the transmitter FIFO.
    pub fn clear_tx_fifo(&mut self) {
        let mut new_value = self.fifo_register;
        new_value.set_bit(2, true);
        self.set_fifo_control(new_value);
    }
    /// Clear the receiver FIFO.
    pub fn clear_rx_fifo(&mut self) {
        let mut new_value = self.fifo_register;
        new_value.set_bit(1, true);
        self.set_fifo_control(new_value);
    }
    /// Enable or disable FIFOs.
    pub fn set_fifo_enable(&mut self, enable: bool) {
        self.fifo_register.set_bit(0, enable);
        self.set_fifo_control(self.fifo_register);
    }
    /// Get whether FIFOs were enabled using this API.
    pub fn get_fifo_enable(&mut self) -> bool {
        self.fifo_register.bit(0)
    }
    /// Set the DMA mode of the serial controller.
    pub fn set_dma_mode(&mut self, enable: bool) {
        self.fifo_register.set_bit(3, enable);
        self.set_fifo_control(self.fifo_register);
    }
    /// Get whether DMA mode was enabled using this API.
    pub fn get_dma_mode(&mut self) -> bool {
        self.fifo_register.bit(3)
    }
    /// Get the Interrupt Trigger Level.
    pub fn get_interrupt_trigger_level(&mut self) -> TriggerLevel {
        TriggerLevel::from_u8(self.fifo_register.bits(6..=7) >> 6).unwrap()
    }
    /// Set the Interrupt Trigger Level.
    pub fn set_interrupt_trigger_level(&mut self, level: TriggerLevel) {
        self.fifo_register.set_bits(6..=7, level as u8);
        self.set_fifo_control(self.fifo_register);
    }

    /// Get the Interrupt ID status register.
    pub fn get_interrupt_id(&mut self) -> u8 {
        unsafe { self.interrupt_id_fifo_control.read() }
    }
    /// Get the state of the FIFO buffers.
    pub fn get_fifo_buffer_state(&mut self) -> FifoState {
        FifoState::from_u8(self.get_interrupt_id().bits(6..=7) >> 6).unwrap()
    }
    /// Get whether a timeout interrupt is pending. Only used on UART 16550 chips, otherwise reserved.
    pub fn get_timeout_pending(&mut self) -> bool {
        self.get_interrupt_id().bit(3)
    }
    /// Get the Interrupt State of the controller.
    pub fn get_interrupt_state(&mut self) -> InterruptState {
        InterruptState::from_u8(self.get_interrupt_id().bits(1..=2) >> 1).unwrap()
    }
    /// Get whether an interrupt is pending.
    pub fn get_interrupt_pending(&mut self) -> bool {
        !self.get_interrupt_id().bit(0)
    }

    /// Set the Modem Control register flags.
    /// This OVERWRITES all held flags. To set specific flags, first store the current
    /// value using `get_modem_control()`, then modify the flags you want to change.
    pub fn set_modem_control(&mut self, control: BitFlags<ModemControl>) {
        unsafe { self.modem_control.write(control.bits()) };
    }
    /// Get the Modem Control register flags.
    pub fn get_modem_control(&mut self) -> BitFlags<ModemControl> {
        ModemControl::from_bits(unsafe { self.modem_control.read() & 0b0001111 }).unwrap()
    }

    /// Get the Line Status Register flags.
    pub fn get_line_status(&mut self) -> BitFlags<LineStatus> {
        LineStatus::from_bits(unsafe { self.line_status.read() }).unwrap()
    }

    /// Get the Modem Status Register flags.
    pub fn get_modem_status(&mut self) -> BitFlags<ModemStatus> {
        ModemStatus::from_bits(unsafe { self.modem_status.read() }).unwrap()
    }
}
