use core::sync::atomic::{AtomicU64, Ordering};

use abi::error::Error;
use acpi::HpetInfo;
use alloc::{collections::btree_map::BTreeMap, format, string::String, sync::Arc};
use device_api::{
    device::{Device, DeviceInitContext},
    interrupt::{
        InterruptAffinity, InterruptHandler, Irq, IrqOptions, IrqTrigger, IrqVector, MsiInfo,
    },
};
use libk::{device::external_interrupt_controller, task::runtime, time};
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::sync::spin_rwlock::IrqSafeRwLock;
use tock_registers::{
    interfaces::{ReadWriteable, Readable, Writeable},
    register_bitfields, register_structs,
    registers::{ReadOnly, ReadWrite},
};

use crate::arch::{x86::dummy_init_context, x86_64::X86_64};

register_bitfields! {
    u64,
    GENERAL_CAPABILITIES [
        COUNTER_CLK_PERIOD OFFSET(32) NUMBITS(32) [],
        VENDOR_ID OFFSET(16) NUMBITS(16) [],
        COUNT_SIZE_CAP OFFSET(13) NUMBITS(1) [],
        NUM_TIM_CAP OFFSET(8) NUMBITS(4) [],
        REV_ID OFFSET(0) NUMBITS(8) [],
    ],
    GENERAL_CONFIG [
        LEG_RT_CNF OFFSET(1) NUMBITS(1) [],
        ENABLE_CNF OFFSET(0) NUMBITS(1) [],
    ],
    Tn_CONFIG [
        Tn_INT_ROUTE_CAP OFFSET(32) NUMBITS(32) [],
        Tn_FSB_INT_DEL_CAP OFFSET(15) NUMBITS(1) [],
        Tn_FSB_EN_CNF OFFSET(14) NUMBITS(1) [],
        Tn_INT_ROUTE_CNF OFFSET(9) NUMBITS(5) [],
        Tn_32MODE_CNF OFFSET(8) NUMBITS(1) [],
        Tn_VAL_SET_CNF OFFSET(6) NUMBITS(1) [],
        Tn_SIZE_CAP OFFSET(5) NUMBITS(1) [],
        Tn_PER_INT_CAP OFFSET(4) NUMBITS(1) [],
        Tn_TYPE_CNF OFFSET(3) NUMBITS(1) [
            NonPeriodic = 0,
            Periodic = 1,
        ],
        Tn_INT_ENB_CNF OFFSET(2) NUMBITS(1) [],
        Tn_INT_TYPE_CNF OFFSET(1) NUMBITS(1) [
            Edge = 0,
            Level = 1,
        ],
    ],
    Tn_FSB_ROUTE [
        Tn_FSB_INT_VAL  OFFSET(0)   NUMBITS(32) [],
        Tn_FSB_INT_ADDR OFFSET(32)  NUMBITS(32) [],
    ],
}

register_structs! {
    #[allow(non_snake_case)]
    GeneralRegs {
        (0x000 => GENERAL_CAPABILITIES: ReadOnly<u64, GENERAL_CAPABILITIES::Register>),
        (0x008 => _0),
        (0x010 => GENERAL_CONFIG: ReadWrite<u64, GENERAL_CONFIG::Register>),
        (0x018 => _1),
        (0x020 => GENERAL_ISR: ReadWrite<u64>),
        (0x028 => _2),
        (0x0F0 => MAIN_COUNTER: ReadWrite<u64>),
        (0x0F8 => _3),
        (0x100 => @END),
    }
}

register_structs! {
    #[allow(non_snake_case)]
    TimerRegs {
        (0x00 => Tn_CONFIG: ReadWrite<u64, Tn_CONFIG::Register>),
        (0x08 => Tn_COMPARATOR: ReadWrite<u64>),
        (0x10 => Tn_FSB_ROUTE: ReadWrite<u64, Tn_FSB_ROUTE::Register>),
        (0x18 => _0),
        (0x20 => @END),
    }
}

pub struct HpetTimer {
    name: String,
    parent: Arc<Hpet>,
    regs: IrqSafeRwLock<DeviceMemoryIo<'static, TimerRegs>>,
    system_clock_source: bool,
    period: u64,

    last_ticks: AtomicU64,

    periodic: bool,
    bits64: bool,
}

pub struct Hpet {
    base: PhysicalAddress,
    regs: IrqSafeRwLock<DeviceMemoryIo<'static, GeneralRegs>>,
    timers: IrqSafeRwLock<BTreeMap<usize, Arc<HpetTimer>>>,

    base_frequency: u64,
    base_period: u64,
}

const FS_IN_S: u64 = 10u64.pow(15);
const FS_IN_NS: u64 = 10u64.pow(6);

impl InterruptHandler for HpetTimer {
    fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
        let now = self.parent.read_counter();
        let last = self.last_ticks.swap(now, Ordering::Relaxed);
        let delta = now.wrapping_sub(last);

        if !self.periodic {
            self.regs.write().rearm(now.wrapping_add(self.period));
        }

