// Copyright 2023 The Pigweed Authors
//
// 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
//
//     https://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 "pw_bluetooth_sapphire/internal/host/common/uuid.h"

#include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
#include "pw_unit_test/framework.h"

namespace bt {
namespace {

// Variants of 16-bit ID 180d
constexpr uint16_t kId1As16 = 0x180d;
constexpr uint32_t kId1As32 = 0x0000180d;
constexpr UInt128 kId1As128 = {{0xFB,
                                0x34,
                                0x9B,
                                0x5F,
                                0x80,
                                0x00,
                                0x00,
                                0x80,
                                0x00,
                                0x10,
                                0x00,
                                0x00,
                                0x0d,
                                0x18,
                                0x00,
                                0x00}};
constexpr char kId1AsString[] = "0000180d-0000-1000-8000-00805f9b34fb";

// 16-bit ID for comparison
constexpr uint16_t kOther16BitId = 0x1800;

// Variants of 32-bit ID 0xdeadbeef
constexpr uint32_t kId2As32 = 0xdeadbeef;
constexpr UInt128 kId2As128 = {{0xFB,
                                0x34,
                                0x9B,
                                0x5F,
                                0x80,
                                0x00,
                                0x00,
                                0x80,
                                0x00,
                                0x10,
                                0x00,
                                0x00,
                                0xef,
                                0xbe,
                                0xad,
                                0xde}};
constexpr char kId2AsString[] = "deadbeef-0000-1000-8000-00805f9b34fb";

constexpr UInt128 kId3As128 = {
    {0x00,
     0x01,
     0x02,
     0x03,
     0x04,
     0x05,
     0x06,
     0x07,
     0x08,
     0x09,
     0x0A,
     0x0B,

     // Make this part be the same as kId1* for the sake of testing.
     0x0d,
     0x18,
     0x00,
     0x00}};
constexpr char kId3AsString[] = "0000180d-0b0a-0908-0706-050403020100";

TEST(UUIDTest, 16Bit) {
  constexpr UUID uuid(kId1As16);

  // We perform each comparison twice, swapping the lhs and rhs, to test the
  // top-level equality operators.

  // Direct comparison with uint16_t.
  EXPECT_EQ(uuid, kId1As16);
  EXPECT_NE(uuid, kOther16BitId);
  EXPECT_EQ(kId1As16, uuid);
  EXPECT_NE(kOther16BitId, uuid);

  // Direct comparison with uint32_t.
  EXPECT_EQ(uuid, kId1As32);
  EXPECT_NE(uuid, kId2As32);
  EXPECT_EQ(kId1As32, uuid);
  EXPECT_NE(kId2As32, uuid);

  // Direct comparison with UInt128.
  EXPECT_EQ(kId1As128, uuid);
  EXPECT_NE(kId2As128, uuid);

  // Direct comparison with UUID.
  EXPECT_EQ(UUID(kId1As16), uuid);
  EXPECT_EQ(UUID(kId1As32), uuid);
  EXPECT_EQ(UUID(static_cast<uint16_t>(kId1As32)), uuid);
  EXPECT_EQ(UUID(kId1As128), uuid);
  EXPECT_NE(UUID(kOther16BitId), uuid);
  EXPECT_NE(UUID(kId2As32), uuid);
  EXPECT_NE(UUID(kId2As128), uuid);

  auto as16 = uuid.As16Bit();
  EXPECT_TRUE(as16);
  EXPECT_EQ(kId1As16, *as16);
}

TEST(UUIDTest, 32Bit) {
  constexpr UUID uuid(kId2As32);

  // Direct comparison with uint32_t.
  EXPECT_EQ(uuid, kId2As32);
  EXPECT_EQ(kId2As32, uuid);
  EXPECT_NE(uuid, kId1As32);
  EXPECT_NE(kId1As32, uuid);

  // Direct comparison with UInt128.
  EXPECT_EQ(kId2As128, uuid);
  EXPECT_NE(kId1As128, uuid);

  // Direct comparison with UUID.
  EXPECT_EQ(UUID(kId2As32), uuid);
  EXPECT_EQ(UUID(kId2As128), uuid);
  EXPECT_NE(UUID(kId1As16), uuid);
  EXPECT_NE(UUID(kId1As32), uuid);
  EXPECT_NE(UUID(kId1As128), uuid);

  EXPECT_FALSE(uuid.As16Bit());
}

TEST(UUIDTest, 128Bit) {
  constexpr UUID uuid(kId3As128);

  EXPECT_EQ(kId3As128, uuid);

  // 16-bit and 32-bit comparison should fail as the base-UUID portions do not
  // match.
  EXPECT_NE(kId1As16, uuid);
  EXPECT_NE(kId1As32, uuid);

  EXPECT_EQ(UUID(kId3As128), uuid);
  EXPECT_NE(UUID(kId1As128), uuid);

  EXPECT_FALSE(uuid.As16Bit());
}

TEST(UUIDTest, CompareBytes) {
  StaticByteBuffer kUuid16Bytes(0x0d, 0x18);
  StaticByteBuffer kUuid32Bytes(0x0d, 0x18, 0x00, 0x00);
  StaticByteBuffer kUuid128Bytes(0xFB,
                                 0x34,
                                 0x9B,
                                 0x5F,
                                 0x80,
                                 0x00,
                                 0x00,
                                 0x80,
                                 0x00,
                                 0x10,
                                 0x00,
                                 0x00,
                                 0x0d,
                                 0x18,
                                 0x00,
                                 0x00);

  constexpr UUID uuid(kId1As16);
  EXPECT_TRUE(uuid.CompareBytes(kUuid16Bytes));
  EXPECT_TRUE(uuid.CompareBytes(kUuid32Bytes));
  EXPECT_TRUE(uuid.CompareBytes(kUuid128Bytes));

  BufferView empty;
  EXPECT_FALSE(uuid.CompareBytes(empty));
}

TEST(UUIDTest, ToString) {
  EXPECT_EQ(kId1AsString, UUID(kId1As16).ToString());
  EXPECT_EQ(kId1AsString, UUID(kId1As32).ToString());
  EXPECT_EQ(kId1AsString, UUID(kId1As128).ToString());

  EXPECT_EQ(kId2AsString, UUID(kId2As32).ToString());
  EXPECT_EQ(kId2AsString, UUID(kId2As128).ToString());

  EXPECT_EQ(kId3AsString, UUID(kId3As128).ToString());
}

TEST(UUIDTest, IsStringValidUuid) {
  EXPECT_FALSE(IsStringValidUuid("0000180d00001000800000805f9b34fb"));
  EXPECT_FALSE(IsStringValidUuid("0000180d-0000-1000-8000000805f9b34fb"));
  EXPECT_FALSE(IsStringValidUuid("0000180d-0000-100008000-00805f9b34fb"));
  EXPECT_FALSE(IsStringValidUuid("0000180d-000001000-8000-00805f9b34fb"));
  EXPECT_FALSE(IsStringValidUuid("0000180d00000-1000-8000-00805f9b34fb"));
  EXPECT_FALSE(IsStringValidUuid("0000180d-0000-1000-8000-00805g9b34fb"));
  EXPECT_FALSE(IsStringValidUuid("000-180d-0000-1000-8000-00805f9b34fb"));

  // Combinations of lower and upper case characters should work.
  EXPECT_TRUE(IsStringValidUuid("0000180d-0000-1000-8000-00805f9b34fb"));
  EXPECT_TRUE(IsStringValidUuid("0000180D-0000-1000-8000-00805F9B34FB"));
  EXPECT_TRUE(IsStringValidUuid("0000180d-0000-1000-8000-00805F9b34fB"));
  EXPECT_TRUE(IsStringValidUuid(kId2AsString));
  EXPECT_TRUE(IsStringValidUuid(kId3AsString));
}

TEST(UUIDTest, StringToUuid) {
  UUID uuid;

  EXPECT_FALSE(StringToUuid("0000180d00001000800000805f9b34fb", &uuid));
  EXPECT_FALSE(StringToUuid("0000180d-0000-1000-8000000805f9b34fb", &uuid));
  EXPECT_FALSE(StringToUuid("0000180d-0000-100008000-00805f9b34fb", &uuid));
  EXPECT_FALSE(StringToUuid("0000180d-000001000-8000-00805f9b34fb", &uuid));
  EXPECT_FALSE(StringToUuid("0000180d00000-1000-8000-00805f9b34fb", &uuid));
  EXPECT_FALSE(StringToUuid("0000180d-0000-1000-8000-00805g9b34fb", &uuid));
  EXPECT_FALSE(StringToUuid("000-180d-0000-1000-8000-00805f9b34fb", &uuid));

  // Combinations of lower and upper case characters should work.
  EXPECT_TRUE(StringToUuid("0000180d-0000-1000-8000-00805f9b34fb", &uuid));
  EXPECT_EQ(kId1As16, uuid);
  EXPECT_TRUE(StringToUuid("0000180D-0000-1000-8000-00805F9B34FB", &uuid));
  EXPECT_EQ(kId1As16, uuid);
  EXPECT_TRUE(StringToUuid("0000180d-0000-1000-8000-00805F9b34fB", &uuid));
  EXPECT_EQ(kId1As16, uuid);

  EXPECT_TRUE(StringToUuid(kId2AsString, &uuid));
  EXPECT_EQ(kId2As32, uuid);

  EXPECT_TRUE(StringToUuid(kId3AsString, &uuid));
  EXPECT_EQ(kId3As128, uuid.value());
}

TEST(UUIDTest, StringToUuid16) {
  UUID uuid;

  EXPECT_FALSE(StringToUuid("0180d", &uuid));
  EXPECT_FALSE(StringToUuid("0000180d", &uuid));
  EXPECT_FALSE(StringToUuid("why", &uuid));
  EXPECT_FALSE(StringToUuid("d", &uuid));
  EXPECT_FALSE(StringToUuid("0x180d", &uuid));

  // Combinations of lower and upper case characters should work.
  EXPECT_TRUE(StringToUuid("180d", &uuid));
  EXPECT_EQ(kId1As16, uuid);
  EXPECT_TRUE(StringToUuid("180D", &uuid));
  EXPECT_EQ(kId1As16, uuid);
}

TEST(UUIDTest, FromBytes) {
  StaticByteBuffer kUuid16Bytes(0x0d, 0x18);
  StaticByteBuffer kUuid32Bytes(0x0d, 0x18, 0x00, 0x00);
  StaticByteBuffer kUuid128Bytes(0xFB,
                                 0x34,
                                 0x9B,
                                 0x5F,
                                 0x80,
                                 0x00,
                                 0x00,
                                 0x80,
                                 0x00,
                                 0x10,
                                 0x00,
                                 0x00,
                                 0x0d,
                                 0x18,
                                 0x00,
                                 0x00);

  StaticByteBuffer kInvalid0(0x0d);
  StaticByteBuffer kInvalid1(0x0d, 0x18, 0x00);
  BufferView kInvalid2;

  UUID uuid;

  EXPECT_FALSE(UUID::FromBytes(kInvalid0, &uuid));
  EXPECT_FALSE(UUID::FromBytes(kInvalid1, &uuid));
  EXPECT_FALSE(UUID::FromBytes(kInvalid2, &uuid));

  EXPECT_TRUE(UUID::FromBytes(kUuid16Bytes, &uuid));
  EXPECT_EQ(kId1As16, uuid);
  EXPECT_TRUE(UUID::FromBytes(kUuid32Bytes, &uuid));
  EXPECT_EQ(kId1As16, uuid);
  EXPECT_TRUE(UUID::FromBytes(kUuid128Bytes, &uuid));
  EXPECT_EQ(kId1As16, uuid);
}

TEST(UUIDTest, ByteConstructor) {
  const StaticByteBuffer kUuid16(0x0d, 0x18);
  UUID uuid(kUuid16);
  EXPECT_EQ(kId1As16, uuid);
}

TEST(UUIDTest, ByteConstructorAssertsOnInvalidInput) {
  const StaticByteBuffer kInvalidUuid(0x0d);
  EXPECT_DEATH_IF_SUPPORTED((UUID(kInvalidUuid)), ".*");
}

TEST(UUIDTest, CompactSize) {
  UUID direct(kId1As16);
  UUID fromstring;

  StringToUuid(kId1AsString, &fromstring);

  EXPECT_EQ(2u, direct.CompactSize());
  EXPECT_EQ(2u, fromstring.CompactSize());

  direct = UUID(kId2As32);
  StringToUuid(kId2AsString, &fromstring);

  EXPECT_EQ(4u, direct.CompactSize());
  EXPECT_EQ(4u, fromstring.CompactSize());
  EXPECT_EQ(16u, direct.CompactSize(/*allow_32bit=*/false));
  EXPECT_EQ(16u, fromstring.CompactSize(/*allow_32bit=*/false));

  direct = UUID(kId3As128);
  StringToUuid(kId3AsString, &fromstring);

  EXPECT_EQ(16u, direct.CompactSize());
  EXPECT_EQ(16u, fromstring.CompactSize());
}

TEST(UUIDTest, ToBytes16) {
  StaticByteBuffer kUuid16Bytes(0x0d, 0x18);

  UUID uuid(kId1As16);
  DynamicByteBuffer bytes(uuid.CompactSize());

  EXPECT_EQ(bytes.size(), uuid.ToBytes(&bytes));
  EXPECT_TRUE(ContainersEqual(kUuid16Bytes, bytes));

  uuid = UUID(kId1As32);

  EXPECT_EQ(bytes.size(), uuid.ToBytes(&bytes));
  EXPECT_TRUE(ContainersEqual(kUuid16Bytes, bytes));
}

TEST(UUIDTest, ToBytes32) {
  StaticByteBuffer kUuid32Bytes(0xef, 0xbe, 0xad, 0xde);

  UUID uuid(kId2As32);
  DynamicByteBuffer bytes(uuid.CompactSize());

  EXPECT_EQ(bytes.size(), uuid.ToBytes(&bytes));
  EXPECT_TRUE(ContainersEqual(kUuid32Bytes, bytes));

  StaticByteBuffer<16> bytes128;
  EXPECT_EQ(bytes128.size(), uuid.ToBytes(&bytes128, /*allow_32bit=*/false));
  EXPECT_TRUE(ContainersEqual(kId2As128, bytes128));
}

TEST(UUIDTest, CompactView16) {
  StaticByteBuffer kUuid16Bytes(0x0d, 0x18);

  UUID uuid(kId1As16);

  BufferView view = uuid.CompactView();
  EXPECT_TRUE(ContainersEqual(kUuid16Bytes, view));

  uuid = UUID(kId1As32);
  view = uuid.CompactView();
  EXPECT_TRUE(ContainersEqual(kUuid16Bytes, view));
}

TEST(UUIDTest, CompactView32) {
  StaticByteBuffer kUuid32Bytes(0xef, 0xbe, 0xad, 0xde);

  UUID uuid(kId2As32);

  BufferView view = uuid.CompactView();
  EXPECT_TRUE(ContainersEqual(kUuid32Bytes, view));

  view = uuid.CompactView(/*allow_32bit=*/false);
  EXPECT_TRUE(ContainersEqual(kId2As128, view));
}

TEST(UUIDTest, Hash) {
  constexpr UUID uuid1(kId3As128);
  constexpr UUID uuid2(kId3As128);

  EXPECT_EQ(uuid1, uuid2);
  EXPECT_EQ(uuid1.Hash(), uuid2.Hash());
}

TEST(UUIDTest, Generate) {
  for (int i = 0; i < 50; i++) {
    UUID uuid = UUID::Generate();
    EXPECT_EQ(uuid.value()[6] & 0b1111'0000, 0b0100'0000);
    EXPECT_EQ(uuid.value()[8] & 0b1100'0000, 0b1000'0000);
  }
}

}  // namespace
}  // namespace bt
