/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#include "app_ble_cmd_handler.h"
#include "app_ble_custom_cmd.h"
#include "apps.h"
#include "bluetooth.h"
#include "cmsis_os.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "rwapp_config.h"
#include "stdbool.h"
#include "string.h"

#ifdef BTIF_BLE_APP_DATAPATH_SERVER

#define BLE_CUSTOM_CMD_WAITING_RSP_TIMEOUT_COUNT 8

#define BLE_RAW_DATA_XFER_BUF_SIZE 80
/**
 * @brief waiting response timeout supervision data structure
 *
 */
typedef struct {
  uint16_t entryIndex;    /**< The command waiting for the response */
  uint16_t msTillTimeout; /**< run-time timeout left milliseconds */
} BLE_CUSTOM_CMD_WAITING_RSP_SUPERVISOR_T;

/**
 * @brief Custom command handling environment
 *
 */
typedef struct {
  uint8_t isInRawDataXferStage; /**< true if the received data is raw data,
                                                                false if the
                                   received data is in the format of
                                   BLE_CUSTOM_CMD_PAYLOAD_T*/
  uint16_t lengthOfRawDataXferToReceive;
  uint16_t lengthOfReceivedRawDataXfer;
  uint8_t *ptrRawXferDstBuf;
  BLE_RawDataReceived_Handler_t rawDataHandler;
  BLE_CUSTOM_CMD_WAITING_RSP_SUPERVISOR_T
  waitingRspTimeoutInstance[BLE_CUSTOM_CMD_WAITING_RSP_TIMEOUT_COUNT];
  uint32_t lastSysTicks;
  uint8_t timeoutSupervisorCount;
  osTimerId supervisor_timer_id;
  osMutexId mutex;

} BLE_CUSTOM_CMD_ENV_T;

static uint8_t rawDataXferBuf[BLE_RAW_DATA_XFER_BUF_SIZE];
static BLE_CUSTOM_CMD_ENV_T ble_custom_cmd_env;

osMutexDef(app_ble_cmd_mutex);

static void ble_custom_cmd_rsp_supervision_timer_cb(void const *n);
osTimerDef(APP_CUSTOM_CMD_RSP_SUPERVISION_TIMER,
           ble_custom_cmd_rsp_supervision_timer_cb);

static void BLE_remove_waiting_rsp_timeout_supervision(uint16_t entryIndex);

extern void app_datapath_server_send_data_via_notification(uint8_t *ptrData,
                                                           uint32_t length);
extern void app_datapath_server_send_data_via_indication(uint8_t *ptrData,
                                                         uint32_t length);
extern void app_datapath_server_send_data_via_write_command(uint8_t *ptrData,
                                                            uint32_t length);
extern void app_datapath_server_send_data_via_write_request(uint8_t *ptrData,
                                                            uint32_t length);

/**
 * @brief Callback function of the waiting response supervisor timer.
 *
 */
static void ble_custom_cmd_rsp_supervision_timer_cb(void const *n) {
  uint32_t entryIndex =
      ble_custom_cmd_env.waitingRspTimeoutInstance[0].entryIndex;

  BLE_remove_waiting_rsp_timeout_supervision(entryIndex);

  // it means time-out happens before the response is received from the peer
  // device, trigger the response handler
  CUSTOM_COMMAND_PTR_FROM_ENTRY_INDEX(entryIndex)
      ->cmdRspHandler(TIMEOUT_WAITING_RESPONSE, NULL, 0);
}

