//! Low-level RTC driver.

#![macro_use]

use core::marker::PhantomData;

use embassy_hal_internal::interrupt::InterruptExt;
use embassy_hal_internal::{Peri, PeripheralType};

use crate::interrupt::typelevel::Interrupt as _;
use crate::{interrupt, pac};

/// Prescaler has an invalid value which exceeds 12 bits.
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PrescalerOutOfRangeError(u32);

/// Compare value has an invalid value which exceeds 24 bits.
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CompareOutOfRangeError(u32);

/// Interrupts/Events that can be generated by the RTCn peripheral.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Interrupt {
    /// Tick interrupt.
    Tick,
    /// Overflow interrupt.
    Overflow,
    /// Compare 0 interrupt.
    Compare0,
    /// Compare 1 interrupt.
    Compare1,
    /// Compare 2 interrupt.
    Compare2,
    /// Compare 3 interrupt. Only implemented for RTC1 and RTC2.
    Compare3,
}

/// Compare registers available on the RTCn.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CompareChannel {
    /// Channel 0
    _0,
    /// Channel 1
    _1,
    /// Channel 2
    _2,
    /// Channel 3. Only implemented for RTC1 and RTC2.
    _3,
}

pub(crate) trait SealedInstance {
    fn regs() -> pac::rtc::Rtc;
}

/// Basic RTC instance.
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType + 'static + Send {
    /// Interrupt for this peripheral.
    type Interrupt: crate::interrupt::typelevel::Interrupt;

    /// Unsafely create a peripheral instance.
    ///
    /// # Safety
    ///
    /// Potentially allows to create multiple instances of the driver for the same peripheral
    /// which can lead to undefined behavior.
    unsafe fn steal() -> Peri<'static, Self>;
}

macro_rules! impl_rtc {
    ($type:ident, $pac_type:ident, $irq:ident) => {
        impl crate::rtc::SealedInstance for peripherals::$type {
            #[inline]
            fn regs() -> pac::rtc::Rtc {
                unsafe { pac::rtc::Rtc::from_ptr(pac::$pac_type.as_ptr()) }
            }
        }

        impl crate::rtc::Instance for peripherals::$type {
            type Interrupt = crate::interrupt::typelevel::$irq;

            unsafe fn steal() -> embassy_hal_internal::Peri<'static, Self> {
                unsafe { peripherals::$type::steal() }
            }
        }
    };
}

/// nRF RTC driver.
pub struct Rtc<'d> {
    r: pac::rtc::Rtc,
    irq: interrupt::Interrupt,
    _phantom: PhantomData<&'d ()>,
}

impl<'d> Rtc<'d> {
    /// Create a new `Rtc` driver.
    ///
    /// fRTC \[Hz\] = 32_768 / (`prescaler` + 1 )
    pub fn new<T: Instance>(_rtc: Peri<'d, T>, prescaler: u32) -> Result<Self, PrescalerOutOfRangeError> {
        if prescaler >= (1 << 12) {
            return Err(PrescalerOutOfRangeError(prescaler));
        }

        T::regs().prescaler().write(|w| w.set_prescaler(prescaler as u16));
        Ok(Self {
            r: T::regs(),
            irq: T::Interrupt::IRQ,
            _phantom: PhantomData,
        })
    }

    /// Create a new `Rtc` driver, configuring it to run at the given frequency.
    pub fn new_for_freq<T: Instance>(rtc: Peri<'d, T>, freq_hz: u32) -> Result<Self, PrescalerOutOfRangeError> {
        let prescaler = (32_768 / freq_hz).saturating_sub(1);
        Self::new(rtc, prescaler)
    }

    /// Steal the RTC peripheral, without checking if it's already taken.
    ///
    /// # Safety
    ///
    /// Potentially allows to create multiple instances of the driver for the same peripheral
    /// which can lead to undefined behavior.
    pub unsafe fn steal<T: Instance>() -> Self {
        Self {
            r: T::regs(),
            irq: T::Interrupt::IRQ,
            _phantom: PhantomData,
        }
    }

    /// Direct access to the RTC registers.
    #[cfg(feature = "unstable-pac")]
    #[inline]
    pub fn regs(&mut self) -> pac::rtc::Rtc {
        self.r
    }

    /// Enable the RTC.
    #[inline]
    pub fn enable(&mut self) {
        self.r.tasks_start().write_value(1);
    }

    /// Disable the RTC.
    #[inline]
    pub fn disable(&mut self) {
        self.r.tasks_stop().write_value(1);
    }

    /// Enables interrupts for the given [Interrupt] source.
    ///
    /// Optionally also enables the interrupt in the NVIC.
    pub fn enable_interrupt(&mut self, int: Interrupt, enable_in_nvic: bool) {
        let regs = self.r;
        match int {
            Interrupt::Tick => regs.intenset().write(|w| w.set_tick(true)),
            Interrupt::Overflow => regs.intenset().write(|w| w.set_ovrflw(true)),
            Interrupt::Compare0 => regs.intenset().write(|w| w.set_compare(0, true)),
            Interrupt::Compare1 => regs.intenset().write(|w| w.set_compare(1, true)),
            Interrupt::Compare2 => regs.intenset().write(|w| w.set_compare(2, true)),
            Interrupt::Compare3 => regs.intenset().write(|w| w.set_compare(3, true)),
        }
        if enable_in_nvic {
            unsafe { self.irq.enable() };
        }
    }

