use core::cell::{Cell, RefCell};
use core::sync::atomic::{AtomicU32, Ordering, compiler_fence};
use core::task::Waker;

use critical_section::{CriticalSection, Mutex};
use embassy_time_driver::Driver;
use embassy_time_queue_utils::Queue;
use mspm0_metapac::interrupt;
use mspm0_metapac::tim::vals::{Cm, Cvae, CxC, EvtCfg, PwrenKey, Ratio, Repeat, ResetKey};
use mspm0_metapac::tim::{Counterregs16, Tim};

use crate::peripherals;
use crate::timer::SealedTimer;

#[cfg(any(time_driver_timg12, time_driver_timg13))]
compile_error!("TIMG12 and TIMG13 are not supported by the time driver yet");

// Currently TIMG12 and TIMG13 are excluded because those are 32-bit timers.
#[cfg(time_driver_timb0)]
type T = peripherals::TIMB0;
#[cfg(time_driver_timg0)]
type T = peripherals::TIMG0;
#[cfg(time_driver_timg1)]
type T = peripherals::TIMG1;
#[cfg(time_driver_timg2)]
type T = peripherals::TIMG2;
#[cfg(time_driver_timg3)]
type T = peripherals::TIMG3;
#[cfg(time_driver_timg4)]
type T = peripherals::TIMG4;
#[cfg(time_driver_timg5)]
type T = peripherals::TIMG5;
#[cfg(time_driver_timg6)]
type T = peripherals::TIMG6;
#[cfg(time_driver_timg7)]
type T = peripherals::TIMG7;
#[cfg(time_driver_timg8)]
type T = peripherals::TIMG8;
#[cfg(time_driver_timg9)]
type T = peripherals::TIMG9;
#[cfg(time_driver_timg10)]
type T = peripherals::TIMG10;
#[cfg(time_driver_timg11)]
type T = peripherals::TIMG11;
#[cfg(time_driver_timg14)]
type T = peripherals::TIMG14;
#[cfg(time_driver_tima0)]
type T = peripherals::TIMA0;
#[cfg(time_driver_tima1)]
type T = peripherals::TIMA1;

// TODO: RTC

fn regs() -> Tim {
    unsafe { Tim::from_ptr(T::regs()) }
}

fn regs_counter(tim: Tim) -> Counterregs16 {
    unsafe { Counterregs16::from_ptr(tim.counterregs(0).as_ptr()) }
}

/// Clock timekeeping works with something we call "periods", which are time intervals
/// of 2^15 ticks. The Clock counter value is 16 bits, so one "overflow cycle" is 2 periods.
fn calc_now(period: u32, counter: u16) -> u64 {
    ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64)
}

/// The TIMx driver uses one of the `TIMG` or `TIMA` timer instances to implement a timer with a 32.768 kHz
/// tick rate. (TODO: Allow setting the tick rate)
///
/// This driver defines a period to be 2^15 ticks. 16-bit timers of course count to 2^16 ticks.
///
/// To generate a period every 2^15 ticks, the CC0 value is set to 2^15 and the load value set to 2^16.
/// Incrementing the period on a CCU0 and load results in the a period of 2^15 ticks.
///
/// For a specific timestamp, load the lower 16 bits into the CC1 value. When the period where the timestamp
/// should be enabled is reached, then the CCU1 (CC1 up) interrupt runs to actually wake the timer.
///
/// TODO: Compensate for per part variance. This can supposedly be done with the FCC system.
/// TODO: Allow using 32-bit timers (TIMG12 and TIMG13).
struct TimxDriver {
    /// Number of 2^15 periods elapsed since boot.
    period: AtomicU32,
    /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
    alarm: Mutex<Cell<u64>>,
    queue: Mutex<RefCell<Queue>>,
}