        if self.system_clock_source {
            let dt = delta * (self.parent.base_period / FS_IN_NS);
            time::add_nanoseconds(dt);
            runtime::tick();
        }

        true
    }
}

impl Device for HpetTimer {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        if self.period > u32::MAX as u64 && !self.bits64 {
            log::error!(
                "HPET period is >32bit and the HPET itself does not support 64bit counters"
            );
            return Err(Error::InvalidArgument);
        }

        let intc = external_interrupt_controller()?;

        let regs = self.regs.write();

        #[cfg(target_arch = "x86_64")]
        let irq = if regs.is_fsb_capable() {
            let lapic = X86_64::cpu_local_apic();
            let msi_info = lapic.register_msi(InterruptAffinity::Any, self.clone())?;
            log::info!(
                "Set up {:?}, period={}t, periodic={}, msi mode",
                self.name,
                self.period,
                self.periodic
            );

            IrqDestination::Fsb(msi_info)
        } else {
            let irq = regs.select_irq().ok_or(Error::InvalidArgument)?;
            log::info!(
                "Set up {:?}, period={}t, periodic={}, irq={:?}",
                self.name,
                self.period,
                self.periodic,
                irq
            );

            intc.register_irq(
                irq,
                IrqOptions {
                    trigger: IrqTrigger::Edge,
                    ..Default::default()
                },
                self.clone(),
            )?;
            intc.enable_irq(irq)?;

            IrqDestination::Irq(irq)
        };

        let now = self.parent.read_counter();

        // Pause the timer
        regs.stop();
        if self.periodic {
            regs.configure_periodic(now.wrapping_add(self.period), self.period, irq);
        } else {
            regs.configure_oneshot(now.wrapping_add(self.period), irq);
        }

        Ok(())
    }

    unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
        todo!()
    }

    fn display_name(&self) -> &str {
        self.name.as_str()
    }
}

impl Device for Hpet {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        let regs = self.regs.write();

        // Reset the device until at least one timer is created
        regs.stop();
        regs.GENERAL_CONFIG
            .modify(GENERAL_CONFIG::LEG_RT_CNF::CLEAR);
        regs.MAIN_COUNTER.set(0);

        Ok(())
    }

    fn display_name(&self) -> &str {
        "HPET"
    }
}

impl HpetTimer {
    fn create(
        hpet: Arc<Hpet>,
        index: usize,
        frequency: u64,
        system_clock_source: bool,
    ) -> Result<Arc<Self>, Error> {
        let base = hpet.base.add(0x100 + 0x20 * index);
        let regs = unsafe { DeviceMemoryIo::<TimerRegs>::map(base, Default::default()) }?;

        let period = hpet.base_frequency / frequency;

        let periodic = regs.is_periodic();
        let bits64 = regs.is_64_bit();

        log::info!("hpet{index}: periodic={periodic}, bits64={bits64}, period={period}");

        let timer = Arc::new(HpetTimer {
            name: format!("hpet{index}"),
            regs: IrqSafeRwLock::new(regs),
            parent: hpet,
            last_ticks: AtomicU64::new(0),
            system_clock_source,
            periodic,
            period,
            bits64,
        });

        Ok(timer)
    }
}

impl Hpet {
    fn new(base: PhysicalAddress) -> Result<Self, Error> {
        let regs = unsafe { DeviceMemoryIo::<GeneralRegs>::map(base, Default::default()) }?;
        let base_period = regs.timer_period();
        // qemu: 100000000
        // t430: 14318179
        let base_frequency = FS_IN_S / base_period;

        let hpet = Self {
            base,
            base_period,
            base_frequency,
            regs: IrqSafeRwLock::new(regs),
            timers: IrqSafeRwLock::new(BTreeMap::new()),
        };

        hpet.test_timer_counts()?;

        Ok(hpet)
    }

    fn test_timer_counts(&self) -> Result<(), Error> {
        let regs = self.regs.write();
        regs.stop();
        for _ in 0..10000 {
            core::hint::spin_loop();
        }
        let start = regs.counter();
        regs.start();
        for _ in 0..100000 {
            core::hint::spin_loop();
        }
        regs.stop();
        let now = regs.counter();

        if now > start {
            Ok(())
        } else {
            Err(Error::InvalidOperation)
        }
    }

    pub fn setup_from_acpi(acpi: &HpetInfo) -> Result<Arc<Self>, Error> {
        let cx = dummy_init_context();
        let base = PhysicalAddress::from_usize(acpi.base_address);
        let hpet = Arc::new(Self::new(base)?);
        unsafe { hpet.clone().init(cx) }?;
        Ok(hpet)
    }

