use abi::error::Error;
use alloc::sync::Arc;
use device_api::device::Device;
use kernel_arch::{Architecture, ArchitectureImpl};
use kernel_arch_x86::intrinsics::{io_wait, IoPort, IoPortAccess};
use libk::fs::sysfs::{self, nodes::SysfsRtcNode};
use libk_util::sync::IrqSafeSpinlock;

const CMOS_REG_SEC: u8 = 0x00;
const CMOS_REG_MIN: u8 = 0x02;
const CMOS_REG_HOUR: u8 = 0x04;
const CMOS_REG_DAY: u8 = 0x07;
const CMOS_REG_MON: u8 = 0x08;
const CMOS_REG_YEAR: u8 = 0x09;
const CMOS_REG_STATUS_A: u8 = 0x0A;
const STATUS_A_UPDATE_IN_PROGRESS: u8 = 1 << 7;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct DateTime {
    seconds: u8,
    minutes: u8,
    hours: u8,
    day_of_month: u8,
    month: u8,
    year: u8,
}

struct Inner {
    command: IoPort<u8>,
    data: IoPort<u8>,
}

pub struct Rtc {
    inner: IrqSafeSpinlock<Inner>,
}

fn bcd_to_dec(x: u8) -> u8 {
    let a = (x >> 4) & 0xF;
    let b = x & 0xF;
    a * 10 + b
}

impl Inner {
    fn read_date_time(&mut self) -> DateTime {
        self.wait_for_update();
        let mut last = self.try_read_time();

        loop {
            self.wait_for_update();
            let now = self.try_read_time();

            if now == last {
                break now;
            }
            last = now;
        }
    }

    fn wait_for_update(&mut self) {
        while self.read_reg(CMOS_REG_STATUS_A) & STATUS_A_UPDATE_IN_PROGRESS != 0 {
            core::hint::spin_loop();
        }
    }

    fn try_read_time(&mut self) -> DateTime {
        let seconds = bcd_to_dec(self.read_reg(CMOS_REG_SEC));
        let minutes = bcd_to_dec(self.read_reg(CMOS_REG_MIN));
        let hours = bcd_to_dec(self.read_reg(CMOS_REG_HOUR));
        let day_of_month = bcd_to_dec(self.read_reg(CMOS_REG_DAY));
        let month = bcd_to_dec(self.read_reg(CMOS_REG_MON));
        let year = bcd_to_dec(self.read_reg(CMOS_REG_YEAR));
        DateTime {
            seconds,
            minutes,
            hours,
            day_of_month,
            month,
            year,
        }
    }

    fn read_reg(&mut self, reg: u8) -> u8 {
        assert!(ArchitectureImpl::interrupt_mask());
        self.command.write(reg);
        for _ in 0..10 {
            io_wait();
        }
        let value = self.data.read();
        for _ in 0..10 {
            io_wait();
        }
        value
    }
}

impl SysfsRtcNode for Rtc {
    fn read(&self) -> Result<u64, Error> {
        let time = self.inner.lock().read_date_time();
        Ok(time.to_seconds())
    }
}

impl Device for Rtc {
    fn display_name(&self) -> &str {
        "x86 RTC"
    }
}

impl Rtc {
    const fn new() -> Self {
        Self {
            inner: IrqSafeSpinlock::new(Inner {
                command: IoPort::new(0x70),
                data: IoPort::new(0x71),
            }),
        }
    }

    pub fn setup() -> Result<Arc<Self>, Error> {
        let this = Arc::new(Self::new());
        sysfs::nodes::add_rtc_node(this.clone());
        Ok(this)
    }
}

impl DateTime {
    pub fn to_seconds(self) -> u64 {
        let date = chrono::NaiveDate::from_ymd_opt(
            2000 + self.year.min(100) as i32,
            self.month.clamp(1, 12) as u32,
            self.day_of_month.clamp(1, 31) as u32,
        )
        .unwrap();
        let time = chrono::NaiveTime::from_hms_opt(
            self.hours.clamp(0, 23) as u32,
            self.minutes.clamp(0, 59) as u32,
            self.seconds.clamp(0, 59) as u32,
        )
        .unwrap();

        chrono::NaiveDateTime::new(date, time)
            .signed_duration_since(chrono::NaiveDateTime::UNIX_EPOCH)
            .num_seconds() as u64
    }
}
