//! NFC tag emulator driver.
//!
//! This driver implements support for emulating an ISO14443-3 card. Anticollision and selection
//! are handled automatically in hardware, then the driver lets you receive and reply to
//! raw ISO14443-3 frames in software.
//!
//! Higher layers such as ISO14443-4 aka ISO-DEP and ISO7816 must be handled on top
//! in software.

#![macro_use]

use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;

use embassy_hal_internal::{into_ref, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
pub use vals::{Bitframesdd as SddPat, Discardmode as DiscardMode};

use crate::interrupt::InterruptExt;
use crate::pac::nfct::vals;
use crate::pac::NFCT;
use crate::peripherals::NFCT;
use crate::util::slice_in_ram;
use crate::{interrupt, pac, Peripheral};

/// NFCID1 (aka UID) of different sizes.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum NfcId {
    /// 4-byte UID.
    SingleSize([u8; 4]),
    /// 7-byte UID.
    DoubleSize([u8; 7]),
    /// 10-byte UID.
    TripleSize([u8; 10]),
}

/// The protocol field to be sent in the `SEL_RES` response byte (b6-b7).
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum SelResProtocol {
    /// Configured for Type 2 Tag platform.
    #[default]
    Type2 = 0,
    /// Configured for Type 4A Tag platform, compliant with ISO/IEC_14443.
    Type4A = 1,
    /// Configured for the NFC-DEP Protocol.
    NfcDep = 2,
    /// Configured for the NFC-DEP Protocol and Type 4A Tag platform.
    NfcDepAndType4A = 3,
}

/// Config for the `NFCT` peripheral driver.
#[derive(Clone)]
pub struct Config {
    /// NFCID1 to use during autocollision.
    pub nfcid1: NfcId,
    /// SDD pattern to be sent in `SENS_RES`.
    pub sdd_pat: SddPat,
    /// Platform config to be sent in `SEL_RES`.
    pub plat_conf: u8,
    /// Protocol to be sent in the `SEL_RES` response.
    pub protocol: SelResProtocol,
}

/// Interrupt handler.
pub struct InterruptHandler {
    _private: (),
}

impl interrupt::typelevel::Handler<interrupt::typelevel::NFCT> for InterruptHandler {
    unsafe fn on_interrupt() {
        trace!("irq");
        pac::NFCT.inten().write(|w| w.0 = 0);
        WAKER.wake();
    }
}

static WAKER: AtomicWaker = AtomicWaker::new();

/// NFC error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
    /// Rx Error received while waiting for frame
    RxError,
    /// Rx buffer was overrun, increase your buffer size to resolve this
    RxOverrun,
    /// Lost field.
    Deactivated,
    /// Collision
    Collision,
    /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash.
    BufferNotInRAM,
}

/// NFC tag emulator driver.
pub struct NfcT<'d> {
    _p: PeripheralRef<'d, NFCT>,
    rx_buf: [u8; 256],
    tx_buf: [u8; 256],
}