void BLE_get_response_handler(uint32_t funcCode, uint8_t *ptrParam,
                              uint32_t paramLen) {
  // parameter length check
  if (paramLen > sizeof(BLE_CUSTOM_CMD_RSP_T)) {
    return;
  }

  if (0 == ble_custom_cmd_env.timeoutSupervisorCount) {
    return;
  }

  BLE_CUSTOM_CMD_RSP_T *rsp = (BLE_CUSTOM_CMD_RSP_T *)ptrParam;

  BLE_CUSTOM_CMD_INSTANCE_T *ptCmdInstance =
      BLE_custom_command_get_entry_pointer_from_cmd_code(rsp->cmdCodeToRsp);
  if (NULL == ptCmdInstance) {
    return;
  }

  // remove the function code from the time-out supervision chain
  BLE_remove_waiting_rsp_timeout_supervision(rsp->cmdCodeToRsp);

  // call the response handler
  if (ptCmdInstance->cmdRspHandler) {
    ptCmdInstance->cmdRspHandler(rsp->cmdRetStatus, rsp->rspData,
                                 rsp->rspDataLen);
  }
}

void BLE_control_raw_data_xfer(bool isStartXfer) {
  ble_custom_cmd_env.isInRawDataXferStage = isStartXfer;

  if (true == isStartXfer) {
    ble_custom_cmd_env.lengthOfReceivedRawDataXfer = 0;
    // default configuration, can be customized by BLE_config_raw_data_xfer
    ble_custom_cmd_env.lengthOfRawDataXferToReceive = sizeof(rawDataXferBuf);
    ble_custom_cmd_env.ptrRawXferDstBuf = (uint8_t *)&rawDataXferBuf;
    ble_custom_cmd_env.rawDataHandler = NULL;
  }
}

void BLE_set_raw_data_xfer_received_callback(
    BLE_RawDataReceived_Handler_t callback) {
  ble_custom_cmd_env.rawDataHandler = callback;
}

void BLE_raw_data_xfer_control_handler(uint32_t funcCode, uint8_t *ptrParam,
                                       uint32_t paramLen) {
  bool isStartXfer = false;

  if (OP_START_RAW_DATA_XFER == funcCode) {
    isStartXfer = true;
  }

  BLE_control_raw_data_xfer(isStartXfer);

  BLE_send_response_to_command(funcCode, NO_ERROR, NULL, 0,
                               TRANSMISSION_VIA_NOTIFICATION);
}

void BLE_start_raw_data_xfer_control_rsp_handler(
    BLE_CUSTOM_CMD_RET_STATUS_E retStatus, uint8_t *ptrParam,
    uint32_t paramLen) {
  if (NO_ERROR == retStatus) {
    /**< now the sending data api won't wrap BLE_CUSTOM_CMD_PAYLOAD_T but will
     * directly send the raw data */
    ble_custom_cmd_env.isInRawDataXferStage = true;
  }
}

void BLE_stop_raw_data_xfer_control_rsp_handler(
    BLE_CUSTOM_CMD_RET_STATUS_E retStatus, uint8_t *ptrParam,
    uint32_t paramLen) {
  if (NO_ERROR == retStatus) {
    ble_custom_cmd_env.isInRawDataXferStage = false;
  }
}

/**
 * @brief Refresh the waiting response supervisor list
 *
 */
static void ble_custom_cmd_refresh_supervisor_env(void) {
  // do nothing if no supervisor was added
  if (ble_custom_cmd_env.timeoutSupervisorCount > 0) {
    uint32_t currentTicks = GET_CURRENT_TICKS();
    uint32_t passedTicks;
    if (currentTicks >= ble_custom_cmd_env.lastSysTicks) {
      passedTicks = (currentTicks - ble_custom_cmd_env.lastSysTicks);
    } else {
      passedTicks =
          (hal_sys_timer_get_max() - ble_custom_cmd_env.lastSysTicks + 1) +
          currentTicks;
    }

    uint32_t deltaMs = TICKS_TO_MS(passedTicks);

    BLE_CUSTOM_CMD_WAITING_RSP_SUPERVISOR_T *pRspSupervisor =
        &(ble_custom_cmd_env.waitingRspTimeoutInstance[0]);
    for (uint32_t index = 0; index < ble_custom_cmd_env.timeoutSupervisorCount;
         index++) {
      ASSERT(pRspSupervisor[index].msTillTimeout > deltaMs,
             "the waiting command response supervisor timer is missing!!!, \
				%d ms passed but the ms to trigger is %d",
             deltaMs, pRspSupervisor[index].msTillTimeout);
      pRspSupervisor[index].msTillTimeout -= deltaMs;
    }
  }

  ble_custom_cmd_env.lastSysTicks = GET_CURRENT_TICKS();
}

