/*
 * Copyright (C) 2012-2014 NXP Semiconductors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <phNxpLog.h>
#include <phNxpNciHal.h>
#include <phNxpNciHal_NfcDepSWPrio.h>

/* Timeout value to wait for NFC-DEP detection.*/
#define CUSTOM_POLL_TIMEOUT 160
#define CLEAN_UP_TIMEOUT 250
#define MAX_WRITE_RETRY 5

/******************* Global variables *****************************************/
extern phNxpNciHal_Control_t nxpncihal_ctrl;
extern NFCSTATUS phNxpNciHal_send_ext_cmd(uint16_t cmd_len, uint8_t* p_cmd);
static uint8_t cmd_stop_rf_discovery[] = {0x21, 0x06, 0x01, 0x00}; /* IDLE */
static uint8_t cmd_resume_rf_discovery[] = {0x21, 0x06, 0x01,
                                            0x03}; /* RF_DISCOVER */

/*RF_DISCOVER_SELECT_CMD*/
static uint8_t cmd_select_rf_discovery[] = {0x21, 0x04, 0x03, 0x01, 0x04, 0x02};

static uint8_t cmd_poll[64];
static uint8_t cmd_poll_len = 0;
int discover_type = 0xFF;
uint32_t cleanup_timer;

/*PRIO LOGIC related dead functions undefined*/
#ifdef P2P_PRIO_LOGIC_HAL_IMP

static int iso_dep_detected = 0x00;
static int poll_timer_fired = 0x00;
static uint8_t bIgnorep2plogic = 0;
static uint8_t* p_iso_ntf_buff = NULL; /* buffer to store second notification */
static uint8_t bIgnoreIsoDep = 0;
static uint32_t custom_poll_timer;

/************** NFC-DEP SW PRIO functions *************************************/

static NFCSTATUS phNxpNciHal_start_polling_loop(void);
static NFCSTATUS phNxpNciHal_stop_polling_loop(void);
static NFCSTATUS phNxpNciHal_resume_polling_loop(void);
static void phNxpNciHal_NfcDep_store_ntf(uint8_t* p_cmd_data, uint16_t cmd_len);

/*******************************************************************************
**
** Function         cleanup_timer_handler
**
** Description      Callback function for cleanup timer.
**
** Returns          None
**
*******************************************************************************/
static void cleanup_timer_handler(uint32_t timerId, void* pContext) {
  NXPLOG_NCIHAL_D(">> cleanup_timer_handler.");

  NXPLOG_NCIHAL_D(
      ">> cleanup_timer_handler. ISO_DEP not detected second time.");

  phOsalNfc_Timer_Delete(cleanup_timer);
  cleanup_timer = 0;
  iso_dep_detected = 0x00;
  EnableP2P_PrioLogic = false;
  return;
}

/*******************************************************************************
**
** Function         custom_poll_timer_handler
**
** Description      Callback function for custom poll timer.
**
** Returns          None
**
*******************************************************************************/
static void custom_poll_timer_handler(uint32_t timerId, void* pContext) {
  NXPLOG_NCIHAL_D(">> custom_poll_timer_handler.");

  NXPLOG_NCIHAL_D(
      ">> custom_poll_timer_handler. NFC_DEP not detected. so giving early "
      "chance to ISO_DEP.");

  phOsalNfc_Timer_Delete(custom_poll_timer);

  if (iso_dep_detected == 0x01) {
    poll_timer_fired = 0x01;

    /*
     * Restart polling loop.
     * When the polling loop is stopped, polling will be restarted.
     */
    NXPLOG_NCIHAL_D(">> custom_poll_timer_handler - restart polling loop.");

    phNxpNciHal_stop_polling_loop();
  } else {
    NXPLOG_NCIHAL_E(
        ">> custom_poll_timer_handler - invalid flag state (iso_dep_detected)");
  }

  return;
}
/*******************************************************************************
**
** Function         phNxpNciHal_stop_polling_loop
**
** Description      Sends stop polling cmd to NFCC
**
** Returns          NFCSTATUS_SUCCESS if successful,otherwise NFCSTATUS_FAILED
**
*******************************************************************************/
static NFCSTATUS phNxpNciHal_stop_polling_loop() {
  NFCSTATUS status = NFCSTATUS_SUCCESS;
  phNxpNciHal_Sem_t cb_data;
  pthread_t pthread;
  discover_type = STOP_POLLING;

  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  if (pthread_create(&pthread, &attr, tmp_thread, (void*)&discover_type) != 0) {
    NXPLOG_NCIHAL_E("fail to create pthread");
  }
  pthread_attr_destroy(&attr);
  return status;
}

