/*
 * Copyright 2020 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.
 */

#pragma once

#include <bluetooth/log.h>

#include <cstdint>
#include <string>
#include <vector>

#include "device/include/esco_parameters.h"
#include "internal_include/bt_target.h"
#include "macros.h"
#include "stack/btm/sco_pkt_status.h"
#include "stack/include/btm_api_types.h"
#include "types/raw_address.h"

#define BTM_MSBC_CODE_SIZE 240
#define BTM_LC3_CODE_SIZE 480

constexpr uint16_t kMaxScoLinks = static_cast<uint16_t>(BTM_MAX_SCO_LINKS);

/* SCO-over-HCI audio related definitions */
namespace bluetooth::audio::sco {

/* Initialize SCO-over-HCI socket (UIPC); the client is audio server */
void init();

/* Open the socket when there is SCO connection open */
void open();

/* Clean up the socket when the SCO connection is done */
void cleanup();

/* Read PCM data from the socket (audio server) for SCO Tx */
size_t read(uint8_t* p_buf, uint32_t len);

/* Write PCM data to the socket from SCO Rx */
size_t write(const uint8_t* buf, uint32_t len);
}  // namespace bluetooth::audio::sco

/* SCO-over-HCI audio HFP WBS related definitions */
namespace bluetooth::audio::sco::wbs {

/* Initialize struct used for storing WBS related information.
 * Args:
 *    pkt_size - Length of the SCO packet. It is determined based on the BT-USB
 *    adapter's capability and alt mode setting. The value should be queried
 *    from HAL interface. It will be used to determine the size of the SCO
 *    packet buffer. Currently, the stack only supports 60 and 72.
 * Returns:
 *    The selected packet size. Will fallback to the typical mSBC packet
 *    length(60) if the pkt_size argument is not supported.
 */
size_t init(size_t pkt_size);

/* Clean up when the SCO connection is done */
void cleanup();

/* Fill in packet loss stats
 * Args:
 *    num_decoded_frames - Output argument for the number of decode frames
 *    packet_loss_ratio - Output argument for the ratio of lost frames
 * Returns:
 *    False for invalid arguments or unreasonable stats. True otherwise.
 */
bool fill_plc_stats(int* num_decoded_frames, double* packet_loss_ratio);

/* Try to enqueue a packet to a buffer.
 * Args:
 *    data - Vector of received packet data bytes.
 *    corrupted - If the current mSBC packet read is corrupted.
 * Returns:
 *    true if enqueued, false if it failed.
 */
bool enqueue_packet(const std::vector<uint8_t>& data, bool corrupted);

/* Try to decode mSBC frames from the packets in the buffer.
 * Args:
 *    output - Pointer to the decoded PCM bytes caller can read from.
 * Returns:
 *    The length of decoded bytes. 0 if failed.
 */
size_t decode(const uint8_t** output);

/* Try to encode PCM data into one SCO packet and put the packets in the buffer.
 * Args:
 *    data - Pointer to the input PCM bytes for the encoder to encode.
 *    len - Length of the input data.
 * Returns:
 *    The length of input data that is encoded. 0 if failed.
 */
size_t encode(int16_t* data, size_t len);

/* Dequeue a SCO packet with encoded mSBC data if possible. The length of the
 * packet is determined by the pkt_size set by the init().
 * Args:
 *    output - Pointer to output mSBC packets encoded by the encoder.
 * Returns:
 *    The length of dequeued packet. 0 if failed.
 */
size_t dequeue_packet(const uint8_t** output);

/* Get mSBC packets' status record.
 * Returns:
 *      Pointer to the record struct, nullptr if not valid.
 */
tBTM_SCO_PKT_STATUS* get_pkt_status();
}  // namespace bluetooth::audio::sco::wbs

/* SCO-over-HCI audio HFP SWB related definitions */
namespace bluetooth::audio::sco::swb {

/* Initialize struct used for storing SWB related information.
 * Args:
 *    pkt_size - Length of the SCO packet. It is determined based on the BT-USB
 *    adapter's capability and alt mode setting. The value should be queried
 *    from HAL interface. It will be used to determine the size of the SCO
 *    packet buffer. Currently, the stack only supports 60 and 72.
 * Returns:
 *    The selected packet size. Will fallback to the typical LC3 packet
 *    length(60) if the pkt_size argument is not supported.
 */
size_t init(size_t pkt_size);

/* Clean up when the SCO connection is done */
void cleanup();

/* Fill in packet loss stats
 * Args:
 *    num_decoded_frames - Output argument for the number of decode frames
 *    packet_loss_ratio - Output argument for the ratio of lost frames
 * Returns:
 *    False for invalid arguments or unreasonable stats. True otherwise.
 */
bool fill_plc_stats(int* num_decoded_frames, double* packet_loss_ratio);

/* Try to enqueue a packet to a buffer.
 * Args:
 *    data - Vector of received packet data bytes.
 *    corrupted - If the current LC3 packet read is corrupted.
 * Returns:
 *    true if enqueued, false if it failed.
 */
bool enqueue_packet(const std::vector<uint8_t>& data, bool corrupted);

/* Try to decode LC3 frames from the packets in the buffer.
 * Args:
 *    output - Pointer to the decoded PCM bytes caller can read from.
 * Returns:
 *    The length of decoded bytes. 0 if failed.
 */
size_t decode(const uint8_t** output);

/* Try to encode PCM data into one SCO packet and put the packets in the buffer.
 * Args:
 *    data - Pointer to the input PCM bytes for the encoder to encode.
 *    len - Length of the input data.
 * Returns:
 *    The length of input data that is encoded. 0 if failed.
 */
size_t encode(int16_t* data, size_t len);

/* Dequeue a SCO packet with encoded LC3 data if possible. The length of the
 * packet is determined by the pkt_size set by the init().
 * Args:
 *    output - Pointer to output LC3 packets encoded by the encoder.
 * Returns:
 *    The length of dequeued packet. 0 if failed.
 */
size_t dequeue_packet(const uint8_t** output);

/* Get LC3 packets' status record.
 * Returns:
 *      Pointer to the record struct, nullptr if not valid.
 */
tBTM_SCO_PKT_STATUS* get_pkt_status();
}  // namespace bluetooth::audio::sco::swb

