/*
 * Copyright 2020 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.
 */
#pragma once

#include <functional>
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
#include <unordered_set>
#include <utility>

#include "hci/address.h"
#include "hci/address_with_type.h"
#include "hci/class_of_device.h"
#include "hci/enum_helper.h"
#include "hci/uuid.h"
#include "storage/config_cache.h"
#include "storage/config_cache_helper.h"
#include "storage/config_keys.h"
#include "storage/mutation_entry.h"
#include "storage/serializable.h"

namespace bluetooth {
namespace storage {

class LeDevice;
class ClassicDevice;

// Make sure our macro is used
#ifdef GENERATE_PROPERTY_GETTER_SETTER_REMOVER
static_assert(
        false,
        "GENERATE_PROPERTY_GETTER_SETTER_REMOVER() must be uniquely defined once in this file");
#endif

#define GENERATE_PROPERTY_GETTER_SETTER_REMOVER(NAME, RETURN_TYPE, PROPERTY_KEY)               \
public:                                                                                        \
  std::optional<RETURN_TYPE> Get##NAME() const {                                               \
    return ConfigCacheHelper(*config_).Get<RETURN_TYPE>(section_, PROPERTY_KEY);               \
  }                                                                                            \
  MutationEntry Set##NAME(const RETURN_TYPE& value) {                                          \
    return MutationEntry::Set<RETURN_TYPE>(MutationEntry::PropertyType::NORMAL, section_,      \
                                           PROPERTY_KEY, value);                               \
  }                                                                                            \
  MutationEntry Remove##NAME() {                                                               \
    return MutationEntry::Remove(MutationEntry::PropertyType::NORMAL, section_, PROPERTY_KEY); \
  }

// Make sure our macro is used
#ifdef GENERATE_PROPERTY_GETTER_SETTER_REMOVER_WITH_CUSTOM_SETTER
static_assert(false,
              "GENERATE_PROPERTY_GETTER_SETTER_REMOVER_WITH_CUSTOM_SETTER() must be uniquely "
              "defined once in this file");
#endif

// FUNC is bracketed function definition that takes a const RETURN_TYPE& value and return
// RETURN_TYPE e.g. { return value + 1; }
#define GENERATE_PROPERTY_GETTER_SETTER_REMOVER_WITH_CUSTOM_SETTER(NAME, RETURN_TYPE,          \
                                                                   PROPERTY_KEY, FUNC)         \
public:                                                                                        \
  std::optional<RETURN_TYPE> Get##NAME() const {                                               \
    return ConfigCacheHelper(*config_).Get<RETURN_TYPE>(section_, PROPERTY_KEY);               \
  }                                                                                            \
  MutationEntry Set##NAME(const RETURN_TYPE& value) {                                          \
    auto new_value = [this](const RETURN_TYPE& value) -> RETURN_TYPE FUNC(value);              \
    return MutationEntry::Set<RETURN_TYPE>(MutationEntry::PropertyType::NORMAL, section_,      \
                                           PROPERTY_KEY, new_value);                           \
  }                                                                                            \
  MutationEntry Remove##NAME() {                                                               \
    return MutationEntry::Remove(MutationEntry::PropertyType::NORMAL, section_, PROPERTY_KEY); \
  }

// Make sure our macro is used
#ifdef GENERATE_TEMP_PROPERTY_GETTER_SETTER_REMOVER
static_assert(false,
              "GENERATE_TEMP_PROPERTY_GETTER_SETTER_REMOVER() must be uniquely defined once in "
              "this file");
#endif

// Macro to generate tempoarary property that exists in memory only
// It is subjected to a limit of 10,000 devices
// It will be cleared when the stack is restarted
#define GENERATE_TEMP_PROPERTY_GETTER_SETTER_REMOVER(NAME, RETURN_TYPE, PROPERTY_KEY)          \
public:                                                                                        \
  std::optional<RETURN_TYPE> GetTemp##NAME() const {                                           \
    return ConfigCacheHelper(*memory_only_config_).Get<RETURN_TYPE>(section_, PROPERTY_KEY);   \
  }                                                                                            \
  MutationEntry SetTemp##NAME(const RETURN_TYPE& value) {                                      \
    return MutationEntry::Set<RETURN_TYPE>(MutationEntry::PropertyType::MEMORY_ONLY, section_, \
                                           PROPERTY_KEY, value);                               \
  }                                                                                            \
  MutationEntry RemoveTemp##NAME() {                                                           \
    return MutationEntry::Remove(MutationEntry::PropertyType::MEMORY_ONLY, section_,           \
                                 PROPERTY_KEY);                                                \
  }

// A think wrapper of device in ConfigCache, allowing easy access to various predefined properties
// of a Bluetooth device
//
// Device, LeDevice, and Classic device objects are fully copyable, comparable hashable
//
// A newly created device does not have any DeviceType information and user can only read or write
// the values in this common Device abstraction layer.
//
// As soon as a user determines the type of device, they should call SetDeviceType() to assign
// device to a type After that, Classic() or Le() will return interfaces that allows access to
// deeper layer properties
class Device {
public:
  enum ConfigKeyAddressType {
    LEGACY_KEY_ADDRESS,
    CLASSIC_ADDRESS,
    LE_IDENTITY_ADDRESS,
    LE_LEGACY_PSEUDO_ADDRESS
  };