/**
 * @brief Remove the time-out supervision of waiting response
 *
 * @param entryIndex 	Entry index of the command table
 *
 */
static void BLE_remove_waiting_rsp_timeout_supervision(uint16_t entryIndex) {
  ASSERT(ble_custom_cmd_env.timeoutSupervisorCount > 0,
         "%s The BLE custom command time-out supervisor is already empty!!!",
         __FUNCTION__);

  osMutexWait(ble_custom_cmd_env.mutex, osWaitForever);

  uint32_t index;
  for (index = 0; index < ble_custom_cmd_env.timeoutSupervisorCount; index++) {
    if (ble_custom_cmd_env.waitingRspTimeoutInstance[index].entryIndex ==
        entryIndex) {
      memcpy(&(ble_custom_cmd_env.waitingRspTimeoutInstance[index]),
             &(ble_custom_cmd_env.waitingRspTimeoutInstance[index + 1]),
             (ble_custom_cmd_env.timeoutSupervisorCount - index - 1) *
                 sizeof(BLE_CUSTOM_CMD_WAITING_RSP_SUPERVISOR_T));
      break;
    }
  }

  // cannot find it, directly return
  if (index == ble_custom_cmd_env.timeoutSupervisorCount) {
    goto exit;
  }

  ble_custom_cmd_env.timeoutSupervisorCount--;

  if (ble_custom_cmd_env.timeoutSupervisorCount > 0) {
    // refresh supervisor environment firstly
    ble_custom_cmd_refresh_supervisor_env();

    // start timer, the first entry is the most close one
    osTimerStart(ble_custom_cmd_env.supervisor_timer_id,
                 ble_custom_cmd_env.waitingRspTimeoutInstance[0].msTillTimeout);
  } else {
    // no supervisor, directly stop the timer
    osTimerStop(ble_custom_cmd_env.supervisor_timer_id);
  }

exit:
  osMutexRelease(ble_custom_cmd_env.mutex);
}

/**
 * @brief Add the time-out supervision of waiting response
 *
 * @param entryIndex 	Index of the command entry
 *
 */