/*******************************************************************************
**
** Function         phNxpNciHal_resume_polling_loop
**
** Description      Sends resume polling cmd to NFCC
**
** Returns          NFCSTATUS_SUCCESS if successful,otherwise NFCSTATUS_FAILED
**
*******************************************************************************/
static NFCSTATUS phNxpNciHal_resume_polling_loop() {
  NFCSTATUS status = NFCSTATUS_SUCCESS;
  phNxpNciHal_Sem_t cb_data;
  pthread_t pthread;
  discover_type = RESUME_POLLING;

  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  if (pthread_create(&pthread, &attr, tmp_thread, (void*)&discover_type) != 0) {
    NXPLOG_NCIHAL_E("fail to create pthread");
  }
  pthread_attr_destroy(&attr);
  return status;
}

/*******************************************************************************
**
** Function         phNxpNciHal_start_polling_loop
**
** Description      Sends start polling cmd to NFCC
**
** Returns          NFCSTATUS_SUCCESS if successful,otherwise NFCSTATUS_FAILED
**
*******************************************************************************/
NFCSTATUS phNxpNciHal_start_polling_loop() {
  NFCSTATUS status = NFCSTATUS_FAILED;
  phNxpNciHal_Sem_t cb_data;
  pthread_t pthread;
  discover_type = START_POLLING;

  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  if (pthread_create(&pthread, &attr, tmp_thread, (void*)&discover_type) != 0) {
    NXPLOG_NCIHAL_E("fail to create pthread");
  }
  pthread_attr_destroy(&attr);
  return status;
}