    /// Disables interrupts for the given [Interrupt] source.
    ///
    /// Optionally also disables the interrupt in the NVIC.
    pub fn disable_interrupt(&mut self, int: Interrupt, disable_in_nvic: bool) {
        let regs = self.r;
        match int {
            Interrupt::Tick => regs.intenclr().write(|w| w.set_tick(true)),
            Interrupt::Overflow => regs.intenclr().write(|w| w.set_ovrflw(true)),
            Interrupt::Compare0 => regs.intenclr().write(|w| w.set_compare(0, true)),
            Interrupt::Compare1 => regs.intenclr().write(|w| w.set_compare(1, true)),
            Interrupt::Compare2 => regs.intenclr().write(|w| w.set_compare(2, true)),
            Interrupt::Compare3 => regs.intenclr().write(|w| w.set_compare(3, true)),
        }
        if disable_in_nvic {
            self.irq.disable();
        }
    }

    /// Enable the generation of a hardware event from a given stimulus.
    pub fn enable_event(&mut self, evt: Interrupt) {
        let regs = self.r;
        match evt {
            Interrupt::Tick => regs.evtenset().write(|w| w.set_tick(true)),
            Interrupt::Overflow => regs.evtenset().write(|w| w.set_ovrflw(true)),
            Interrupt::Compare0 => regs.evtenset().write(|w| w.set_compare(0, true)),
            Interrupt::Compare1 => regs.evtenset().write(|w| w.set_compare(1, true)),
            Interrupt::Compare2 => regs.evtenset().write(|w| w.set_compare(2, true)),
            Interrupt::Compare3 => regs.evtenset().write(|w| w.set_compare(3, true)),
        }
    }

    /// Disable the generation of a hardware event from a given stimulus.
    pub fn disable_event(&mut self, evt: Interrupt) {
        let regs = self.r;
        match evt {
            Interrupt::Tick => regs.evtenclr().write(|w| w.set_tick(true)),
            Interrupt::Overflow => regs.evtenclr().write(|w| w.set_ovrflw(true)),
            Interrupt::Compare0 => regs.evtenclr().write(|w| w.set_compare(0, true)),
            Interrupt::Compare1 => regs.evtenclr().write(|w| w.set_compare(1, true)),
            Interrupt::Compare2 => regs.evtenclr().write(|w| w.set_compare(2, true)),
            Interrupt::Compare3 => regs.evtenclr().write(|w| w.set_compare(3, true)),
        }
    }

    /// Resets the given event.
    pub fn reset_event(&mut self, evt: Interrupt) {
        let regs = self.r;
        match evt {
            Interrupt::Tick => regs.events_tick().write_value(0),
            Interrupt::Overflow => regs.events_ovrflw().write_value(0),
            Interrupt::Compare0 => regs.events_compare(0).write_value(0),
            Interrupt::Compare1 => regs.events_compare(1).write_value(0),
            Interrupt::Compare2 => regs.events_compare(2).write_value(0),
            Interrupt::Compare3 => regs.events_compare(3).write_value(0),
        }
    }

    /// Checks if the given event has been triggered.
    pub fn is_event_triggered(&self, evt: Interrupt) -> bool {
        let regs = self.r;
        let val = match evt {
            Interrupt::Tick => regs.events_tick().read(),
            Interrupt::Overflow => regs.events_ovrflw().read(),
            Interrupt::Compare0 => regs.events_compare(0).read(),
            Interrupt::Compare1 => regs.events_compare(1).read(),
            Interrupt::Compare2 => regs.events_compare(2).read(),
            Interrupt::Compare3 => regs.events_compare(3).read(),
        };
        val == 1
    }

    /// Set the compare value of a given register. The compare registers have a width
    /// of 24 bits.
    pub fn set_compare(&mut self, reg: CompareChannel, val: u32) -> Result<(), CompareOutOfRangeError> {
        if val >= (1 << 24) {
            return Err(CompareOutOfRangeError(val));
        }

        let reg = match reg {
            CompareChannel::_0 => 0,
            CompareChannel::_1 => 1,
            CompareChannel::_2 => 2,
            CompareChannel::_3 => 3,
        };

        self.r.cc(reg).write(|w| w.set_compare(val));
        Ok(())
    }

    /// Clear the Real Time Counter.
    #[inline]
    pub fn clear(&self) {
        self.r.tasks_clear().write_value(1);
    }

    /// Obtain the current value of the Real Time Counter, 24 bits of range.
    #[inline]
    pub fn read(&self) -> u32 {
        self.r.counter().read().counter()
    }
}
