/*
 * Copyright 2020 HIMSA II K/S - www.himsa.com.
 * Represented by EHIMA - www.ehima.com
 *
 * 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 <map>
#include <vector>

#include "bta_gatt_api.h"
#include "bta_gatt_queue.h"
#include "devices.h"
#include "gatt_api.h"
#include "stack/btm/btm_sec.h"

using namespace bluetooth::vc::internal;

void VolumeControlDevice::Disconnect(tGATT_IF gatt_if) {
  LOG(INFO) << __func__ << ": " << this->ToString();

  if (IsConnected()) {
    if (volume_state_handle != 0)
      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
                                           volume_state_handle);

    if (volume_flags_handle != 0)
      BTA_GATTC_DeregisterForNotifications(gatt_if, address,
                                           volume_flags_handle);

    BtaGattQueue::Clean(connection_id);
    BTA_GATTC_Close(connection_id);
    connection_id = GATT_INVALID_CONN_ID;
  } else {
    BTA_GATTC_CancelOpen(gatt_if, address, false);
  }

  device_ready = false;
  handles_pending.clear();
}

/*
 * Find the handle for the client characteristics configuration of a given
 * characteristics
 */
uint16_t VolumeControlDevice::find_ccc_handle(uint16_t chrc_handle) {
  const gatt::Characteristic* p_char =
      BTA_GATTC_GetCharacteristic(connection_id, chrc_handle);
  if (!p_char) {
    LOG(WARNING) << __func__ << ": no such handle=" << loghex(chrc_handle);
    return 0;
  }

  for (const gatt::Descriptor& desc : p_char->descriptors) {
    if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG))
      return desc.handle;
  }

  return 0;
}

bool VolumeControlDevice::set_volume_control_service_handles(
    const gatt::Service& service) {
  uint16_t state_handle = 0, state_ccc_handle = 0, control_point_handle = 0,
           flags_handle = 0, flags_ccc_handle = 0;

  for (const gatt::Characteristic& chrc : service.characteristics) {
    if (chrc.uuid == kVolumeControlStateUuid) {
      state_handle = chrc.value_handle;
      state_ccc_handle = find_ccc_handle(chrc.value_handle);
    } else if (chrc.uuid == kVolumeControlPointUuid) {
      control_point_handle = chrc.value_handle;
    } else if (chrc.uuid == kVolumeFlagsUuid) {
      flags_handle = chrc.value_handle;
      flags_ccc_handle = find_ccc_handle(chrc.value_handle);
    } else {
      LOG(WARNING) << __func__ << ": unknown characteristic=" << chrc.uuid;
    }
  }

  // Validate service handles
  if (GATT_HANDLE_IS_VALID(state_handle) &&
      GATT_HANDLE_IS_VALID(state_ccc_handle) &&
      GATT_HANDLE_IS_VALID(control_point_handle) &&
      GATT_HANDLE_IS_VALID(flags_handle)
      /* volume_flags_ccc_handle is optional */) {
    volume_state_handle = state_handle;
    volume_state_ccc_handle = state_ccc_handle;
    volume_control_point_handle = control_point_handle;
    volume_flags_handle = flags_handle;
    volume_flags_ccc_handle = flags_ccc_handle;
    return true;
  }

  return false;
}

bool VolumeControlDevice::UpdateHandles(void) {
  ResetHandles();

  bool vcs_found = false;
  const std::list<gatt::Service>* services =
      BTA_GATTC_GetServices(connection_id);
  if (services == nullptr) {
    LOG(ERROR) << "No services found";
    return false;
  }

  for (auto const& service : *services) {
    if (service.uuid == kVolumeControlUuid) {
      LOG(INFO) << "Found VCS, handle=" << loghex(service.handle);
      vcs_found = set_volume_control_service_handles(service);
      if (!vcs_found) break;
    }
  }

  return vcs_found;
}

void VolumeControlDevice::ResetHandles(void) {
  device_ready = false;

  // the handles are not valid, so discard pending GATT operations
  BtaGattQueue::Clean(connection_id);

  volume_state_handle = 0;
  volume_state_ccc_handle = 0;
  volume_control_point_handle = 0;
  volume_flags_handle = 0;
  volume_flags_ccc_handle = 0;
}