impl<'d> NfcT<'d> {
    /// Create an Nfc Tag driver
    pub fn new(
        _p: impl Peripheral<P = NFCT> + 'd,
        _irq: impl interrupt::typelevel::Binding<interrupt::typelevel::NFCT, InterruptHandler> + 'd,
        config: &Config,
    ) -> Self {
        into_ref!(_p);

        let r = pac::NFCT;

        unsafe {
            let reset = (r.as_ptr() as *mut u32).add(0xFFC / 4);
            reset.write_volatile(0);
            reset.read_volatile();
            reset.write_volatile(1);
        }

        let nfcid_size = match &config.nfcid1 {
            NfcId::SingleSize(bytes) => {
                r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*bytes));

                vals::Nfcidsize::NFCID1SINGLE
            }
            NfcId::DoubleSize(bytes) => {
                let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap();
                r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk));

                let mut chunk = [0u8; 4];
                chunk[1..].copy_from_slice(bytes);
                r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk));

                vals::Nfcidsize::NFCID1DOUBLE
            }
            NfcId::TripleSize(bytes) => {
                let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap();
                r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk));

                let (bytes, chunk2) = bytes.split_last_chunk::<3>().unwrap();
                let mut chunk = [0u8; 4];
                chunk[1..].copy_from_slice(chunk2);
                r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk));

                let mut chunk = [0u8; 4];
                chunk[1..].copy_from_slice(bytes);
                r.nfcid1_3rd_last().write(|w| w.0 = u32::from_be_bytes(chunk));

                vals::Nfcidsize::NFCID1TRIPLE
            }
        };

        r.sensres().write(|w| {
            w.set_nfcidsize(nfcid_size);
            w.set_bitframesdd(config.sdd_pat);
            w.set_platfconfig(config.plat_conf & 0xF);
        });

        r.selres().write(|w| {
            w.set_protocol(config.protocol as u8);
        });

        // errata
        #[cfg(feature = "nrf52832")]
        unsafe {
            // Errata 57 nrf52832 only
            //(0x40005610 as *mut u32).write_volatile(0x00000005);
            //(0x40005688 as *mut u32).write_volatile(0x00000001);
            //(0x40005618 as *mut u32).write_volatile(0x00000000);
            //(0x40005614 as *mut u32).write_volatile(0x0000003F);

            // Errata 98
            (0x4000568C as *mut u32).write_volatile(0x00038148);
        }

        r.inten().write(|w| w.0 = 0);

        interrupt::NFCT.unpend();
        unsafe { interrupt::NFCT.enable() };

        // clear all shorts
        r.shorts().write(|_| {});

        let res = Self {
            _p,
            tx_buf: [0u8; 256],
            rx_buf: [0u8; 256],
        };

        assert!(slice_in_ram(&res.tx_buf), "TX Buf not in ram");
        assert!(slice_in_ram(&res.rx_buf), "RX Buf not in ram");

        res
    }

    /// Wait for field on and select.
    ///
    /// This waits for the field to become on, and then for a reader to select us. The ISO14443-3
    /// sense, anticollision and select procedure is handled entirely in hardware.
    ///
    /// When this returns, we have successfully been selected as a card. You must then
    /// loop calling [`receive`](Self::receive) and responding with [`transmit`](Self::transmit).
    pub async fn activate(&mut self) {
        let r = pac::NFCT;
        loop {
            r.events_fieldlost().write_value(0);
            r.events_fielddetected().write_value(0);
            r.tasks_sense().write_value(1);

            // enable autocoll
            #[cfg(not(feature = "nrf52832"))]
            r.autocolresconfig().write(|w| w.0 = 0b10);

            // framedelaymax=4096 is needed to make it work with phones from
            // a certain company named after some fruit.
            r.framedelaymin().write(|w| w.set_framedelaymin(1152));
            r.framedelaymax().write(|w| w.set_framedelaymax(4096));
            r.framedelaymode().write(|w| {
                w.set_framedelaymode(vals::Framedelaymode::WINDOW_GRID);
            });

            info!("waiting for field");
            poll_fn(|cx| {
                WAKER.register(cx.waker());

                if r.events_fielddetected().read() != 0 {
                    r.events_fielddetected().write_value(0);
                    return Poll::Ready(());
                }

                r.inten().write(|w| {
                    w.set_fielddetected(true);
                });
                Poll::Pending
            })
            .await;

            #[cfg(feature = "time")]
            embassy_time::Timer::after_millis(1).await; // workaround errata 190

            r.events_selected().write_value(0);
            r.tasks_activate().write_value(1);

            trace!("Waiting to be selected");
            poll_fn(|cx| {
                let r = pac::NFCT;

                WAKER.register(cx.waker());

                if r.events_selected().read() != 0 || r.events_fieldlost().read() != 0 {
                    return Poll::Ready(());
                }

                r.inten().write(|w| {
                    w.set_selected(true);
                    w.set_fieldlost(true);
                });
                Poll::Pending
            })
            .await;
            if r.events_fieldlost().read() != 0 {
                continue;
            }

            // disable autocoll
            #[cfg(not(feature = "nrf52832"))]
            r.autocolresconfig().write(|w| w.0 = 0b11u32);

            // once anticoll is done, set framedelaymax to the maximum possible.
            // this gives the firmware as much time as possible to reply.
            // higher layer still has to reply faster than the FWT it specifies in the iso14443-4 ATS,
            // but that's not our concern.
            //
            // nrf52832 field is 16bit instead of 20bit. this seems to force a too short timeout, maybe it's a SVD bug?
            #[cfg(not(feature = "nrf52832"))]
            r.framedelaymax().write(|w| w.set_framedelaymax(0xF_FFFF));
            #[cfg(feature = "nrf52832")]
            r.framedelaymax().write(|w| w.set_framedelaymax(0xFFFF));

            return;
        }
    }

    /// Transmit an ISO14443-3 frame to the reader.
    ///
    /// You must call this only after receiving a frame with [`receive`](Self::receive),
    /// and only once. Higher-layer protocols usually define timeouts, so calling this
    /// too late can cause things to fail.
    ///
    /// This will fail with [`Error::Deactivated`] if we have been deselected due to either
    /// the field being switched off or due to the ISO14443 state machine. When this happens,
    /// you must stop calling [`receive`](Self::receive) and [`transmit`](Self::transmit), reset
    /// all protocol state, and go back to calling [`activate`](Self::activate).
    pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), Error> {
        let r = pac::NFCT;

        //Setup DMA
        self.tx_buf[..buf.len()].copy_from_slice(buf);
        r.packetptr().write_value(self.tx_buf.as_ptr() as u32);
        r.maxlen().write(|w| w.0 = buf.len() as _);

        // Set packet length
        r.txd().amount().write(|w| {
            w.set_txdatabits(0);
            w.set_txdatabytes(buf.len() as _);
        });

        r.txd().frameconfig().write(|w| {
            w.set_crcmodetx(true);
            w.set_discardmode(DiscardMode::DISCARD_END);
            w.set_parity(true);
            w.set_sof(true);
        });

        r.events_error().write_value(0);
        r.events_txframeend().write_value(0);
        r.errorstatus().write(|w| w.0 = 0xffff_ffff);

        // Start starttx task
        compiler_fence(Ordering::SeqCst);
        r.tasks_starttx().write_value(1);

        poll_fn(move |cx| {
            trace!("polling tx");
            let r = pac::NFCT;
            WAKER.register(cx.waker());

            if r.events_fieldlost().read() != 0 {
                return Poll::Ready(Err(Error::Deactivated));
            }

            if r.events_txframeend().read() != 0 {
                trace!("Txframend hit, should be finished trasmitting");
                return Poll::Ready(Ok(()));
            }

            if r.events_error().read() != 0 {
                trace!("Got error?");
                let errs = r.errorstatus().read();
                r.errorstatus().write(|w| w.0 = 0xFFFF_FFFF);
                trace!("errors: {:08x}", errs.0);
                r.events_error().write_value(0);
                return Poll::Ready(Err(Error::RxError));
            }

            r.inten().write(|w| {
                w.set_txframeend(true);
                w.set_error(true);
                w.set_fieldlost(true);
            });

            Poll::Pending
        })
        .await
    }

    /// Receive an ISO14443-3 frame from the reader.
    ///
    /// After calling this, you must send back a response with [`transmit`](Self::transmit),
    /// and only once. Higher-layer protocols usually define timeouts, so calling this
    /// too late can cause things to fail.
    pub async fn receive(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
        let r = pac::NFCT;

        r.rxd().frameconfig().write(|w| {
            w.set_crcmoderx(true);
            w.set_parity(true);
            w.set_sof(true);
        });

        //Setup DMA
        r.packetptr().write_value(self.rx_buf.as_mut_ptr() as u32);
        r.maxlen().write(|w| w.0 = self.rx_buf.len() as _);

        // Reset and enable the end event
        r.events_rxframeend().write_value(0);
        r.events_rxerror().write_value(0);

        // Start enablerxdata only after configs are finished writing
        compiler_fence(Ordering::SeqCst);
        r.tasks_enablerxdata().write_value(1);

        poll_fn(move |cx| {
            trace!("polling rx");
            let r = pac::NFCT;
            WAKER.register(cx.waker());

            if r.events_fieldlost().read() != 0 {
                return Poll::Ready(Err(Error::Deactivated));
            }

            if r.events_rxerror().read() != 0 {
                trace!("RXerror got in recv frame, should be back in idle state");
                r.events_rxerror().write_value(0);
                let errs = r.framestatus().rx().read();
                r.framestatus().rx().write(|w| w.0 = 0xFFFF_FFFF);
                trace!("errors: {:08x}", errs.0);
                return Poll::Ready(Err(Error::RxError));
            }

            if r.events_rxframeend().read() != 0 {
                trace!("RX Frameend got in recv frame, should have data");
                r.events_rxframeend().write_value(0);
                return Poll::Ready(Ok(()));
            }

            r.inten().write(|w| {
                w.set_rxframeend(true);
                w.set_rxerror(true);
                w.set_fieldlost(true);
            });

            Poll::Pending
        })
        .await?;

        let n = r.rxd().amount().read().rxdatabytes() as usize - 2;
        buf[..n].copy_from_slice(&self.rx_buf[..n]);
        Ok(n)
    }
}

/// Wake the system if there if an NFC field close to the antenna
pub fn wake_on_nfc_sense() {
    NFCT.tasks_sense().write_value(0x01);
}