static void BLE_add_waiting_rsp_timeout_supervision(uint16_t entryIndex) {
  ASSERT(ble_custom_cmd_env.timeoutSupervisorCount <
             BLE_CUSTOM_CMD_WAITING_RSP_TIMEOUT_COUNT,
         "%s The BLE custom command time-out supervisor is full!!!",
         __FUNCTION__);

  osMutexWait(ble_custom_cmd_env.mutex, osWaitForever);

  // refresh supervisor environment firstly
  ble_custom_cmd_refresh_supervisor_env();

  BLE_CUSTOM_CMD_INSTANCE_T *pInstance =
      CUSTOM_COMMAND_PTR_FROM_ENTRY_INDEX(entryIndex);

  BLE_CUSTOM_CMD_WAITING_RSP_SUPERVISOR_T
  waitingRspTimeoutInstance[BLE_CUSTOM_CMD_WAITING_RSP_TIMEOUT_COUNT];

  uint32_t index = 0, insertedIndex = 0;
  for (index = 0; index < ble_custom_cmd_env.timeoutSupervisorCount; index++) {
    uint32_t msTillTimeout =
        ble_custom_cmd_env.waitingRspTimeoutInstance[index].msTillTimeout;

    // in the order of low to high
    if ((ble_custom_cmd_env.waitingRspTimeoutInstance[index].entryIndex !=
         entryIndex) &&
        (pInstance->timeoutWaitingRspInMs >= msTillTimeout)) {
      waitingRspTimeoutInstance[insertedIndex++] =
          ble_custom_cmd_env.waitingRspTimeoutInstance[index];
    } else if (pInstance->timeoutWaitingRspInMs < msTillTimeout) {
      waitingRspTimeoutInstance[insertedIndex].entryIndex = entryIndex;
      waitingRspTimeoutInstance[insertedIndex].msTillTimeout =
          pInstance->timeoutWaitingRspInMs;

      insertedIndex++;
    }
  }

  // biggest one? then put it at the end of the list
  if (ble_custom_cmd_env.timeoutSupervisorCount == index) {
    waitingRspTimeoutInstance[insertedIndex].entryIndex = entryIndex;
    waitingRspTimeoutInstance[insertedIndex].msTillTimeout =
        pInstance->timeoutWaitingRspInMs;

    insertedIndex++;
  }

  // copy to the global variable
  memcpy((uint8_t *)&(ble_custom_cmd_env.waitingRspTimeoutInstance),
         (uint8_t *)&waitingRspTimeoutInstance,
         insertedIndex * sizeof(BLE_CUSTOM_CMD_WAITING_RSP_SUPERVISOR_T));

  ble_custom_cmd_env.timeoutSupervisorCount = insertedIndex;

  // start timer, the first entry is the most close one
  osTimerStart(ble_custom_cmd_env.supervisor_timer_id,
               ble_custom_cmd_env.waitingRspTimeoutInstance[0].msTillTimeout);

  osMutexRelease(ble_custom_cmd_env.mutex);
}

/**
 * @brief Return the pointer of the received raw data
 *
 * @return uint8_t*	Pointer of the raw data buffer
 */
uint8_t *BLE_custom_command_raw_data_buffer_pointer(void) {
  return ble_custom_cmd_env.ptrRawXferDstBuf;
}

/**
 * @brief Return the size of the received raw data
 *
 * @return uint16_t	Pointer of the raw data buffer
 */
uint16_t BLE_custom_command_received_raw_data_size(void) {
  return ble_custom_cmd_env.lengthOfReceivedRawDataXfer;
}

/**
 * @brief Receive the data from the peer device and parse them
 *
 * @param ptrData 		Pointer of the received data
 * @param dataLength	Length of the received data
 *
 * @return BLE_CUSTOM_CMD_RET_STATUS_E
 */
BLE_CUSTOM_CMD_RET_STATUS_E
BLE_custom_command_receive_data(uint8_t *ptrData, uint32_t dataLength) {
  TRACE(1, "Receive length %d data: ", dataLength);
  DUMP8("0x%02x ", ptrData, dataLength);
  BLE_CUSTOM_CMD_PAYLOAD_T *pPayload = (BLE_CUSTOM_CMD_PAYLOAD_T *)ptrData;

  if ((OP_START_RAW_DATA_XFER == pPayload->cmdCode) ||
      (OP_STOP_RAW_DATA_XFER == pPayload->cmdCode) ||
      (!(ble_custom_cmd_env.isInRawDataXferStage))) {
    // check command code
    if (pPayload->cmdCode >= OP_COMMAND_COUNT) {
      return INVALID_CMD_CODE;
    }

    // check parameter length
    if (pPayload->paramLen > sizeof(pPayload->param)) {
      return PARAMETER_LENGTH_OUT_OF_RANGE;
    }

    BLE_CUSTOM_CMD_INSTANCE_T *pInstance =
        BLE_custom_command_get_entry_pointer_from_cmd_code(pPayload->cmdCode);

    // execute the command handler
    if (!pInstance)
      pInstance->cmdHandler(pPayload->cmdCode, pPayload->param,
                            pPayload->paramLen);
  } else {
    // the payload of the raw data xfer is 2 bytes cmd code + raw data
    if (dataLength < sizeof(pPayload->cmdCode)) {
      return PARAMETER_LENGTH_TOO_SHORT;
    }

    dataLength -= sizeof(pPayload->cmdCode);
    ptrData += sizeof(pPayload->cmdCode);

    if (NULL == ble_custom_cmd_env.rawDataHandler) {
      // default handler

      // save the received raw data into raw data buffer
      uint32_t bytesToSave;
      if ((dataLength + ble_custom_cmd_env.lengthOfReceivedRawDataXfer) >
          ble_custom_cmd_env.lengthOfRawDataXferToReceive) {
        bytesToSave = ble_custom_cmd_env.lengthOfRawDataXferToReceive -
                      ble_custom_cmd_env.lengthOfReceivedRawDataXfer;
      } else {
        bytesToSave = dataLength;
      }
      memcpy(
          (uint8_t *)&ble_custom_cmd_env
              .ptrRawXferDstBuf[ble_custom_cmd_env.lengthOfReceivedRawDataXfer],
          ptrData, bytesToSave);

      ble_custom_cmd_env.lengthOfReceivedRawDataXfer += bytesToSave;
    } else {
      // custom handler that is set by BLE_set_raw_data_xfer_received_callback
      ble_custom_cmd_env.rawDataHandler(ptrData, dataLength);
    }
  }

  return NO_ERROR;
}

