//
// Created by Kai Jan Kriegel on 17.10.22.
//

#include "pollingtagdetect.h"
#include "rfal_nfc.h"
#include "utils.h"


/* P2P communication data */
static uint8_t NFCID3[] = {0x01, 0xFE, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A};
static uint8_t GB[] = {0x46, 0x66, 0x6d, 0x01, 0x01, 0x11, 0x02, 0x02, 0x07, 0x80, 0x03, 0x02, 0x00, 0x03, 0x04, 0x01,
                       0x32, 0x07, 0x01, 0x03};

/* APDUs communication data */
static uint8_t ndefSelectApp[] = {0x00, 0xA4, 0x04, 0x00, 0x07, 0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00};
static uint8_t ccSelectFile[] = {0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03};
static uint8_t readBinary[] = {0x00, 0xB0, 0x00, 0x00, 0x0F};

typedef enum {
    NOT_INIT = 0,     /*!< Demo State:  Not initialized        */
    START_DISCOVERY = 1,     /*!< Demo State:  Start Discovery        */
    DISCOVERY = 2     /*!< Demo State:  Discovery              */
} TagDetectState;


static rfalNfcDiscoverParam discParam;
static TagDetectState state = NOT_INIT;

void TagDetectAPDU(void);

ReturnCode TagDetectTransceiveBlocking(uint8_t *txBuf, uint16_t txBufSize, uint8_t **rxData, uint16_t **rcvLen, uint32_t fwt);

static void TagDetectNotify(rfalNfcState st) {
    uint8_t devCnt;
    rfalNfcDevice *dev;


    if (st == RFAL_NFC_STATE_WAKEUP_MODE) {
        platformLog("Wake Up mode started \r\n");
    } else if (st == RFAL_NFC_STATE_POLL_TECHDETECT) {
        platformLog("Wake Up mode terminated. Polling for devices \r\n");
    } else if (st == RFAL_NFC_STATE_POLL_SELECT) {
        /* Multiple devices were found, activate first of them */
        rfalNfcGetDevicesFound(&dev, &devCnt);
        rfalNfcSelect(0);

        platformLog("Multiple Tags detected: %d \r\n", devCnt);
    }
}

esp_err_t TagDetectInit(void) {
    ReturnCode err = rfalNfcInitialize();
    if (err == ERR_NONE) {
        discParam.compMode = RFAL_COMPLIANCE_MODE_NFC;
        discParam.devLimit = 1U;

        ST_MEMCPY(&discParam.nfcid3, NFCID3, sizeof(NFCID3));
        ST_MEMCPY(&discParam.GB, GB, sizeof(GB));
        discParam.GBLen = sizeof(GB);

        discParam.notifyCb = TagDetectNotify;
        discParam.totalDuration = 1000U;
        discParam.wakeupEnabled = false;
        discParam.wakeupConfigDefault = true;
        discParam.techs2Find = (RFAL_NFC_POLL_TECH_A | RFAL_NFC_POLL_TECH_B);

#if defined(ST25R3911) || defined(ST25R3916)
        discParam.techs2Find   |= RFAL_NFC_POLL_TECH_AP2P;
#endif /* ST25R95 */
        state = START_DISCOVERY;
        return ESP_OK;
    }
    return err;
}


/*!
 *****************************************************************************
 * \brief Demo Cycle
 *
 *  This function executes the demo state machine.
 *  It must be called periodically
 *****************************************************************************
 */
