/******************************************************************************
 *
 *  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 "database_builder.h"

#include "bt_trace.h"

#include <base/logging.h>
#include <algorithm>

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,
                                           .end_handle = end_handle,
                                           .is_primary = is_primary,
                                           .uuid = uuid});
  } 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,
                            .end_handle = end_handle,
                            .is_primary = is_primary,
                            .uuid = uuid});
  }

  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,
                     .value_handle = value_handle,
                     .properties = properties,
                     .uuid = uuid});
  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});
}

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};
}

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