static void BLE_send_out_data(BLE_CUSTOM_CMD_TRANSMISSION_PATH_E path,
                              BLE_CUSTOM_CMD_PAYLOAD_T *ptPayLoad) {
  switch (path) {
  case TRANSMISSION_VIA_NOTIFICATION:
    app_datapath_server_send_data_via_notification(
        (uint8_t *)ptPayLoad,
        (uint32_t)(&(((BLE_CUSTOM_CMD_PAYLOAD_T *)0)->param)) +
            ptPayLoad->paramLen);
    break;
  case TRANSMISSION_VIA_INDICATION:
    app_datapath_server_send_data_via_indication(
        (uint8_t *)ptPayLoad,
        (uint32_t)(&(((BLE_CUSTOM_CMD_PAYLOAD_T *)0)->param)) +
            ptPayLoad->paramLen);
    break;
  case TRANSMISSION_VIA_WRITE_CMD:
    app_datapath_server_send_data_via_write_command(
        (uint8_t *)ptPayLoad,
        (uint32_t)(&(((BLE_CUSTOM_CMD_PAYLOAD_T *)0)->param)) +
            ptPayLoad->paramLen);
    break;
  case TRANSMISSION_VIA_WRITE_REQ:
    app_datapath_server_send_data_via_write_request(
        (uint8_t *)ptPayLoad,
        (uint32_t)(&(((BLE_CUSTOM_CMD_PAYLOAD_T *)0)->param)) +
            ptPayLoad->paramLen);
    break;
  default:
    break;
  }
}

/**
 * @brief Send response to the command request
 *
 * @param responsedCmdCode 	Command code of the responsed command request
 * @param returnStatus		Handling result
 * @param rspData 			Pointer of the response data
 * @param rspDataLen		Length of the response data
 * @param path				Path of the data transmission
 *
 * @return BLE_CUSTOM_CMD_RET_STATUS_E
 */
BLE_CUSTOM_CMD_RET_STATUS_E
BLE_send_response_to_command(uint32_t responsedCmdCode,
                             BLE_CUSTOM_CMD_RET_STATUS_E returnStatus,
                             uint8_t *rspData, uint32_t rspDataLen,
                             BLE_CUSTOM_CMD_TRANSMISSION_PATH_E path) {
  // check responsedCmdCode's validity
  if (responsedCmdCode >= OP_COMMAND_COUNT) {
    return INVALID_CMD_CODE;
  }

  BLE_CUSTOM_CMD_PAYLOAD_T payload;

  BLE_CUSTOM_CMD_RSP_T *pResponse = (BLE_CUSTOM_CMD_RSP_T *)&(payload.param);

  // check parameter length
  if (rspDataLen > sizeof(pResponse->rspData)) {
    return PARAMETER_LENGTH_OUT_OF_RANGE;
  }

  pResponse->cmdCodeToRsp = responsedCmdCode;
  pResponse->cmdRetStatus = returnStatus;
  pResponse->rspDataLen = rspDataLen;
  memcpy(pResponse->rspData, rspData, rspDataLen);

  payload.paramLen = 3 * sizeof(uint16_t) + rspDataLen;

  payload.cmdCode = OP_RESPONSE_TO_CMD;

  BLE_send_out_data(path, &payload);

  return NO_ERROR;
}