impl TimxDriver {
    #[inline(never)]
    fn init(&'static self, _cs: CriticalSection) {
        // Clock config
        // TODO: Configurable tick rate up to 4 MHz (32 kHz for now)
        let regs = regs();

        // Reset timer
        regs.gprcm(0).rstctl().write(|w| {
            w.set_resetassert(true);
            w.set_key(ResetKey::KEY);
            w.set_resetstkyclr(true);
        });

        // Power up timer
        regs.gprcm(0).pwren().write(|w| {
            w.set_enable(true);
            w.set_key(PwrenKey::KEY);
        });

        // Following the instructions according to SLAU847D 23.2.1: TIMCLK Configuration

        // 1. Select TIMCLK source
        regs.clksel().modify(|w| {
            // Use LFCLK for a 32.768kHz tick rate
            w.set_lfclk_sel(true);
            // TODO: Allow MFCLK for configurable tick rate up to 4 MHz
            // w.set_mfclk_sel(ClkSel::ENABLE);
        });

        // 2. Divide by TIMCLK, we don't need to divide further for the 32kHz tick rate
        regs.clkdiv().modify(|w| {
            w.set_ratio(Ratio::DIV_BY_1);
        });

        // 3. To be generic across timer instances, we do not use the prescaler.
        // TODO: mspm0-sdk always sets this, regardless of timer width?
        regs.commonregs(0).cps().modify(|w| {
            w.set_pcnt(0);
        });

        regs.pdbgctl().modify(|w| {
            w.set_free(true);
        });

        // 4. Enable the TIMCLK.
        regs.commonregs(0).cclkctl().modify(|w| {
            w.set_clken(true);
        });

        regs.counterregs(0).ctrctl().modify(|w| {
            // allow counting during debug
            w.set_repeat(Repeat::REPEAT_3);
            w.set_cvae(Cvae::ZEROVAL);
            w.set_cm(Cm::UP);

            // Must explicitly set CZC, CAC and CLC to 0 in order for all the timers to count.
            //
            // The reset value of these registers is 0x07, which is a reserved value.
            //
            // Looking at a bit representation of the reset value, this appears to be an AND
            // of 2-input QEI mode and CCCTL_3 ACOND. Given that TIMG14 and TIMA0 have no QEI
            // and 4 capture and compare channels, this works by accident for those timer units.
            w.set_czc(CxC::CCTL0);
            w.set_cac(CxC::CCTL0);
            w.set_clc(CxC::CCTL0);
        });

        // Setup the period
        let ctr = regs_counter(regs);

        // Middle
        ctr.cc(0).modify(|w| {
            w.set_ccval(0x7FFF);
        });

        ctr.load().modify(|w| {
            w.set_ld(u16::MAX);
        });

        // Enable the period interrupts
        //
        // This does not appear to ever be set for CPU_INT in the TI SDK and is not technically needed.
        regs.evt_mode().modify(|w| {
            w.set_evt_cfg(0, EvtCfg::SOFTWARE);
        });

        regs.int_event(0).imask().modify(|w| {
            w.set_l(true);
            w.set_ccu0(true);
        });

        unsafe { T::enable_interrupt() };

        // Allow the counter to start counting.
        regs.counterregs(0).ctrctl().modify(|w| {
            w.set_en(true);
        });
    }

    #[inline(never)]
    fn next_period(&self) {
        let r = regs();

        // We only modify the period from the timer interrupt, so we know this can't race.
        let period = self.period.load(Ordering::Relaxed) + 1;
        self.period.store(period, Ordering::Relaxed);
        let t = (period as u64) << 15;

        critical_section::with(move |cs| {
            r.int_event(0).imask().modify(move |w| {
                let alarm = self.alarm.borrow(cs);
                let at = alarm.get();

                if at < t + 0xC000 {
                    // just enable it. `set_alarm` has already set the correct CC1 val.
                    w.set_ccu1(true);
                }
            })
        });
    }

    #[inline(never)]
    fn on_interrupt(&self) {
        let r = regs();

        critical_section::with(|cs| {
            let mis = r.int_event(0).mis().read();

            // Advance to next period if overflowed
            if mis.l() {
                self.next_period();

                r.int_event(0).iclr().write(|w| {
                    w.set_l(true);
                });
            }

            if mis.ccu0() {
                self.next_period();

                r.int_event(0).iclr().write(|w| {
                    w.set_ccu0(true);
                });
            }

            if mis.ccu1() {
                r.int_event(0).iclr().write(|w| {
                    w.set_ccu1(true);
                });

                self.trigger_alarm(cs);
            }
        });
    }