/* Define the structures needed by sco */
typedef enum : uint16_t {
  SCO_ST_UNUSED = 0,
  SCO_ST_LISTENING = 1,
  SCO_ST_W4_CONN_RSP = 2,
  SCO_ST_CONNECTING = 3,
  SCO_ST_CONNECTED = 4,
  SCO_ST_DISCONNECTING = 5,
  SCO_ST_PEND_UNPARK = 6,
  SCO_ST_PEND_ROLECHANGE = 7,
  SCO_ST_PEND_MODECHANGE = 8,
} tSCO_STATE;

inline std::string sco_state_text(const tSCO_STATE& state) {
  switch (state) {
    CASE_RETURN_TEXT(SCO_ST_UNUSED);
    CASE_RETURN_TEXT(SCO_ST_LISTENING);
    CASE_RETURN_TEXT(SCO_ST_W4_CONN_RSP);
    CASE_RETURN_TEXT(SCO_ST_CONNECTING);
    CASE_RETURN_TEXT(SCO_ST_CONNECTED);
    CASE_RETURN_TEXT(SCO_ST_DISCONNECTING);
    CASE_RETURN_TEXT(SCO_ST_PEND_UNPARK);
    CASE_RETURN_TEXT(SCO_ST_PEND_ROLECHANGE);
    CASE_RETURN_TEXT(SCO_ST_PEND_MODECHANGE);
    default:
      return std::string("unknown_sco_state: ") + std::to_string(static_cast<uint16_t>(state));
  }
}

/* Define the structure that contains (e)SCO data */
typedef struct {
  tBTM_ESCO_CBACK* p_esco_cback; /* Callback for eSCO events     */
  enh_esco_params_t setup;
  tBTM_ESCO_DATA data; /* Connection complete information */
  uint8_t hci_status;
} tBTM_ESCO_INFO;

/* Define the structure used for SCO Management */
typedef struct {
  tBTM_ESCO_INFO esco;    /* Current settings             */
  tBTM_SCO_CB* p_conn_cb; /* Callback for when connected  */
  tBTM_SCO_CB* p_disc_cb; /* Callback for when disconnect */
  tSCO_STATE state;       /* The state of the SCO link    */

  uint16_t hci_handle; /* HCI Handle                   */
public:
  bool is_active() const { return state != SCO_ST_UNUSED; }
  bool is_inband() const { return esco.setup.input_data_path == ESCO_DATA_PATH_HCI; }
  tBTM_SCO_CODEC_TYPE get_codec_type() const {
    switch (esco.setup.coding_format) {
      case ESCO_CODING_FORMAT_CVSD:
        return BTM_SCO_CODEC_CVSD;
      case ESCO_CODING_FORMAT_MSBC:
        return BTM_SCO_CODEC_MSBC;
      case ESCO_CODING_FORMAT_LC3:
        return BTM_SCO_CODEC_LC3;
      default:
        return BTM_SCO_CODEC_NONE;
    }
  }
  uint16_t Handle() const { return hci_handle; }

  bool is_orig;      /* true if the originator       */
  bool rem_bd_known; /* true if remote BD addr known */
} tSCO_CONN;

/* SCO Management control block */
struct tSCO_CB {
  tSCO_CONN sco_db[BTM_MAX_SCO_LINKS];
  enh_esco_params_t def_esco_parms;
  bool esco_supported; /* true if 1.2 cntlr AND supports eSCO links */

  tSCO_CONN* get_sco_connection_from_index(uint16_t index) {
    return (index < kMaxScoLinks) ? (&sco_db[index]) : nullptr;
  }

  tSCO_CONN* get_sco_connection_from_handle(uint16_t handle) {
    tSCO_CONN* p_sco = sco_db;
    for (uint16_t xx = 0; xx < kMaxScoLinks; xx++, p_sco++) {
      if (p_sco->hci_handle == handle) {
        return p_sco;
      }
    }
    return nullptr;
  }

  void Init();

  void Free();

  uint16_t get_index(const tSCO_CONN* p_sco) const {
    bluetooth::log::assert_that(p_sco != nullptr, "assert failed: p_sco != nullptr");
    const tSCO_CONN* p = sco_db;
    for (uint16_t xx = 0; xx < kMaxScoLinks; xx++, p++) {
      if (p_sco == p) {
        return xx;
      }
    }
    return 0xffff;
  }
};

void btm_sco_chk_pend_rolechange(uint16_t hci_handle);
void btm_sco_disc_chk_pend_for_modechange(uint16_t hci_handle);

/* Send a SCO packet */
void btm_send_sco_packet(std::vector<uint8_t> data);

bool btm_peer_supports_esco_2m_phy(RawAddress remote_bda);
bool btm_peer_supports_esco_3m_phy(RawAddress remote_bda);
bool btm_peer_supports_esco_ev3(RawAddress remote_bda);
