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

#include "pw_bluetooth_sapphire/internal/host/common/assert.h"

namespace bt {

namespace {

DeviceClass::ServiceClass bit_no_to_service_class(uint8_t bit_no) {
  BT_DEBUG_ASSERT(bit_no >= 13);
  BT_DEBUG_ASSERT(bit_no < 24);
  switch (bit_no) {
    case 13:
      return DeviceClass::ServiceClass::kLimitedDiscoverableMode;
    case 14:
      return DeviceClass::ServiceClass::kLEAudio;
    case 15:
      return DeviceClass::ServiceClass::kReserved;
    case 16:
      return DeviceClass::ServiceClass::kPositioning;
    case 17:
      return DeviceClass::ServiceClass::kNetworking;
    case 18:
      return DeviceClass::ServiceClass::kRendering;
    case 19:
      return DeviceClass::ServiceClass::kCapturing;
    case 20:
      return DeviceClass::ServiceClass::kObjectTransfer;
    case 21:
      return DeviceClass::ServiceClass::kAudio;
    case 22:
      return DeviceClass::ServiceClass::kTelephony;
    case 23:
      return DeviceClass::ServiceClass::kInformation;
  };
  // Should be unreachable.
  return DeviceClass::ServiceClass::kInformation;
}

std::string service_class_to_string(const DeviceClass::ServiceClass& serv) {
  switch (serv) {
    case DeviceClass::ServiceClass::kLimitedDiscoverableMode:
      return "Limited Discoverable Mode";
    case DeviceClass::ServiceClass::kLEAudio:
      return "LE Audio";
    case DeviceClass::ServiceClass::kReserved:
      return "Reserved";
    case DeviceClass::ServiceClass::kPositioning:
      return "Positioning";
    case DeviceClass::ServiceClass::kNetworking:
      return "Networking";
    case DeviceClass::ServiceClass::kRendering:
      return "Rendering";
    case DeviceClass::ServiceClass::kCapturing:
      return "Capturing";
    case DeviceClass::ServiceClass::kObjectTransfer:
      return "Object Transfer";
    case DeviceClass::ServiceClass::kAudio:
      return "Audio";
    case DeviceClass::ServiceClass::kTelephony:
      return "Telephony";
    case DeviceClass::ServiceClass::kInformation:
      return "Information";
  }
}

}  // namespace

DeviceClass::DeviceClass() : DeviceClass(MajorClass::kUnspecified) {}

DeviceClass::DeviceClass(MajorClass major_class)
    : bytes_{0x00, static_cast<uint8_t>(major_class), 0x00} {}

DeviceClass::DeviceClass(std::initializer_list<uint8_t> bytes) {
  BT_DEBUG_ASSERT(bytes.size() == bytes_.size());
  std::copy(bytes.begin(), bytes.end(), bytes_.begin());
}

DeviceClass::DeviceClass(uint32_t value) {
  BT_DEBUG_ASSERT(value < 1 << 24);  // field should only populate 24 bits
  bytes_ = {
      static_cast<uint8_t>((value >> 0) & 0xFF),
      static_cast<uint8_t>((value >> 8) & 0xFF),
      static_cast<uint8_t>((value >> 16) & 0xFF),
  };
}

uint32_t DeviceClass::to_int() const {
  uint32_t out = 0;
  out |= bytes_[0];
  out |= bytes_[1] << CHAR_BIT;
  out |= bytes_[2] << 2 * CHAR_BIT;
  return out;
}

void DeviceClass::SetServiceClasses(
    const std::unordered_set<ServiceClass>& classes) {
  for (const auto& c : classes) {
    uint8_t bit = static_cast<uint8_t>(c);
    if (bit >= 16) {
      bytes_[2] |= 0x01 << (bit - 16);
    } else if (bit >= 8) {
      bytes_[1] |= 0x01 << (bit - 8);
    }
  }
}

std::unordered_set<DeviceClass::ServiceClass> DeviceClass::GetServiceClasses()
    const {
  std::unordered_set<ServiceClass> classes;
  for (uint8_t bit_no = 13; bit_no < 16; bit_no++) {
    if (bytes_[1] & (0x01 << (bit_no - 8))) {
      classes.emplace(bit_no_to_service_class(bit_no));
    }
  }
  for (uint8_t bit_no = 16; bit_no < 24; bit_no++) {
    if (bytes_[2] & (0x01 << (bit_no - 16))) {
      classes.emplace(bit_no_to_service_class(bit_no));
    }
  }
  return classes;
}

std::string DeviceClass::ToString() const {
  std::string service_desc;
  auto classes = GetServiceClasses();
  if (!classes.empty()) {
    auto it = classes.begin();
    service_desc = " (" + service_class_to_string(*it);
    ++it;
    for (; it != classes.end(); ++it) {
      service_desc += ", " + service_class_to_string(*it);
    }
    service_desc = service_desc + ")";
  }
  switch (major_class()) {
    case MajorClass::kMiscellaneous:
      return "Miscellaneous" + service_desc;
    case MajorClass::kComputer:
      return "Computer" + service_desc;
    case MajorClass::kPhone:
      return "Phone" + service_desc;
    case MajorClass::kLAN:
      return "LAN" + service_desc;
    case MajorClass::kAudioVideo:
      return "A/V" + service_desc;
    case MajorClass::kPeripheral:
      return "Peripheral" + service_desc;
    case MajorClass::kImaging:
      return "Imaging" + service_desc;
    case MajorClass::kWearable:
      return "Wearable" + service_desc;
    case MajorClass::kToy:
      return "Toy" + service_desc;
    case MajorClass::kHealth:
      return "Health Device" + service_desc;
    case MajorClass::kUnspecified:
      return "Unspecified" + service_desc;
  };

  return "(unknown)";
}

}  // namespace bt