/*******************************************************************************
**
** Function         phNxpNciHal_NfcDep_rsp_ext
**
** Description      Implements algorithm for NFC-DEP protocol priority over
**                  ISO-DEP protocol.
**                  Following the algorithm:
**                  IF ISO-DEP detected first time,set the ISO-DEP detected flag
**                  and resume polling loop with 60ms timeout value.
**                      a) if than NFC-DEP detected than send the response to
**                         libnfc-nci stack and stop the timer.
**                      b) if NFC-DEP not detected with in 60ms, than restart
**                         the polling loop to give early chance to ISO-DEP with
**                         a cleanup timer.
**                      c) if ISO-DEP detected second time send the response to
**                         libnfc-nci stack and stop the cleanup timer.
**                      d) if ISO-DEP not detected with in cleanup timeout, than
**                         clear the ISO-DEP detection flag.
**
** Returns          NFCSTATUS_SUCCESS if successful,otherwise NFCSTATUS_FAILED
**
*******************************************************************************/
NFCSTATUS phNxpNciHal_NfcDep_rsp_ext(uint8_t* p_ntf, uint16_t* p_len) {
  NFCSTATUS status = NFCSTATUS_INVALID_PARAMETER;

  NXPLOG_NCIHAL_D(">> p_ntf[0]=%02x , p_ntf[1]=%02x", p_ntf[0], p_ntf[1]);

  if (p_ntf[0] == 0x41 && p_ntf[1] == 0x04) {
    // Tag selected, Disable P2P Prio logic.
    bIgnoreIsoDep = 1;
    NXPLOG_NCIHAL_D(">> Tag selected, Disable P2P Prio logic.");

  } else if (((p_ntf[0] == 0x61 && p_ntf[1] == 0x06) ||
              (p_ntf[0] == 0x41 && p_ntf[1] == 0x06)) &&
             bIgnoreIsoDep == 1) {
    // Tag deselected, enable P2P Prio logic.
    bIgnoreIsoDep = 0x00;
    NXPLOG_NCIHAL_D(">> Tag deselected, enable P2P Prio logic.");
  }
  if (bIgnoreIsoDep == 0x00 && p_ntf[0] == 0x61 && p_ntf[1] == 0x05 &&
      *p_len > 5) {
    if (p_ntf[5] == 0x04 && p_ntf[6] < 0x80) {
      NXPLOG_NCIHAL_D(">> ISO DEP detected.");

      if (iso_dep_detected == 0x00) {
        NXPLOG_NCIHAL_D(">> ISO DEP detected first time. Resume polling loop");

        iso_dep_detected = 0x01;
        status = phNxpNciHal_resume_polling_loop();

        custom_poll_timer = phOsalNfc_Timer_Create();
        NXPLOG_NCIHAL_D("custom poll timer started - %d", custom_poll_timer);

        status = phOsalNfc_Timer_Start(custom_poll_timer, CUSTOM_POLL_TIMEOUT,
                                       &custom_poll_timer_handler, NULL);

        if (NFCSTATUS_SUCCESS == status) {
          NXPLOG_NCIHAL_D("custom poll timer started");
        } else {
          NXPLOG_NCIHAL_E("custom poll timer not started!!!");
          status = NFCSTATUS_FAILED;
        }

        status = NFCSTATUS_FAILED;
      } else {
        NXPLOG_NCIHAL_D(">> ISO DEP detected second time.");
        /* Store notification */
        phNxpNciHal_NfcDep_store_ntf(p_ntf, *p_len);

        /* Stop Cleanup_timer */
        phOsalNfc_Timer_Stop(cleanup_timer);
        phOsalNfc_Timer_Delete(cleanup_timer);
        cleanup_timer = 0;
        EnableP2P_PrioLogic = false;
        iso_dep_detected = 0;
        status = NFCSTATUS_SUCCESS;
      }
    } else if (p_ntf[5] == 0x05) {
      NXPLOG_NCIHAL_D(">> NFC-DEP Detected - stopping the custom poll timer");

      phOsalNfc_Timer_Stop(custom_poll_timer);
      phOsalNfc_Timer_Delete(custom_poll_timer);
      EnableP2P_PrioLogic = false;
      iso_dep_detected = 0;
      status = NFCSTATUS_SUCCESS;
    } else {
      NXPLOG_NCIHAL_D(
          ">>  detected other technology- stopping the custom poll timer");
      phOsalNfc_Timer_Stop(custom_poll_timer);
      phOsalNfc_Timer_Delete(custom_poll_timer);
      EnableP2P_PrioLogic = false;
      iso_dep_detected = 0;
      status = NFCSTATUS_INVALID_PARAMETER;
    }
  } else if (bIgnoreIsoDep == 0x00 &&
             ((p_ntf[0] == 0x41 && p_ntf[1] == 0x06) ||
              (p_ntf[0] == 0x61 && p_ntf[1] == 0x06))) {
    NXPLOG_NCIHAL_D(">> RF disabled");
    if (poll_timer_fired == 0x01) {
      poll_timer_fired = 0x00;

      NXPLOG_NCIHAL_D(">>restarting polling loop.");

      /* start polling loop */
      phNxpNciHal_start_polling_loop();
      EnableP2P_PrioLogic = false;
      NXPLOG_NCIHAL_D(
          ">> NFC DEP NOT  detected - custom poll timer expired - RF disabled");

      cleanup_timer = phOsalNfc_Timer_Create();

      /* Start cleanup_timer */
      NFCSTATUS status = phOsalNfc_Timer_Start(cleanup_timer, CLEAN_UP_TIMEOUT,
                                               &cleanup_timer_handler, NULL);

      if (NFCSTATUS_SUCCESS == status) {
        NXPLOG_NCIHAL_D("cleanup timer started");
      } else {
        NXPLOG_NCIHAL_E("cleanup timer not started!!!");
        status = NFCSTATUS_FAILED;
      }

      status = NFCSTATUS_FAILED;
    } else {
      status = NFCSTATUS_SUCCESS;
    }
  }
  if (bIgnoreIsoDep == 0x00 && iso_dep_detected == 1) {
    if ((p_ntf[0] == 0x41 && p_ntf[1] == 0x06) ||
        (p_ntf[0] == 0x61 && p_ntf[1] == 0x06)) {
      NXPLOG_NCIHAL_D(">>iso_dep_detected Disconnect related notification");
      status = NFCSTATUS_FAILED;
    } else {
      NXPLOG_NCIHAL_W("Never come here");
    }
  }

  return status;
}
/*******************************************************************************
**
** Function         phNxpNciHal_NfcDep_store_ntf
**
** Description      Stores the iso dep notification locally.
**
** Returns          None
**
*******************************************************************************/
static void phNxpNciHal_NfcDep_store_ntf(uint8_t* p_cmd_data,
                                         uint16_t cmd_len) {
  p_iso_ntf_buff = NULL;

  p_iso_ntf_buff = malloc(sizeof(uint8_t) * cmd_len);
  if (p_iso_ntf_buff == NULL) {
    NXPLOG_NCIHAL_E("Error allocating memory (p_iso_ntf_buff)");
    return;
  }
  memcpy(p_iso_ntf_buff, p_cmd_data, cmd_len);
  bIgnorep2plogic = 1;
}