    pub fn setup_timer(
        self: Arc<Self>,
        index: usize,
        frequency: u64,
        system_clock_source: bool,
    ) -> Result<Arc<HpetTimer>, Error> {
        let regs = self.regs.write();
        let mut timers = self.timers.write();
        let timer_count = regs.timer_count();

        if index >= timer_count {
            log::warn!("HPET #{index} does not exist");
            return Err(Error::DoesNotExist);
        }

        if timers.contains_key(&index) {
            log::warn!("HPET #{index} already configured");
            return Err(Error::AlreadyExists);
        }

        // Pause the timer
        regs.stop();

        drop(regs);

        let timer = HpetTimer::create(self.clone(), index, frequency, system_clock_source);
        let timer = if let Ok(timer) = timer {
            timers.insert(index, timer.clone());

            let cx = dummy_init_context();
            match unsafe { timer.clone().init(cx) } {
                Ok(()) => Ok(timer),
                Err(error) => Err(error),
            }
        } else {
            timer
        };

        let regs = self.regs.write();

        regs.start();

        timer
    }

    fn read_counter(&self) -> u64 {
        self.regs.read().counter()
    }
}

impl TimerRegs {
    fn is_periodic(&self) -> bool {
        self.Tn_CONFIG.matches_all(Tn_CONFIG::Tn_PER_INT_CAP::SET)
    }

    fn is_64_bit(&self) -> bool {
        self.Tn_CONFIG.matches_all(Tn_CONFIG::Tn_SIZE_CAP::SET)
    }

    fn is_fsb_capable(&self) -> bool {
        self.Tn_CONFIG
            .matches_all(Tn_CONFIG::Tn_FSB_INT_DEL_CAP::SET)
    }

    fn select_irq(&self) -> Option<Irq> {
        let mask = self.Tn_CONFIG.read(Tn_CONFIG::Tn_INT_ROUTE_CAP);
        for i in 9..32 {
            if mask & (1 << i) != 0 {
                return Some(Irq::External(i));
            }
        }
        None
    }

    fn stop(&self) {
        self.Tn_CONFIG.modify(Tn_CONFIG::Tn_INT_ENB_CNF::CLEAR);
    }

    fn enable(&self, irq: IrqDestination) {
        match irq {
            IrqDestination::Irq(Irq::External(irq)) => {
                self.Tn_CONFIG.modify(
                    Tn_CONFIG::Tn_INT_ROUTE_CNF.val(irq as u64)
                        + Tn_CONFIG::Tn_INT_TYPE_CNF::Edge
                        + Tn_CONFIG::Tn_FSB_EN_CNF::CLEAR,
                );
            }
            IrqDestination::Fsb(MsiInfo { address, value, .. }) => {
                let address: u32 = address
                    .try_into()
                    .expect("FIXME: non-32-bit LAPIC MSI address");
                self.Tn_CONFIG
                    .modify(Tn_CONFIG::Tn_INT_TYPE_CNF::Edge + Tn_CONFIG::Tn_FSB_EN_CNF::SET);
                self.Tn_FSB_ROUTE.write(
                    Tn_FSB_ROUTE::Tn_FSB_INT_VAL.val(value as u64)
                        + Tn_FSB_ROUTE::Tn_FSB_INT_ADDR.val(address as u64),
                );
            }
            _ => unreachable!(),
        }

        self.Tn_CONFIG.modify(Tn_CONFIG::Tn_INT_ENB_CNF::SET);
    }

    fn configure_oneshot(&self, compare_value: u64, irq: IrqDestination) {
        self.Tn_CONFIG
            .modify(Tn_CONFIG::Tn_32MODE_CNF::CLEAR + Tn_CONFIG::Tn_TYPE_CNF::NonPeriodic);

        self.Tn_COMPARATOR.set(compare_value);

        self.enable(irq);
    }

    fn configure_periodic(&self, compare_value: u64, period: u64, irq: IrqDestination) {
        self.Tn_CONFIG.modify(
            Tn_CONFIG::Tn_VAL_SET_CNF::SET
                + Tn_CONFIG::Tn_32MODE_CNF::CLEAR
                + Tn_CONFIG::Tn_TYPE_CNF::Periodic,
        );
        self.Tn_COMPARATOR.set(compare_value);
        self.Tn_COMPARATOR.set(period);

        self.enable(irq);
    }

    fn rearm(&self, compare_value: u64) {
        self.Tn_COMPARATOR.set(compare_value);
    }
}

impl GeneralRegs {
    fn stop(&self) {
        self.GENERAL_CONFIG
            .modify(GENERAL_CONFIG::ENABLE_CNF::CLEAR);
    }
    fn start(&self) {
        self.GENERAL_CONFIG.modify(GENERAL_CONFIG::ENABLE_CNF::SET);
    }

    fn timer_count(&self) -> usize {
        self.GENERAL_CAPABILITIES
            .read(GENERAL_CAPABILITIES::NUM_TIM_CAP) as usize
            + 1
    }

    fn timer_period(&self) -> u64 {
        self.GENERAL_CAPABILITIES
            .read(GENERAL_CAPABILITIES::COUNTER_CLK_PERIOD)
    }

    fn counter(&self) -> u64 {
        self.MAIN_COUNTER.get()
    }
}

enum IrqDestination {
    Irq(Irq),
    Fsb(MsiInfo),
}
