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

#include <gtest/gtest.h>

#include <iterator>
#include <utility>

#include "types/bluetooth/uuid.h"

using bluetooth::Uuid;

namespace gatt {

namespace {
/* make_pair doesn't work well with ASSERT_EQ, have own helper instead */
inline std::pair<uint16_t, uint16_t> make_pair_u16(uint16_t first, uint16_t second) {
  return std::make_pair(first, second);
}

Uuid SERVICE_1_UUID = Uuid::FromString("00001800-0000-1000-8000-00805f9b34fb");
Uuid SERVICE_2_UUID = Uuid::FromString("00001801-0000-1000-8000-00805f9b34fb");
Uuid SERVICE_3_UUID = Uuid::FromString("0000180f-0000-1000-8000-00805f9b34fb");
Uuid SERVICE_4_UUID = Uuid::FromString("0000fef5-0000-1000-8000-00805f9b34fb");
Uuid SERVICE_5_UUID = Uuid::FromString("0000180a-0000-1000-8000-00805f9b34fb");
Uuid SERVICE_1_CHAR_1_UUID = Uuid::FromString("00002a00-0000-1000-8000-00805f9b34fb");
Uuid SERVICE_1_CHAR_1_DESC_1_UUID = Uuid::FromString("00002902-0000-1000-8000-00805f9b34fb");

}  // namespace

/* Verify adding empty service works ok */
TEST(DatabaseBuilderTest, EmptyServiceAddTest) {
  DatabaseBuilder builder;

  EXPECT_FALSE(builder.InProgress());

  // Simple database, just one empty
  builder.AddService(0x0001, 0x0001, SERVICE_1_UUID, true);
  EXPECT_FALSE(builder.StartNextServiceExploration());

  Database result = builder.Build();

  // verify that the returned database matches what was discovered
  auto service = result.Services().begin();
  ASSERT_EQ(service->handle, 0x0001);
  ASSERT_EQ(service->end_handle, 0x0001);
  ASSERT_EQ(service->is_primary, true);
  ASSERT_EQ(service->uuid, SERVICE_1_UUID);
}

/* Verify adding service, characteristic and descriptor work */
TEST(DatabaseBuilderTest, DescriptorAddTest) {
  DatabaseBuilder builder;

  EXPECT_FALSE(builder.InProgress());

  // Simple database, just one empty
  builder.AddService(0x0001, 0x000f, SERVICE_1_UUID, true);
  builder.AddCharacteristic(0x0002, 0x0003, SERVICE_1_CHAR_1_UUID, 0x02);
  builder.AddDescriptor(0x0004, SERVICE_1_CHAR_1_DESC_1_UUID);

  Database result = builder.Build();

  // verify that the returned database matches what was discovered
  auto service = result.Services().begin();
  ASSERT_EQ(service->handle, 0x0001);
  ASSERT_EQ(service->end_handle, 0x000f);
  ASSERT_EQ(service->is_primary, true);
  ASSERT_EQ(service->uuid, SERVICE_1_UUID);

  ASSERT_EQ(service->characteristics[0].uuid, SERVICE_1_CHAR_1_UUID);
  ASSERT_EQ(service->characteristics[0].declaration_handle, 0x0002);
  ASSERT_EQ(service->characteristics[0].value_handle, 0x0003);
  ASSERT_EQ(service->characteristics[0].properties, 0x02);

  ASSERT_EQ(service->characteristics[0].descriptors[0].uuid, SERVICE_1_CHAR_1_DESC_1_UUID);
  ASSERT_EQ(service->characteristics[0].descriptors[0].handle, 0x0004);
}

/* This test verifies that DatabaseBuilder properly handle discovery of
 * secondary service, that is added to the discovery queue from included service
 * definition. Such service might come out of order.  */
TEST(DatabaseBuilderTest, SecondaryServiceOutOfOrderTest) {
  DatabaseBuilder builder;

  EXPECT_FALSE(builder.InProgress());

  // At start of discovery, builder will receive All services in order from
  // lower layers.
  builder.AddService(0x0001, 0x000f, SERVICE_1_UUID, true);
  builder.AddService(0x0030, 0x003f, SERVICE_3_UUID, true);
  builder.AddService(0x0050, 0x005f, SERVICE_5_UUID, true);

  // First service skipped, no place for handles
  EXPECT_TRUE(builder.StartNextServiceExploration());
  ASSERT_EQ(builder.CurrentlyExploredService(), make_pair_u16(0x0001, 0x000f));

  // For this test, content of first service is irrevelant

  EXPECT_TRUE(builder.StartNextServiceExploration());
  // Grabbing first service, to start Included Service and Characteristic
  // discovery
  ASSERT_EQ(builder.CurrentlyExploredService(), make_pair_u16(0x0030, 0x003f));

  builder.AddIncludedService(0x0031, SERVICE_4_UUID, 0x0040, 0x004f);
  builder.AddIncludedService(0x0032, SERVICE_2_UUID, 0x0020, 0x002f);

  /* Secondary service exploration */
  EXPECT_TRUE(builder.StartNextServiceExploration());
  ASSERT_EQ(builder.CurrentlyExploredService(), make_pair_u16(0x0020, 0x002f));

  /* Secondary service exploration */
  EXPECT_TRUE(builder.StartNextServiceExploration());
  ASSERT_EQ(builder.CurrentlyExploredService(), make_pair_u16(0x0040, 0x004f));

  /* Back to primary service exploration */
  EXPECT_TRUE(builder.StartNextServiceExploration());
  ASSERT_EQ(builder.CurrentlyExploredService(), make_pair_u16(0x0050, 0x005f));

  Database result = builder.Build();

  // verify that the returned database matches what was discovered
  auto service = result.Services().begin();
  ASSERT_EQ(service->handle, 0x0001);
  ASSERT_EQ(service->is_primary, true);
  ASSERT_EQ(service->uuid, SERVICE_1_UUID);

  service++;
  ASSERT_EQ(service->handle, 0x0020);
  ASSERT_EQ(service->end_handle, 0x002f);
  ASSERT_EQ(service->uuid, SERVICE_2_UUID);
  ASSERT_EQ(service->is_primary, false);

  service++;
  ASSERT_EQ(service->handle, 0x0030);
  ASSERT_EQ(service->end_handle, 0x003f);
  ASSERT_EQ(service->uuid, SERVICE_3_UUID);
  ASSERT_EQ(service->is_primary, true);
  ASSERT_EQ(service->included_services.size(), (size_t)2);
  ASSERT_EQ(service->included_services[0].start_handle, 0x0040);
  ASSERT_EQ(service->included_services[0].end_handle, 0x004f);
  ASSERT_EQ(service->included_services[1].start_handle, 0x0020);
  ASSERT_EQ(service->included_services[1].end_handle, 0x002f);

  service++;
  ASSERT_EQ(service->handle, 0x0040);
  ASSERT_EQ(service->uuid, SERVICE_4_UUID);
  ASSERT_EQ(service->is_primary, false);

  service++;
  ASSERT_EQ(service->handle, 0x0050);
  ASSERT_EQ(service->uuid, SERVICE_5_UUID);
  ASSERT_EQ(service->is_primary, true);

  service++;
  ASSERT_EQ(service, result.Services().end());
}

}  // namespace gatt
