use core::ops::{Deref, DerefMut};

use alloc::sync::Arc;
use device_api::clock::Hertz;
use libk::{
    debug::{self, DebugSink},
    device::manager::DEVICE_REGISTRY,
    error::Error,
    vfs::{Terminal, TerminalInput, TerminalOutput},
};
use libk_util::{OneTimeInit, sync::IrqSafeSpinlock};
use yggdrasil_abi::io::{TerminalOptions, TerminalOutputOptions};

use crate::{Access, Config, PortConfig, Vendor};

const REG_RBR: u8 = 0;
const REG_THR: u8 = 0;
const REG_DLL: u8 = 0;
const REG_IER: u8 = 1;
const REG_DLH: u8 = 1;
const REG_IIR: u8 = 2;
const REG_FCR: u8 = 2;
const REG_LCR: u8 = 3;
const REG_MCR: u8 = 4;
const REG_LSR: u8 = 5;

// Vendor-specific
const DW_REG_USR: u8 = 31;
const DW_REG_SRR: u8 = 34;
const DW_REG_HTX: u8 = 41;
const BCM2835_REG_AUX_MU_CNTL: u8 = 24;

/// Received Data available interrupt
const IER_RXDE: u8 = 1 << 0;
/// Received Data available
const IIR_RXDA: u8 = 0b100;
const IIR_MASK: u8 = 0x7;
const FCR_FIFO_ENABLE: u8 = 1 << 0;
const FCR_RX_FIFO_RESET: u8 = 1 << 1;
const FCR_TX_FIFO_RESET: u8 = 1 << 2;
const LCR_BITS_8: u8 = 0x3;
const LCR_DLAB: u8 = 1 << 7;
const MCR_DTR: u8 = 1 << 0;
const MCR_RTS: u8 = 1 << 1;
const LSR_TEMT: u8 = 1 << 6;

const BCM2835_AUX_MU_CNTL_RE: u8 = 1 << 0;
const BCM2835_AUX_MU_CNTL_TE: u8 = 1 << 1;

pub struct Port8250<A: Access> {
    pub(crate) vendor: Vendor,
    pub(crate) inner: OneTimeInit<Arc<Terminal<Inner<A>>>>,
}

struct PortRegs<A: Access> {
    regs: A,
    baud_rate: u32,
    input_clock: Hertz,
}

pub(crate) struct Inner<A: Access> {
    regs: IrqSafeSpinlock<PortRegs<A>>,
}

impl<A: Access> Port8250<A> {
    pub(crate) fn init(self: Arc<Self>, config: &<A::Config as Config>::Port) -> Result<(), Error> {
        let _guard = debug::MuteGuard::acquire();
        // Deassert reset signal and enable clocks
        config.enable_clocks()?;
        config.deassert_resets()?;

        let input_clock = config.input_clock_frequency()?;

        let mut regs = PortRegs {
            regs: unsafe { A::map(config) }?,
            baud_rate: 0,
            input_clock,
        };

        regs.init(self.vendor, 115200)?;

        let input = TerminalInput::with_capacity(64)?;
        let output = Inner {
            regs: IrqSafeSpinlock::new(regs),
        };

        let terminal = self.inner.init(Arc::new(Terminal::from_parts(
            TerminalOptions::const_default(),
            input,
            output,
        )));

        DEVICE_REGISTRY
            .serial_terminal
            .register(terminal.clone(), Some(self.clone()))
            .ok();

        Ok(())
    }

    pub(crate) fn enable_irq(&self) {
        let mut regs = self.inner.get().output().regs.lock();
        regs.write(REG_IER, IER_RXDE);
    }

    pub(crate) fn handle_irq(&self) -> bool {
        let inner = self.inner.get();
        let byte = {
            let regs = inner.output().regs.lock();
            let iir = regs.read(REG_IIR) & IIR_MASK;
            if iir == IIR_RXDA {
                Some(regs.read(REG_RBR))
            } else {
                None
            }
        };

        if let Some(byte) = byte {
            inner.write_to_input(byte);
            true
        } else {
            false
        }
    }
}

impl<A: Access> DebugSink for Port8250<A> {
    fn putc(&self, c: u8) -> Result<(), Error> {
        self.inner.get().putc_to_output(c)
    }

