/*
 * Copyright 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 <gtest/gtest.h>
#include <string.h>

#include <cstdint>
#include <map>
#include <memory>
#include <string>

#include "bta/gatt/bta_gattc_int.h"
#include "common/message_loop_thread.h"
#include "osi/include/allocator.h"
#include "stack/gatt/gatt_int.h"
#include "test/common/mock_functions.h"

// TODO(b/369381361) Enfore -Wmissing-prototypes
#pragma GCC diagnostic ignored "-Wmissing-prototypes"

namespace param {
struct {
  uint16_t conn_id;
  tGATT_STATUS status;
  uint16_t handle;
  uint16_t len;
  uint8_t* value;
  void* data;
} bta_gatt_read_complete_callback;
}  // namespace param
void bta_gatt_read_complete_callback(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
                                     uint16_t len, uint8_t* value, void* data) {
  param::bta_gatt_read_complete_callback.conn_id = conn_id;
  param::bta_gatt_read_complete_callback.status = status;
  param::bta_gatt_read_complete_callback.handle = handle;
  param::bta_gatt_read_complete_callback.len = len;
  param::bta_gatt_read_complete_callback.value = value;
  param::bta_gatt_read_complete_callback.data = data;
}

namespace param {
struct {
  uint16_t conn_id;
  tGATT_STATUS status;
  uint16_t handle;
  uint16_t len;
  const uint8_t* value;
  void* data;
} bta_gatt_write_complete_callback;
}  // namespace param

void bta_gatt_write_complete_callback(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
                                      uint16_t len, const uint8_t* value, void* data) {
  param::bta_gatt_write_complete_callback.conn_id = conn_id;
  param::bta_gatt_write_complete_callback.status = status;
  param::bta_gatt_write_complete_callback.handle = handle;
  param::bta_gatt_write_complete_callback.len = len;
  param::bta_gatt_write_complete_callback.value = value;
  param::bta_gatt_write_complete_callback.data = data;
}

namespace param {
struct {
  uint16_t conn_id;
  tGATT_STATUS status;
  void* data;
} bta_gatt_configure_mtu_complete_callback;
}  // namespace param

void bta_gatt_configure_mtu_complete_callback(uint16_t conn_id, tGATT_STATUS status, void* data) {
  param::bta_gatt_configure_mtu_complete_callback.conn_id = conn_id;
  param::bta_gatt_configure_mtu_complete_callback.status = status;
  param::bta_gatt_configure_mtu_complete_callback.data = data;
}

namespace param {
struct {
  tBTA_GATTC_EVT event;
  tBTA_GATTC* p_data;
} bta_gattc_event_complete_callback;
}  // namespace param

void bta_gattc_event_complete_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
  param::bta_gattc_event_complete_callback.event = event;
  param::bta_gattc_event_complete_callback.p_data = p_data;
}

class BtaGattTest : public ::testing::Test {
protected:
  void SetUp() override {
    reset_mock_function_count_map();
    param::bta_gatt_read_complete_callback = {};
    param::bta_gatt_write_complete_callback = {};
    param::bta_gatt_configure_mtu_complete_callback = {};
    param::bta_gattc_event_complete_callback = {};
  }

  void TearDown() override {}

  tBTA_GATTC_RCB app_control_block = {
          .p_cback = bta_gattc_event_complete_callback,
  };

  tGATT_CL_COMPLETE gatt_cl_complete = {
          .att_value =
                  {
                          .conn_id = 1,
                          .handle = 2,
                          .offset = 3,
                          .len = 4,  // length of value below
                          .auth_req = GATT_AUTH_REQ_NONE,
                          .value = {10, 11, 12, 13},
                  },
  };

  tBTA_GATTC_SERV service_control_block = {
          .mtu = 456,
  };
  tBTA_GATTC_DATA command_queue;

  tBTA_GATTC_CLCB client_channel_control_block = {
          .bta_conn_id = 456,
          .p_rcb = &app_control_block,
          .p_srcb = &service_control_block,
          .p_q_cmd = &command_queue,
  };
};

TEST_F(BtaGattTest, bta_gattc_op_cmpl_read) {
  command_queue = {
          .api_read =  // tBTA_GATTC_API_READ
          {
                  .hdr =
                          {
                                  .event = BTA_GATTC_API_READ_EVT,
                          },
                  .handle = 123,
                  .read_cb = bta_gatt_read_complete_callback,
                  .read_cb_data = static_cast<void*>(this),
          },
  };

  client_channel_control_block.p_q_cmd = &command_queue;

  tBTA_GATTC_DATA data = {
          .op_cmpl =
                  {
                          .op_code = GATTC_OPTYPE_READ,
                          .status = GATT_OUT_OF_RANGE,
                          .p_cmpl = &gatt_cl_complete,
                  },
  };

  bta_gattc_op_cmpl(&client_channel_control_block, &data);
  ASSERT_EQ(1, get_func_call_count("osi_free_and_reset"));
  ASSERT_EQ(456, param::bta_gatt_read_complete_callback.conn_id);
  ASSERT_EQ(GATT_OUT_OF_RANGE, param::bta_gatt_read_complete_callback.status);
  ASSERT_EQ(123, param::bta_gatt_read_complete_callback.handle);
  ASSERT_EQ(4, param::bta_gatt_read_complete_callback.len);
  ASSERT_EQ(10, param::bta_gatt_read_complete_callback.value[0]);
  ASSERT_EQ(this, param::bta_gatt_read_complete_callback.data);
}

TEST_F(BtaGattTest, bta_gattc_op_cmpl_write) {
  command_queue = {
          .api_write =  // tBTA_GATTC_API_WRITE
          {
                  .hdr =
                          {
                                  .event = BTA_GATTC_API_WRITE_EVT,
                          },
                  .handle = 123,
                  .write_cb = bta_gatt_write_complete_callback,
                  .write_cb_data = static_cast<void*>(this),
          },
  };

  client_channel_control_block.p_q_cmd = &command_queue;

  tBTA_GATTC_DATA data = {
          .op_cmpl =
                  {
                          .op_code = GATTC_OPTYPE_WRITE,
                          .status = GATT_OUT_OF_RANGE,
                          .p_cmpl = &gatt_cl_complete,
                  },
  };

  bta_gattc_op_cmpl(&client_channel_control_block, &data);
  ASSERT_EQ(1, get_func_call_count("osi_free_and_reset"));
  ASSERT_EQ(456, param::bta_gatt_write_complete_callback.conn_id);
  ASSERT_EQ(2, param::bta_gatt_write_complete_callback.handle);
  ASSERT_EQ(GATT_OUT_OF_RANGE, param::bta_gatt_write_complete_callback.status);
  ASSERT_EQ(this, param::bta_gatt_write_complete_callback.data);
}

TEST_F(BtaGattTest, bta_gattc_op_cmpl_config) {
  command_queue = {
          .api_mtu =  // tBTA_GATTC_API_CFG_MTU
          {
                  .hdr =
                          {
                                  .event = BTA_GATTC_API_CFG_MTU_EVT,
                          },
                  .mtu_cb = bta_gatt_configure_mtu_complete_callback,
                  .mtu_cb_data = static_cast<void*>(this),
          },
  };

  client_channel_control_block.p_q_cmd = &command_queue;

  tBTA_GATTC_DATA data = {
          .op_cmpl =
                  {
                          .op_code = GATTC_OPTYPE_CONFIG,
                          .status = GATT_PRC_IN_PROGRESS,
                  },
  };

  bta_gattc_op_cmpl(&client_channel_control_block, &data);
  ASSERT_EQ(1, get_func_call_count("osi_free_and_reset"));
  ASSERT_EQ(456, param::bta_gatt_configure_mtu_complete_callback.conn_id);

  ASSERT_EQ(GATT_PRC_IN_PROGRESS, param::bta_gatt_configure_mtu_complete_callback.status);
  ASSERT_EQ(this, param::bta_gatt_configure_mtu_complete_callback.data);
}

TEST_F(BtaGattTest, bta_gattc_op_cmpl_execute) {
  command_queue = {
          .api_exec =  // tBTA_GATTC_API_EXEC
          {
                  .hdr =
                          {
                                  .event = BTA_GATTC_API_EXEC_EVT,
                          },
          },
  };

  client_channel_control_block.p_q_cmd = &command_queue;

  tBTA_GATTC_DATA data = {
          .op_cmpl =
                  {
                          .op_code = GATTC_OPTYPE_EXE_WRITE,
                  },
  };

  bta_gattc_op_cmpl(&client_channel_control_block, &data);
  ASSERT_EQ(BTA_GATTC_EXEC_EVT, param::bta_gattc_event_complete_callback.event);
  ASSERT_EQ(1, get_func_call_count("osi_free_and_reset"));
}

TEST_F(BtaGattTest, bta_gattc_op_cmpl_read_interrupted) {
  command_queue = {
          .api_read =  // tBTA_GATTC_API_READ
          {
                  .hdr =
                          {
                                  .event = BTA_GATTC_API_READ_EVT,
                          },
                  .handle = 123,
                  .read_cb = bta_gatt_read_complete_callback,
                  .read_cb_data = static_cast<void*>(this),
          },
  };

  client_channel_control_block.p_q_cmd = &command_queue;

  // Create interrupt condition
  client_channel_control_block.auto_update = BTA_GATTC_DISC_WAITING;
  client_channel_control_block.p_srcb->srvc_hdl_chg = 1;

  tBTA_GATTC_DATA data = {
          .op_cmpl =
                  {
                          .op_code = GATTC_OPTYPE_READ,
                          .status = GATT_OUT_OF_RANGE,
                          .p_cmpl = &gatt_cl_complete,
                  },
  };

  bta_gattc_op_cmpl(&client_channel_control_block, &data);
  ASSERT_EQ(GATT_ERROR, param::bta_gatt_read_complete_callback.status);
}