/*******************************************************************************
**
** Function         phNxpNciHal_NfcDep_comapre_ntf
**
** Description      Compare the notification with previous iso dep notification.
**
** Returns          NFCSTATUS_SUCCESS if successful,otherwise NFCSTATUS_FAILED
**
*******************************************************************************/
NFCSTATUS phNxpNciHal_NfcDep_comapre_ntf(uint8_t* p_cmd_data,
                                         uint16_t cmd_len) {
  NFCSTATUS status = NFCSTATUS_FAILED;
  int32_t ret_val = -1;

  if (bIgnorep2plogic == 1) {
    ret_val = memcmp(p_cmd_data, p_iso_ntf_buff, cmd_len);
    if (ret_val != 0) {
      NXPLOG_NCIHAL_E("Third notification is not equal to last");
    } else {
      NXPLOG_NCIHAL_E(
          "Third notification is equal to last (disable p2p logic)");
      status = NFCSTATUS_SUCCESS;
    }
    bIgnorep2plogic = 0;
  }
  if (p_iso_ntf_buff != NULL) {
    free(p_iso_ntf_buff);
    p_iso_ntf_buff = NULL;
  }

  return status;
}

extern NFCSTATUS phNxpNciHal_clean_P2P_Prio() {
  NFCSTATUS status = NFCSTATUS_SUCCESS;

  iso_dep_detected = 0x00;
  EnableP2P_PrioLogic = false;
  poll_timer_fired = 0x00;
  bIgnorep2plogic = 0x00;
  bIgnoreIsoDep = 0x00;

  status = phOsalNfc_Timer_Stop(cleanup_timer);
  status |= phOsalNfc_Timer_Delete(cleanup_timer);

  status |= phOsalNfc_Timer_Stop(custom_poll_timer);
  status |= phOsalNfc_Timer_Delete(custom_poll_timer);
  cleanup_timer = 0;
  return status;
}

#endif
/*******************************************************************************
**
** Function         hal_write_cb
**
** Description      Callback function for hal write.
**
** Returns          None
**
*******************************************************************************/
static void hal_write_cb(void* pContext, phTmlNfc_TransactInfo_t* pInfo) {
  phNxpNciHal_Sem_t* p_cb_data = (phNxpNciHal_Sem_t*)pContext;

  if (pInfo->wStatus == NFCSTATUS_SUCCESS) {
    NXPLOG_NCIHAL_D("hal_write_cb: write successful status = 0x%x",
                    pInfo->wStatus);
  } else {
    NXPLOG_NCIHAL_E("hal_write_cb: write error status = 0x%x", pInfo->wStatus);
  }

  p_cb_data->status = pInfo->wStatus;

  SEM_POST(p_cb_data);
  return;
}

/*******************************************************************************
 **
 ** Function         tmp_thread
 **
 ** Description      Thread to execute custom poll commands .
 **
 ** Returns          None
 **
 *******************************************************************************/