  Device(ConfigCache* config, ConfigCache* memory_only_config, const hci::Address& key_address,
         ConfigKeyAddressType key_address_type);
  Device(ConfigCache* config, ConfigCache* memory_only_config, std::string section);

  // for move
  Device(Device&& other) noexcept = default;
  Device& operator=(Device&& other) noexcept = default;

  // for copy
  Device(const Device& other) noexcept = default;
  Device& operator=(const Device& other) noexcept = default;

  // operators
  bool operator==(const Device& other) const {
    return config_ == other.config_ && memory_only_config_ == other.memory_only_config_ &&
           section_ == other.section_;
  }
  bool operator!=(const Device& other) const { return !(*this == other); }
  bool operator<(const Device& other) const {
    if (config_ != other.config_) {
      return config_ < other.config_;
    }
    if (memory_only_config_ != other.memory_only_config_) {
      return memory_only_config_ < other.memory_only_config_;
    }
    return section_ < other.section_;
  }
  bool operator>(const Device& rhs) const { return rhs < *this; }
  bool operator<=(const Device& rhs) const { return !(*this > rhs); }
  bool operator>=(const Device& rhs) const { return !(*this < rhs); }

  // A newly created Device object may not be backed by any properties in the ConfigCache, where
  // Exists() will return false. As soon as a property value is added to the device. Exists() will
  // become true.
  bool Exists();

  // Remove device and all its properties from config and memory-only temp config
  MutationEntry RemoveFromConfig();
  // Remove device and all its properties from memory-only temp config, but keep items in normal
  // config
  MutationEntry RemoveFromTempConfig();

  // Only works when GetDeviceType() returns BR_EDR or DUAL, will crash otherwise
  // For first time use, please SetDeviceType() to the right value
  ClassicDevice Classic();

  // Only works when GetDeviceType() returns LE or DUAL, will crash otherwise
  // For first time use, please SetDeviceType() to the right value
  LeDevice Le();

  // For logging purpose only, you can't get a Device object from parsing a std::string
  std::string ToLogString() const;

  hci::Address GetAddress() const;

  // Property names that correspond to a link key used in Bluetooth Classic and LE device
  static const std::unordered_set<std::string_view> kLinkKeyProperties;

private:
  ConfigCache* config_;
  ConfigCache* memory_only_config_;
  std::string section_;
  friend std::hash<Device>;

public:
  // Macro generate getters, setters and removers
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(Name, std::string, BTIF_STORAGE_KEY_NAME);
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(ClassOfDevice, hci::ClassOfDevice,
                                          BTIF_STORAGE_KEY_DEV_CLASS);
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER_WITH_CUSTOM_SETTER(
          DeviceType, hci::DeviceType, BTIF_STORAGE_KEY_DEV_TYPE, {
            return static_cast<hci::DeviceType>(value |
                                                GetDeviceType().value_or(hci::DeviceType::UNKNOWN));
          });
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(ServiceUuids, std::vector<hci::Uuid>,
                                          BTIF_STORAGE_KEY_REMOTE_SERVICE);
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(ManufacturerCode, uint16_t, "Manufacturer");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(LmpVersion, uint8_t, "LmpVer");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(LmpSubVersion, uint16_t, "LmpSubVer");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(SdpDiManufacturer, uint16_t, "SdpDiManufacturer");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(SdpDiModel, uint16_t, "SdpDiModel");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(SdpDiHardwareVersion, uint16_t, "SdpDiHardwareVersion");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(SdpDiVendorIdSource, uint16_t, "SdpDiVendorIdSource");

  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(MetricsId, int, "MetricsId");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(PinLength, int, "PinLength");
  // unix timestamp in seconds from epoch
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(CreationUnixTimestamp, int, BTIF_STORAGE_KEY_DEV_CLASS);
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(IsAuthenticated, int, "IsAuthenticated");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(RequiresMitmProtection, int, "RequiresMitmProtection");
  GENERATE_PROPERTY_GETTER_SETTER_REMOVER(IsEncryptionRequired, int, "IsEncryptionRequired");
};

}  // namespace storage
}  // namespace bluetooth

namespace std {
template <>
struct hash<bluetooth::storage::Device> {
  std::size_t operator()(const bluetooth::storage::Device& val) const noexcept {
    std::size_t pointer_hash_1 = std::hash<bluetooth::storage::ConfigCache*>{}(val.config_);
    std::size_t pointer_hash_2 = std::hash<bluetooth::storage::ConfigCache*>{}(val.config_);
    std::size_t addr_hash = std::hash<std::string>{}(val.section_);
    return addr_hash ^ (pointer_hash_1 << 1) ^ (pointer_hash_2 << 2);
  }
};
}  // namespace std
