/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * 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 "bt_vendor.h"

#define UNUSED_PARAM __attribute__((unused))
#define HCI_CMD_PREAMBLE_SIZE 3
#define HCI_RESET 0x0C03
#define HCI_EVT_CMD_CMPL_OPCODE 3
#define HCI_EVT_CMD_CMPL_STATUS_RET_BYTE 5
#define MSG_STACK_TO_HC_HCI_CMD 0x2000
#define BT_HC_HDR_SIZE (sizeof(HC_BT_HDR))
#define STREAM_TO_UINT16(u16, p)                                \
  {                                                             \
    u16 = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8)); \
    (p) += 2;                                                   \
  }
#define UINT16_TO_STREAM(p, u16)    \
  {                                 \
    *(p)++ = (uint8_t)(u16);        \
    *(p)++ = (uint8_t)((u16) >> 8); \
  }
bt_vendor_callbacks_t* bt_vendor_cbacks = nullptr;

void hw_epilog_cback(void* p_mem) {
  HC_BT_HDR* p_evt_buf = (HC_BT_HDR*)p_mem;
  uint8_t *p, status;
  uint16_t opcode;

  status = *((uint8_t*)(p_evt_buf + 1) + HCI_EVT_CMD_CMPL_STATUS_RET_BYTE);
  p = (uint8_t*)(p_evt_buf + 1) + HCI_EVT_CMD_CMPL_OPCODE;
  STREAM_TO_UINT16(opcode, p);

  if (!bt_vendor_cbacks) {
    return;
  }
  /* Must free the RX event buffer */
  bt_vendor_cbacks->dealloc(p_evt_buf);

  /* Once epilog process is done, must call callback to notify caller */
  bt_vendor_cbacks->epilog_cb(BT_VND_OP_RESULT_SUCCESS);
  return;
}

static int testInit(const bt_vendor_callbacks_t* cb,
                    unsigned char* bdaddr UNUSED_PARAM) {
  if (cb == nullptr) {
    return -1;
  }
  /*store reference to user callbacks */
  bt_vendor_cbacks = (bt_vendor_callbacks_t*)cb;
  return 0;
}

static int testOperations(bt_vendor_opcode_t opcode, void* param UNUSED_PARAM) {
  BtVendor* btVendor = BtVendor::getInstance();
  if (bt_vendor_cbacks) {
    btVendor->setVendorCback(bt_vendor_cbacks, opcode);
  }
  switch (opcode) {
    case BT_VND_OP_POWER_CTRL: {
      // No callback for this opcode
      break;
    }
    case BT_VND_OP_USERIAL_OPEN: {
      int32_t(*fd_array)[] = (int32_t(*)[])param;
      int32_t fdArray[CH_MAX];
      *fdArray = *(btVendor->queryFdList());
      size_t fdcount = btVendor->queryFdCount();
      for (size_t i = 0; i < fdcount; ++i) {
        (*fd_array)[i] = fdArray[i];
      }
      return fdcount;
      break;
    }
    case BT_VND_OP_FW_CFG: {
      if (bt_vendor_cbacks) {
        bt_vendor_cbacks->fwcfg_cb(BT_VND_OP_RESULT_SUCCESS);
      }
      break;
    }
    case BT_VND_OP_GET_LPM_IDLE_TIMEOUT: {
      // No callback for this opcode
      uint32_t* timeout_ms = (uint32_t*)param;
      *timeout_ms = 0;
      break;
    }
    case BT_VND_OP_LPM_SET_MODE: {
      if (bt_vendor_cbacks) {
        bt_vendor_cbacks->lpm_cb(BT_VND_OP_RESULT_SUCCESS);
      }
      break;
    }
    case BT_VND_OP_USERIAL_CLOSE: {
      // No callback for this opcode
      break;
    }
    case BT_VND_OP_LPM_WAKE_SET_STATE: {
      // No callback for this opcode
      break;
    }
    default:
      break;
  }
  return 0;
}

static void testCleanup(void) { bt_vendor_cbacks = nullptr; }

const bt_vendor_interface_t BLUETOOTH_VENDOR_LIB_INTERFACE = {
    sizeof(bt_vendor_interface_t), testInit, testOperations, testCleanup};

void BtVendor::populateFdList(int32_t list[], size_t count) {
  fdCount = count;
  for (size_t i = 0; i < count; ++i) {
    fdList[i] = list[i];
  }
}

void BtVendor::callRemainingCbacks() {
  if (mCbacks) {
    mCbacks->audio_state_cb(BT_VND_OP_RESULT_SUCCESS);
    mCbacks->scocfg_cb(BT_VND_OP_RESULT_SUCCESS);
    mCbacks->a2dp_offload_cb(BT_VND_OP_RESULT_SUCCESS, mOpcode, 0);
    mCbacks->epilog_cb(BT_VND_OP_RESULT_SUCCESS);

    HC_BT_HDR* p_buf = NULL;
    uint8_t* p;

    /* Sending a HCI_RESET */
    /* Must allocate command buffer via HC's alloc API */
    p_buf = (HC_BT_HDR*)mCbacks->alloc(BT_HC_HDR_SIZE + HCI_CMD_PREAMBLE_SIZE);
    if (p_buf) {
      p_buf->event = MSG_STACK_TO_HC_HCI_CMD;
      p_buf->offset = 0;
      p_buf->layer_specific = 0;
      p_buf->len = HCI_CMD_PREAMBLE_SIZE;

      p = (uint8_t*)(p_buf + 1);
      UINT16_TO_STREAM(p, HCI_RESET);
      *p = 0; /* parameter length */

      /* Send command via HC's xmit_cb API */
      mCbacks->xmit_cb(HCI_RESET, p_buf, hw_epilog_cback);
    } else {
      mCbacks->epilog_cb(BT_VND_OP_RESULT_FAIL);
    }
  }
}