void* tmp_thread(void* tmp) {
  NFCSTATUS status = NFCSTATUS_SUCCESS;
  uint16_t data_len;
  NXPLOG_NCIHAL_E("tmp_thread: enter type=0x0%x", *((int*)tmp));
  usleep(10 * 1000);

  switch (*((int*)tmp)) {
    case START_POLLING: {
      CONCURRENCY_LOCK();
      data_len = phNxpNciHal_write_unlocked(cmd_poll_len, cmd_poll);
      CONCURRENCY_UNLOCK();

      if (data_len != cmd_poll_len) {
        NXPLOG_NCIHAL_E("phNxpNciHal_start_polling_loop: data len mismatch");
        status = NFCSTATUS_FAILED;
      }
    } break;

    case RESUME_POLLING: {
      CONCURRENCY_LOCK();
      data_len = phNxpNciHal_write_unlocked(sizeof(cmd_resume_rf_discovery),
                                            cmd_resume_rf_discovery);
      CONCURRENCY_UNLOCK();

      if (data_len != sizeof(cmd_resume_rf_discovery)) {
        NXPLOG_NCIHAL_E("phNxpNciHal_resume_polling_loop: data len mismatch");
        status = NFCSTATUS_FAILED;
      }
    } break;

    case STOP_POLLING: {
      CONCURRENCY_LOCK();
      data_len = phNxpNciHal_write_unlocked(sizeof(cmd_stop_rf_discovery),
                                            cmd_stop_rf_discovery);
      CONCURRENCY_UNLOCK();

      if (data_len != sizeof(cmd_stop_rf_discovery)) {
        NXPLOG_NCIHAL_E("phNxpNciHal_stop_polling_loop: data len mismatch");
        status = NFCSTATUS_FAILED;
      }
    } break;

    case DISCOVER_SELECT: {
      CONCURRENCY_LOCK();
      data_len = phNxpNciHal_write_unlocked(sizeof(cmd_select_rf_discovery),
                                            cmd_select_rf_discovery);
      CONCURRENCY_UNLOCK();

      if (data_len != sizeof(cmd_resume_rf_discovery)) {
        NXPLOG_NCIHAL_E("phNxpNciHal_resume_polling_loop: data len mismatch");
        status = NFCSTATUS_FAILED;
      }
    } break;

    default:
      NXPLOG_NCIHAL_E("No Matching case");
      status = NFCSTATUS_FAILED;
      break;
  }

  NXPLOG_NCIHAL_E("tmp_thread: exit");
  return NULL;
}
/*******************************************************************************
 **
 ** Function         phNxpNciHal_select_RF_Discovery
 **
 ** Description     Sends RF_DISCOVER_SELECT_CMD
 ** Parameters    RfID ,  RfProtocolType
 ** Returns          NFCSTATUS_PENDING if success
 **
 *******************************************************************************/
NFCSTATUS phNxpNciHal_select_RF_Discovery(unsigned int RfID,
                                          unsigned int RfProtocolType) {
  NFCSTATUS status = NFCSTATUS_SUCCESS;
  phNxpNciHal_Sem_t cb_data;
  pthread_t pthread;
  discover_type = DISCOVER_SELECT;
  cmd_select_rf_discovery[3] = RfID;
  cmd_select_rf_discovery[4] = RfProtocolType;

  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  if (pthread_create(&pthread, &attr, tmp_thread, (void*)&discover_type) != 0) {
    NXPLOG_NCIHAL_E("fail to create pthread");
  }
  pthread_attr_destroy(&attr);
  return status;
}
/*******************************************************************************
**
** Function         phNxpNciHal_NfcDep_cmd_ext
**
** Description      Stores the polling loop configuration locally.
**
** Returns          None
**
*******************************************************************************/
void phNxpNciHal_NfcDep_cmd_ext(uint8_t* p_cmd_data, uint16_t* cmd_len) {
  if (p_cmd_data[0] == 0x21 && p_cmd_data[1] == 0x03) {
    if (*cmd_len == 6 && p_cmd_data[3] == 0x01 && p_cmd_data[4] == 0x02 &&
        p_cmd_data[5] == 0x01) {
      /* DO NOTHING */
    } else {
      /* Store the polling loop configuration */
      cmd_poll_len = *cmd_len;
      memset(&cmd_poll, 0, cmd_poll_len);
      memcpy(&cmd_poll, p_cmd_data, cmd_poll_len);
    }
  }

  return;
}