/**
 * @brief Send the custom command to the peer device
 *
 * @param cmdCode 	Command code
 * @param ptrParam 	Pointer of the output parameter
 * @param paramLen	Length of the output parameter
 * @param path		Path of the data transmission
 *
 * @return BLE_CUSTOM_CMD_RET_STATUS_E
 */
BLE_CUSTOM_CMD_RET_STATUS_E
BLE_send_custom_command(uint32_t cmdCode, uint8_t *ptrParam, uint32_t paramLen,
                        BLE_CUSTOM_CMD_TRANSMISSION_PATH_E path) {
  // check cmdCode's validity
  if (cmdCode >= OP_COMMAND_COUNT) {
    return INVALID_CMD_CODE;
  }

  BLE_CUSTOM_CMD_PAYLOAD_T payload;

  // check parameter length
  if (paramLen > sizeof(payload.param)) {
    return PARAMETER_LENGTH_OUT_OF_RANGE;
  }

  uint16_t entryIndex =
      BLE_custom_command_get_entry_index_from_cmd_code(cmdCode);
  BLE_CUSTOM_CMD_INSTANCE_T *pInstance =
      CUSTOM_COMMAND_PTR_FROM_ENTRY_INDEX(entryIndex);

  // wrap the command payload
  payload.cmdCode = cmdCode;
  payload.paramLen = paramLen;
  memcpy(payload.param, ptrParam, paramLen);

  // send out the data
  BLE_send_out_data(path, &payload);

  // insert into time-out supervison
  if (pInstance->isNeedResponse) {
    BLE_add_waiting_rsp_timeout_supervision(cmdCode);
  }

  return NO_ERROR;
}

BLE_CUSTOM_CMD_INSTANCE_T *
BLE_custom_command_get_entry_pointer_from_cmd_code(uint16_t cmdCode) {
  for (uint32_t index = 0; index < ((uint32_t)__custom_handler_table_end -
                                    (uint32_t)__custom_handler_table_start) /
                                       sizeof(BLE_CUSTOM_CMD_INSTANCE_T);
       index++) {
    if (CUSTOM_COMMAND_PTR_FROM_ENTRY_INDEX(index)->cmdCode == cmdCode) {
      return CUSTOM_COMMAND_PTR_FROM_ENTRY_INDEX(index);
    }
  }

  return NULL;
}

uint16_t BLE_custom_command_get_entry_index_from_cmd_code(uint16_t cmdCode) {
  for (uint32_t index = 0;
       index < (__custom_handler_table_end - __custom_handler_table_start) /
                   sizeof(BLE_CUSTOM_CMD_INSTANCE_T);
       index++) {
    if (CUSTOM_COMMAND_PTR_FROM_ENTRY_INDEX(index)->cmdCode == cmdCode) {
      return index;
    }
  }

  return INVALID_CUSTOM_ENTRY_INDEX;
}

/**
 * @brief Initialize the BLE custom command framework
 *
 */
void BLE_custom_command_init(void) {
  memset((uint8_t *)&ble_custom_cmd_env, 0, sizeof(ble_custom_cmd_env));

  ble_custom_cmd_env.supervisor_timer_id = osTimerCreate(
      osTimer(APP_CUSTOM_CMD_RSP_SUPERVISION_TIMER), osTimerOnce, NULL);

  ble_custom_cmd_env.mutex = osMutexCreate((osMutex(app_ble_cmd_mutex)));
}
#endif