    fn trigger_alarm(&self, cs: CriticalSection) {
        let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());

        while !self.set_alarm(cs, next) {
            next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now());
        }
    }

    fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
        let r = regs();
        let ctr = regs_counter(r);

        self.alarm.borrow(cs).set(timestamp);

        let t = self.now();

        if timestamp <= t {
            // If alarm timestamp has passed the alarm will not fire.
            // Disarm the alarm and return `false` to indicate that.
            r.int_event(0).imask().modify(|w| w.set_ccu1(false));

            self.alarm.borrow(cs).set(u64::MAX);

            return false;
        }

        // Write the CC1 value regardless of whether we're going to enable it now or not.
        // This way, when we enable it later, the right value is already set.
        ctr.cc(1).write(|w| {
            w.set_ccval(timestamp as u16);
        });

        // Enable it if it'll happen soon. Otherwise, `next_period` will enable it.
        let diff = timestamp - t;
        r.int_event(0).imask().modify(|w| w.set_ccu1(diff < 0xC000));

        // Reevaluate if the alarm timestamp is still in the future
        let t = self.now();
        if timestamp <= t {
            // If alarm timestamp has passed since we set it, we have a race condition and
            // the alarm may or may not have fired.
            // Disarm the alarm and return `false` to indicate that.
            // It is the caller's responsibility to handle this ambiguity.
            r.int_event(0).imask().modify(|w| w.set_ccu1(false));

            self.alarm.borrow(cs).set(u64::MAX);

            return false;
        }

        // We're confident the alarm will ring in the future.
        true
    }
}

impl Driver for TimxDriver {
    fn now(&self) -> u64 {
        let regs = regs();

        let period = self.period.load(Ordering::Relaxed);
        // Ensure the compiler does not read the counter before the period.
        compiler_fence(Ordering::Acquire);

        let counter = regs_counter(regs).ctr().read().cctr() as u16;

        calc_now(period, counter)
    }

    fn schedule_wake(&self, at: u64, waker: &Waker) {
        critical_section::with(|cs| {
            let mut queue = self.queue.borrow(cs).borrow_mut();

            if queue.schedule_wake(at, waker) {
                let mut next = queue.next_expiration(self.now());

                while !self.set_alarm(cs, next) {
                    next = queue.next_expiration(self.now());
                }
            }
        });
    }
}

embassy_time_driver::time_driver_impl!(static DRIVER: TimxDriver = TimxDriver {
    period: AtomicU32::new(0),
    alarm: Mutex::new(Cell::new(u64::MAX)),
    queue: Mutex::new(RefCell::new(Queue::new()))
});

pub(crate) fn init(cs: CriticalSection) {
    DRIVER.init(cs);
}

// TODO: TIMB0

#[cfg(time_driver_timg0)]
#[interrupt]
fn TIMG0() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg1)]
#[interrupt]
fn TIMG1() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg2)]
#[interrupt]
fn TIMG2() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg3)]
#[interrupt]
fn TIMG3() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg4)]
#[interrupt]
fn TIMG4() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg5)]
#[interrupt]
fn TIMG5() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg6)]
#[interrupt]
fn TIMG6() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg7)]
#[interrupt]
fn TIMG7() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg8)]
#[interrupt]
fn TIMG8() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg9)]
#[interrupt]
fn TIMG9() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg10)]
#[interrupt]
fn TIMG10() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_timg11)]
#[interrupt]
fn TIMG11() {
    DRIVER.on_interrupt();
}

// TODO: TIMG12 and TIMG13

#[cfg(time_driver_timg14)]
#[interrupt]
fn TIMG14() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_tima0)]
#[interrupt]
fn TIMA0() {
    DRIVER.on_interrupt();
}

#[cfg(time_driver_tima1)]
#[interrupt]
fn TIMA1() {
    DRIVER.on_interrupt();
}
