/******************************************************************************
 *
 *  Copyright 2018 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 <algorithm>
#include <cstdint>
#include <list>
#include <string>
#include <utility>
#include <vector>

#include "bt_target.h"  // Must be first to define build configuration

#include "bta/gatt/database.h"
#include "bta/gatt/database_builder.h"
#include "stack/include/gattdefs.h"
#include "types/bluetooth/uuid.h"

using bluetooth::Uuid;

namespace gatt {

void DatabaseBuilder::AddService(uint16_t handle, uint16_t end_handle,
                                 const Uuid& uuid, bool is_primary) {
  // general case optimization - we add services in order
  if (database.services.empty() ||
      database.services.back().end_handle < handle) {
    database.services.emplace_back(Service{
        .handle = handle,
        .uuid = uuid,
        .is_primary = is_primary,
        .end_handle = end_handle,
    });
  } else {
    auto& vec = database.services;

    // Find first service whose start handle is bigger than new service handle
    auto it = std::lower_bound(
        vec.begin(), vec.end(), handle,
        [](Service s, uint16_t handle) { return s.end_handle < handle; });

    // Insert new service just before it
    vec.emplace(it, Service{
                        .handle = handle,
                        .uuid = uuid,
                        .is_primary = is_primary,
                        .end_handle = end_handle,
                    });
  }

  services_to_discover.insert({handle, end_handle});
}

void DatabaseBuilder::AddIncludedService(uint16_t handle, const Uuid& uuid,
                                         uint16_t start_handle,
                                         uint16_t end_handle) {
  Service* service = FindService(database.services, handle);
  if (!service) {
    LOG(ERROR) << "Illegal action to add to non-existing service!";
    return;
  }

  /* We discover all Primary Services first. If included service was not seen
   * before, it must be a Secondary Service */
  if (!FindService(database.services, start_handle)) {
    AddService(start_handle, end_handle, uuid, false /* not primary */);
  }

  service->included_services.push_back(IncludedService{
      .handle = handle,
      .uuid = uuid,
      .start_handle = start_handle,
      .end_handle = end_handle,
  });
}

void DatabaseBuilder::AddCharacteristic(uint16_t handle, uint16_t value_handle,
                                        const Uuid& uuid, uint8_t properties) {
  Service* service = FindService(database.services, handle);
  if (!service) {
    LOG(ERROR) << "Illegal action to add to non-existing service!";
    return;
  }

  if (service->end_handle < value_handle)
    LOG(WARNING) << "Remote device violates spec: value_handle="
                 << loghex(value_handle) << " is after service end_handle="
                 << loghex(service->end_handle);

  service->characteristics.emplace_back(Characteristic{
      .declaration_handle = handle,
      .uuid = uuid,
      .value_handle = value_handle,
      .properties = properties,
  });
  return;
}

void DatabaseBuilder::AddDescriptor(uint16_t handle, const Uuid& uuid) {
  Service* service = FindService(database.services, handle);
  if (!service) {
    LOG(ERROR) << "Illegal action to add to non-existing service!";
    return;
  }

  if (service->characteristics.empty()) {
    LOG(ERROR) << __func__
               << ": Illegal action to add to non-existing characteristic!";
    return;
  }

  Characteristic* char_node = &service->characteristics.front();
  for (auto it = service->characteristics.begin();
       it != service->characteristics.end(); it++) {
    if (it->declaration_handle > handle) break;
    char_node = &(*it);
  }

  char_node->descriptors.emplace_back(
      gatt::Descriptor{.handle = handle, .uuid = uuid});

  // We must read value for Characteristic Extended Properties
  if (uuid == Uuid::From16Bit(GATT_UUID_CHAR_EXT_PROP)) {
    descriptor_handles_to_read.emplace_back(handle);
  }
}

bool DatabaseBuilder::StartNextServiceExploration() {
  while (!services_to_discover.empty()) {
    auto handle_range = services_to_discover.begin();
    pending_service = *handle_range;
    services_to_discover.erase(handle_range);

    // Empty service declaration, nothing to explore, skip to next.
    if (pending_service.first == pending_service.second) continue;

    pending_characteristic = HANDLE_MIN;
    return true;
  }
  return false;
}

const std::pair<uint16_t, uint16_t>&
DatabaseBuilder::CurrentlyExploredService() {
  return pending_service;
}

std::pair<uint16_t, uint16_t> DatabaseBuilder::NextDescriptorRangeToExplore() {
  Service* service = FindService(database.services, pending_service.first);
  if (!service || service->characteristics.empty()) {
    return {HANDLE_MAX, HANDLE_MAX};
  }

  for (auto it = service->characteristics.cbegin();
       it != service->characteristics.cend(); it++) {
    if (it->declaration_handle > pending_characteristic) {
      auto next = std::next(it);

      /* Characteristic Declaration is followed by Characteristic Value
       * Declaration, first descriptor is after that, see BT Spect 5.0 Vol 3,
       * Part G 3.3.2 and 3.3.3 */
      uint16_t start = it->declaration_handle + 2;
      uint16_t end;
      if (next != service->characteristics.end())
        end = next->declaration_handle - 1;
      else
        end = service->end_handle;

      // No place for descriptor - skip to next characteristic
      if (start > end) continue;

      pending_characteristic = start;
      return {start, end};
    }
  }

  pending_characteristic = HANDLE_MAX;
  return {HANDLE_MAX, HANDLE_MAX};
}

Descriptor* FindDescriptorByHandle(std::list<Service>& services,
                                   uint16_t handle) {
  Service* service = FindService(services, handle);
  if (!service) return nullptr;

  Characteristic* char_node = &service->characteristics.front();
  for (auto it = service->characteristics.begin();
       it != service->characteristics.end(); it++) {
    if (it->declaration_handle > handle) break;
    char_node = &(*it);
  }

  for (auto& descriptor : char_node->descriptors) {
    if (descriptor.handle == handle) return &descriptor;
  }

  return nullptr;
}

bool DatabaseBuilder::SetValueOfDescriptors(
    const std::vector<uint16_t>& values) {
  if (values.size() > descriptor_handles_to_read.size()) {
    LOG(ERROR) << "values.size() <= descriptors.size() expected";
    descriptor_handles_to_read.clear();
    return false;
  }

  for (size_t i = 0; i < values.size(); i++) {
    Descriptor* d = FindDescriptorByHandle(database.services,
                                           descriptor_handles_to_read[i]);
    if (!d) {
      LOG(ERROR) << __func__ << "non-existing descriptor!";
      descriptor_handles_to_read.clear();
      return false;
    }

    d->characteristic_extended_properties = values[i];
  }

  descriptor_handles_to_read.erase(
      descriptor_handles_to_read.begin(),
      descriptor_handles_to_read.begin() + values.size());
  return true;
}

bool DatabaseBuilder::InProgress() const { return !database.services.empty(); }

Database DatabaseBuilder::Build() {
  Database tmp = database;
  database.Clear();
  return tmp;
}

void DatabaseBuilder::Clear() { database.Clear(); }

std::string DatabaseBuilder::ToString() const { return database.ToString(); }

}  // namespace gatt