    fn supports_control_sequences(&self) -> bool {
        true
    }
}

impl<A: Access> TerminalOutput for Inner<A> {
    fn write(&self, byte: u8, options: &TerminalOutputOptions) -> Result<(), Error> {
        let mut regs = self.regs.lock();
        if byte == b'\n' && options.contains(TerminalOutputOptions::NL_TO_CRNL) {
            regs.serial_out(b'\r')?;
        }
        regs.serial_out(byte)
    }

    fn write_multiple(
        &self,
        bytes: &[u8],
        options: &TerminalOutputOptions,
    ) -> Result<usize, Error> {
        let mut regs = self.regs.lock();
        for &byte in bytes {
            if byte == b'\n' && options.contains(TerminalOutputOptions::NL_TO_CRNL) {
                regs.serial_out(b'\r')?;
            }
            regs.serial_out(byte)?;
        }
        Ok(bytes.len())
    }

    fn set_baud_rate(&self, baud: u32) -> Result<(), Error> {
        let mut regs = self.regs.lock();
        regs.set_baud_rate(baud)
    }

    fn baud_rate(&self) -> u32 {
        let regs = self.regs.lock();
        regs.baud_rate
    }
}

impl<A: Access> PortRegs<A> {
    fn init(&mut self, vendor: Vendor, baud_rate: u32) -> Result<(), Error> {
        match vendor {
            Vendor::Bcm2835Aux => {
                self.write(
                    BCM2835_REG_AUX_MU_CNTL,
                    BCM2835_AUX_MU_CNTL_RE | BCM2835_AUX_MU_CNTL_TE,
                );
                delay();
            }
            Vendor::DwApbUart => {
                self.write(DW_REG_SRR, 1);
                delay();
                self.write(DW_REG_HTX, 0);
                delay();

                // wait while DW USR.BUSY == 1
                while self.read(DW_REG_USR) & (1 << 0) != 0 {
                    core::hint::spin_loop();
                }
            }
            _ => (),
        }

        self.set_baud_rate(baud_rate)
    }

    fn serial_out(&mut self, byte: u8) -> Result<(), Error> {
        let mut timeout = 1000000;
        while timeout > 0 && self.read(REG_LSR) & LSR_TEMT == 0 {
            timeout -= 1;
        }
        if timeout == 0 {
            return Err(Error::TimedOut);
        }
        self.write(REG_THR, byte);
        Ok(())
    }

    fn set_baud_rate(&mut self, baud_rate: u32) -> Result<(), Error> {
        // TODO check that the divisor actually fits

        self.baud_rate = baud_rate;

        let divisor = (self.input_clock.0 / (baud_rate as u64 * 16)) as u32;

        // Disable interrupts, clear FCR/MCR
        self.write(REG_IER, 0x00);
        self.write(REG_MCR, 0x00);
        self.write(REG_FCR, 0x00);

        // Program divisor
        // DLAB=1
        let lcr = self.read(REG_LCR);
        self.write(REG_LCR, lcr | LCR_DLAB);
        self.write(REG_DLH, ((divisor >> 8) & 0xFF) as u8);
        self.write(REG_DLL, (divisor & 0xFF) as u8);
        delay();
        let lcr = self.read(REG_LCR);
        self.write(REG_LCR, lcr & !LCR_DLAB);
        delay();

        // 8n1 setting
        let lcr = self.read(REG_LCR);
        self.write(REG_LCR, lcr | LCR_BITS_8);
        delay();

        // DTR+RTS
        self.write(REG_MCR, MCR_DTR | MCR_RTS);

        // Enable and drain FIFO
        self.write(REG_FCR, FCR_FIFO_ENABLE);
        delay();
        let fcr = self.read(REG_FCR);
        self.write(REG_FCR, fcr | FCR_RX_FIFO_RESET | FCR_TX_FIFO_RESET);
        delay();
        // TODO flush Rx
        Ok(())
    }
}

impl<A: Access> Deref for PortRegs<A> {
    type Target = A;

    fn deref(&self) -> &Self::Target {
        &self.regs
    }
}

impl<A: Access> DerefMut for PortRegs<A> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.regs
    }
}

fn delay() {
    for _ in 0..10000 {
        core::hint::spin_loop();
    }
}
