/*
 * Copyright 2012-2020, 2023 NXP
 *
 * 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.
 */

#ifndef _PHNXPUCIHAL_UTILS_H_
#define _PHNXPUCIHAL_UTILS_H_

#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <unordered_set>

#include <assert.h>
#include <bit>
#include <cstring>
#include <map>
#include <memory>
#include <thread>
#include <type_traits>
#include <vector>


#include "phNxpLog.h"
#include "phUwbStatus.h"

/********************* Definitions and structures *****************************/
/* Which is the direction of UWB Packet.
 *
 * Used by the @ref phNxpUciHal_print_packet API.
 */
enum phNxpUciHal_Pkt_Type {
  NXP_TML_UCI_CMD_AP_2_UWBS,
  NXP_TML_UCI_RSP_NTF_UWBS_2_AP,
  NXP_TML_FW_DNLD_CMD_AP_2_UWBS,
  NXP_TML_FW_DNLD_RSP_UWBS_2_AP,
};


/* Semaphore handling structure */
typedef struct phNxpUciHal_Sem {
  /* Semaphore used to wait for callback */
  sem_t sem;

  /* Used to store the status sent by the callback */
  tHAL_UWB_STATUS status;

  /* Used to provide a local context to the callback */
  void* pContext;

} phNxpUciHal_Sem_t;

/* Semaphore helper macros */
static inline int SEM_WAIT(phNxpUciHal_Sem_t* pCallbackData)
{
  return sem_wait(&pCallbackData->sem);
}

static inline int SEM_POST(phNxpUciHal_Sem_t* pCallbackData)
{
  return sem_post(&pCallbackData->sem);
}

/************************ Exposed functions ***********************************/
/* NXP UCI HAL utility functions */
bool phNxpUciHal_init_monitor(void);
void phNxpUciHal_cleanup_monitor(void);

tHAL_UWB_STATUS phNxpUciHal_init_cb_data(phNxpUciHal_Sem_t* pCallbackData,
                                         void* pContext);

int phNxpUciHal_sem_timed_wait_msec(phNxpUciHal_Sem_t* pCallbackData, long msec);

static inline int phNxpUciHal_sem_timed_wait_sec(phNxpUciHal_Sem_t* pCallbackData, time_t sec)
{
  return phNxpUciHal_sem_timed_wait_msec(pCallbackData, sec * 1000L);
}

static inline int phNxpUciHal_sem_timed_wait(phNxpUciHal_Sem_t* pCallbackData)
{
  /* default 1 second timeout*/
  return phNxpUciHal_sem_timed_wait_msec(pCallbackData, 1000L);
}

void phNxpUciHal_cleanup_cb_data(phNxpUciHal_Sem_t* pCallbackData);

// helper class for Semaphore
// phNxpUciHal_init_cb_data(), phNxpUciHal_cleanup_cb_data(),
// SEM_WAIT(), SEM_POST()
class UciHalSemaphore {
public:
  UciHalSemaphore() {
    phNxpUciHal_init_cb_data(&sem, NULL);
  }
  UciHalSemaphore(void *context) {
    phNxpUciHal_init_cb_data(&sem, context);
  }
  virtual ~UciHalSemaphore() {
    phNxpUciHal_cleanup_cb_data(&sem);
  }
  int wait() {
    return sem_wait(&sem.sem);
  }
  int wait_timeout_msec(long msec) {
    return phNxpUciHal_sem_timed_wait_msec(&sem, msec);
  }
  int post() {
    return sem_post(&sem.sem);
  }
  int post(tHAL_UWB_STATUS status) {
    sem.status = status;
    return sem_post(&sem.sem);
  }
  tHAL_UWB_STATUS getStatus() {
    return sem.status;
  }
private:
  phNxpUciHal_Sem_t sem;
};

/*
 * Print an UWB Packet.
 *
 * @param what The type and direction of packet
 *
 * @param p_data The packet to be printed/logged.
 *
 * @param len Tenth of the packet.
 *
 */

void phNxpUciHal_print_packet(enum phNxpUciHal_Pkt_Type what, const uint8_t* p_data,
                              uint16_t len);
void phNxpUciHal_emergency_recovery(void);
double phNxpUciHal_byteArrayToDouble(const uint8_t* p_data);

template <typename T>
static inline T le_bytes_to_cpu(const uint8_t *p)
{
  static_assert(std::is_integral_v<T>, "bytes_to_cpu must be used with an integral type");
  T val = 0;
  if (std::endian::native == std::endian::little) {
    std::memcpy(&val, p, sizeof(T));
  } else {
    size_t i = sizeof(T);
    while (i--) {
      val = (val << 8) | p[i];
    }
  }
  return val;
}

template <typename T>
static inline void cpu_to_le_bytes(uint8_t *p, const T num)
{
  static_assert(std::is_integral_v<T>, "cpu_to_le_bytes must be used with an integral type");
  T val = num;
  if (std::endian::native == std::endian::little) {
    std::memcpy(p, &val, sizeof(T));
  } else {
    for (size_t i = 0; i < sizeof(T); i++) {
      p[i] = val & 0xff;
      val = val >> 8;
    }
  }
}

/* Lock unlock helper macros */
void REENTRANCE_LOCK();
void REENTRANCE_UNLOCK();
void CONCURRENCY_LOCK();
void CONCURRENCY_UNLOCK();

// Decode bytes into map<key=T, val=LV>
std::map<uint16_t, std::vector<uint8_t>>
decodeTlvBytes(const std::vector<uint8_t> &ext_ids, const uint8_t *tlv_bytes, size_t tlv_len);

// Encode map<key=T, val=LV> into TLV bytes
std::vector<uint8_t> encodeTlvBytes(const std::map<uint16_t, std::vector<uint8_t>> &tlvs);

#endif /* _PHNXPUCIHAL_UTILS_H_ */