void VolumeControlDevice::ControlPointOperation(uint8_t opcode,
                                                const std::vector<uint8_t>* arg,
                                                GATT_WRITE_OP_CB cb,
                                                void* cb_data) {
  std::vector<uint8_t> set_value({opcode, change_counter});
  if (arg != nullptr)
    set_value.insert(set_value.end(), (*arg).begin(), (*arg).end());

  BtaGattQueue::WriteCharacteristic(connection_id, volume_control_point_handle,
                                    set_value, GATT_WRITE, cb, cb_data);
}

bool VolumeControlDevice::subscribe_for_notifications(tGATT_IF gatt_if,
                                                      uint16_t handle,
                                                      uint16_t ccc_handle,
                                                      GATT_WRITE_OP_CB cb) {
  tGATT_STATUS status =
      BTA_GATTC_RegisterForNotifications(gatt_if, address, handle);
  if (status != GATT_SUCCESS) {
    LOG(ERROR) << __func__ << ": failed, status=" << loghex(+status);
    return false;
  }

  std::vector<uint8_t> value(2);
  uint8_t* ptr = value.data();
  UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION);
  BtaGattQueue::WriteDescriptor(connection_id, ccc_handle, std::move(value),
                                GATT_WRITE, cb, nullptr);

  return true;
}

/**
 * Enqueue GATT requests that are required by the Volume Control to be
 * functional. This includes State characteristics read and subscription.
 * Those characteristics contain the change counter needed to send any request
 * via Control Point. Once completed successfully, the device can be stored
 * and reported as connected. In each case we subscribe first to be sure we do
 * not miss any value change.
 */
bool VolumeControlDevice::EnqueueInitialRequests(
    tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb,
    GATT_WRITE_OP_CB cccd_write_cb) {
  handles_pending.clear();
  handles_pending.insert(volume_state_handle);
  handles_pending.insert(volume_state_ccc_handle);
  if (!subscribe_for_notifications(gatt_if, volume_state_handle,
                                   volume_state_ccc_handle, cccd_write_cb)) {
    return false;
  }

  BtaGattQueue::ReadCharacteristic(connection_id, volume_state_handle,
                                   chrc_read_cb, nullptr);

  return true;
}

/**
 * Enqueue the remaining requests. Those are not so crucial and can be done
 * once Volume Control instance indicates it's readiness to profile.
 * This includes characteristics read and subscription.
 * In each case we subscribe first to be sure we do not miss any value change.
 */
void VolumeControlDevice::EnqueueRemainingRequests(
    tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb,
    GATT_WRITE_OP_CB cccd_write_cb) {
  std::map<uint16_t, uint16_t> handle_pairs{
      {volume_flags_handle, volume_flags_ccc_handle},
  };

  for (auto const& handles : handle_pairs) {
    if (GATT_HANDLE_IS_VALID(handles.second)) {
      subscribe_for_notifications(gatt_if, handles.first, handles.second,
                                  cccd_write_cb);
    }

    BtaGattQueue::ReadCharacteristic(connection_id, handles.first, chrc_read_cb,
                                     nullptr);
  }
}

bool VolumeControlDevice::VerifyReady(uint16_t handle) {
  handles_pending.erase(handle);
  device_ready = handles_pending.size() == 0;
  return device_ready;
}

bool VolumeControlDevice::IsEncryptionEnabled() {
  uint8_t sec_flag = 0;
  bool device_found =
      BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE);
  LOG(INFO) << __func__ << ": found=" << static_cast<int>(device_found)
            << " sec_flag=" << loghex(sec_flag);
  return device_found && (sec_flag & BTM_SEC_FLAG_ENCRYPTED);
}

bool VolumeControlDevice::EnableEncryption(tBTM_SEC_CALLBACK* callback) {
  int result = BTM_SetEncryption(address, BT_TRANSPORT_LE, callback, nullptr,
                                 BTM_BLE_SEC_ENCRYPT);
  LOG(INFO) << __func__ << ": result=" << +result;
  // TODO: should we care about the result??
  return true;
}