void TagDetectCycle(void) {
    static rfalNfcDevice *nfcDevice;

    rfalNfcWorker();                                    /* Run RFAL worker periodically */

    /*******************************************************************************/
    /* Check if USER button is pressed */
//    if (platformGpioIsLow(PLATFORM_USER_BUTTON_PORT, PLATFORM_USER_BUTTON_PIN)) {
//        discParam.wakeupEnabled = !discParam.wakeupEnabled;    /* enable/disable wakeup */
//        state = START_DISCOVERY;                       /* restart loop          */
//        platformLog("Toggling Wake Up mode %s\r\n", discParam.wakeupEnabled ? "ON" : "OFF");
//
//        /* Debounce button */
//        while (platformGpioIsLow(PLATFORM_USER_BUTTON_PORT, PLATFORM_USER_BUTTON_PIN));
//    }


    switch (state) {
        /*******************************************************************************/
        case START_DISCOVERY:

//            platformLedOff(PLATFORM_LED_A_PORT, PLATFORM_LED_A_PIN);
//            platformLedOff(PLATFORM_LED_B_PORT, PLATFORM_LED_B_PIN);
//            platformLedOff(PLATFORM_LED_F_PORT, PLATFORM_LED_F_PIN);
//            platformLedOff(PLATFORM_LED_V_PORT, PLATFORM_LED_V_PIN);
//            platformLedOff(PLATFORM_LED_AP2P_PORT, PLATFORM_LED_AP2P_PIN);
//            platformLedOff(PLATFORM_LED_FIELD_PORT, PLATFORM_LED_FIELD_PIN);

            rfalNfcDeactivate(false);
            rfalNfcDiscover(&discParam);
            platformLog("Discovery started \r\n");

            state = DISCOVERY;
            break;

            /*******************************************************************************/
        case DISCOVERY:

            if (rfalNfcIsDevActivated(rfalNfcGetState())) {
                rfalNfcGetActiveDevice(&nfcDevice);
                platformLog("Device activated. \r\n");
                switch (nfcDevice->type) {
                    /*******************************************************************************/
                    case RFAL_NFC_LISTEN_TYPE_NFCA:

//                        platformLedOn(PLATFORM_LED_A_PORT, PLATFORM_LED_A_PIN);
                        switch (nfcDevice->dev.nfca.type) {
                            case RFAL_NFCA_T1T:
                                platformLog("ISO14443A/Topaz (NFC-A T1T) TAG found. UID: %s\r\n",
                                            hex2Str(nfcDevice->nfcid, nfcDevice->nfcidLen));
                                break;

                            case RFAL_NFCA_T4T:
                                platformLog("NFCA Passive ISO-DEP device found. UID: %s\r\n",
                                            hex2Str(nfcDevice->nfcid, nfcDevice->nfcidLen));

                                TagDetectAPDU();
                                break;

                            case RFAL_NFCA_T4T_NFCDEP:
                            case RFAL_NFCA_NFCDEP:
                                platformLog("NFCA Passive P2P device found. NFCID: %s\r\n",
                                            hex2Str(nfcDevice->nfcid, nfcDevice->nfcidLen));

//                                demoP2P();
                                break;

                            default:
                                platformLog("ISO14443A/NFC-A card found. UID: %s\r\n",
                                            hex2Str(nfcDevice->nfcid, nfcDevice->nfcidLen));
                                break;
                        }
                        break;

                        /*******************************************************************************/
                    case RFAL_NFC_LISTEN_TYPE_NFCB:

                        platformLog("ISO14443B/NFC-B card found. UID: %s\r\n",
                                    hex2Str(nfcDevice->nfcid, nfcDevice->nfcidLen));
//                        platformLedOn(PLATFORM_LED_B_PORT, PLATFORM_LED_B_PIN);

                        if (rfalNfcbIsIsoDepSupported(&nfcDevice->dev.nfcb)) {
                            TagDetectAPDU();
                        }
                        break;

                        /*******************************************************************************/
                    default:
                        break;
                }

                rfalNfcDeactivate(false);
                platformLog("Device deactivated. \r\n");
                platformDelay(500);
                state = START_DISCOVERY;
            }
            break;

            /*******************************************************************************/
        case NOT_INIT:
        default:
            platformLog("ERROR: Demo not initialized \r\n");
            break;
    }
}

/*!
 *****************************************************************************
 * \brief Demo APDUs Exchange
 *
 * Example how to exchange a set of predefined APDUs with PICC. The NDEF
 * application will be selected and then CC will be selected and read.
 *
 *****************************************************************************
 */
void TagDetectAPDU(void) {
    ReturnCode err;
    uint16_t *rxLen;
    uint8_t *rxData;


    /* Exchange APDU: NDEF Tag Application Select command */
    err = TagDetectTransceiveBlocking(ndefSelectApp, sizeof(ndefSelectApp), &rxData, &rxLen, RFAL_FWT_NONE);
    platformLog(" Select NDEF Application: %s Data: %s\r\n", (err != ERR_NONE) ? "FAIL" : "OK",
                hex2Str(rxData, *rxLen));

    if ((err == ERR_NONE) && rxData[0] == 0x90 && rxData[1] == 0x00) {
        /* Exchange APDU: Select Capability Container File */
        err = TagDetectTransceiveBlocking(ccSelectFile, sizeof(ccSelectFile), &rxData, &rxLen, RFAL_FWT_NONE);
        platformLog(" Select CC: %s Data: %s\r\n", (err != ERR_NONE) ? "FAIL" : "OK", hex2Str(rxData, *rxLen));

        /* Exchange APDU: Read Capability Container File  */
        err = TagDetectTransceiveBlocking(readBinary, sizeof(readBinary), &rxData, &rxLen, RFAL_FWT_NONE);
        platformLog(" Read CC: %s Data: %s\r\n", (err != ERR_NONE) ? "FAIL" : "OK", hex2Str(rxData, *rxLen));
    }
}


/*!
 *****************************************************************************
 * \brief Demo Blocking Transceive
 *
 * Helper function to send data in a blocking manner via the rfalNfc module
 *
 * \warning A protocol transceive handles long timeouts (several seconds),
 * transmission errors and retransmissions which may lead to a long period of
 * time where the MCU/CPU is blocked in this method.
 * This is a demo implementation, for a non-blocking usage example please
 * refer to the Examples available with RFAL
 *
 * \param[in]  txBuf      : data to be transmitted
 * \param[in]  txBufSize  : size of the data to be transmited
 * \param[out] rxData     : location where the received data has been placed
 * \param[out] rcvLen     : number of data bytes received
 * \param[in]  fwt        : FWT to be used (only for RF frame interface,
 *                                          otherwise use RFAL_FWT_NONE)
 *
 *
 *  \return ERR_PARAM     : Invalid parameters
 *  \return ERR_TIMEOUT   : Timeout error
 *  \return ERR_FRAMING   : Framing error detected
 *  \return ERR_PROTO     : Protocol error detected
 *  \return ERR_NONE      : No error, activation successful
 *
 *****************************************************************************
 */
ReturnCode
TagDetectTransceiveBlocking(uint8_t *txBuf, uint16_t txBufSize, uint8_t **rxData, uint16_t **rcvLen, uint32_t fwt) {
    ReturnCode err;

    err = rfalNfcDataExchangeStart(txBuf, txBufSize, rxData, rcvLen, fwt);
    if (err == ERR_NONE) {
        do {
            rfalNfcWorker();
            err = rfalNfcDataExchangeGetStatus();
        } while (err == ERR_BUSY);
    }
    return err;
}
