/*
 * 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 <chrono>
#include <cstdint>
#include <optional>
#include <ratio>
#include <vector>

#include "hci/address.h"
#include "hci/address_with_type.h"
#include "packets/hci_packets.h"

namespace rootcanal {

// Duration type for slots (increments of 625us).
using slots = std::chrono::duration<unsigned long long, std::ratio<625, 1000000>>;

// User defined literal for slots, e.g. `0x800_slots`
slots operator"" _slots(unsigned long long count);

using namespace bluetooth::hci;

// Advertising interface common to legacy and extended advertisers.
class Advertiser {
public:
  Advertiser() = default;
  ~Advertiser() = default;

  bool IsEnabled() const { return advertising_enable; }
  void Disable() { advertising_enable = false; }

  AddressWithType GetAdvertisingAddress() const { return advertising_address; }
  AddressWithType GetTargetAddress() const { return target_address; }

  // HCI properties.
  bool advertising_enable{false};
  AddressWithType advertising_address{Address::kEmpty, AddressType::PUBLIC_DEVICE_ADDRESS};
  AddressWithType target_address{Address::kEmpty, AddressType::PUBLIC_DEVICE_ADDRESS};

  // Time keeping.
  std::chrono::steady_clock::time_point next_event{};
  std::optional<std::chrono::steady_clock::time_point> timeout{};
};

// Implement the unique legacy advertising instance.
// For extended advertising check the ExtendedAdvertiser class.
class LegacyAdvertiser : public Advertiser {
public:
  LegacyAdvertiser() = default;
  ~LegacyAdvertiser() = default;

  bool IsScannable() const {
    return advertising_type != AdvertisingType::ADV_NONCONN_IND &&
           advertising_type != AdvertisingType::ADV_DIRECT_IND_HIGH &&
           advertising_type != AdvertisingType::ADV_DIRECT_IND_LOW;
  }

  bool IsConnectable() const {
    return advertising_type != AdvertisingType::ADV_NONCONN_IND &&
           advertising_type != AdvertisingType::ADV_SCAN_IND;
  }

  bool IsDirected() const {
    return advertising_type == AdvertisingType::ADV_DIRECT_IND_HIGH ||
           advertising_type == AdvertisingType::ADV_DIRECT_IND_LOW;
  }

  // Host configuration parameters. Gather the configuration from the
  // legacy advertising HCI commands. The initial configuration
  // matches the default values of the parameters of the HCI command
  // LE Set Advertising Parameters.
  slots advertising_interval{0x0800};
  AdvertisingType advertising_type{AdvertisingType::ADV_IND};
  OwnAddressType own_address_type{OwnAddressType::PUBLIC_DEVICE_ADDRESS};
  PeerAddressType peer_address_type{PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS};
  Address peer_address{};
  uint8_t advertising_channel_map{0x07};
  AdvertisingFilterPolicy advertising_filter_policy{AdvertisingFilterPolicy::ALL_DEVICES};
  std::vector<uint8_t> advertising_data{};
  std::vector<uint8_t> scan_response_data{};
};

// Implement a single extended advertising set.
// The configuration is set by the extended advertising commands;
// for the legacy advertiser check the LegacyAdvertiser class.
class ExtendedAdvertiser : public Advertiser {
public:
  ExtendedAdvertiser(uint8_t advertising_handle = 0) : advertising_handle(advertising_handle) {}
  ~ExtendedAdvertiser() = default;

  void Enable() {
    advertising_enable = true;
    periodic_advertising_enable_latch = periodic_advertising_enable;
    next_event = std::chrono::steady_clock::now();
  }

  void EnablePeriodic() {
    periodic_advertising_enable = true;
    periodic_advertising_enable_latch = advertising_enable;
    next_periodic_event = std::chrono::steady_clock::now();
  }

  void DisablePeriodic() {
    periodic_advertising_enable = false;
    periodic_advertising_enable_latch = false;
  }

  bool IsPeriodicEnabled() const { return periodic_advertising_enable_latch; }
  bool IsScannable() const { return advertising_event_properties.scannable_; }

  bool IsConnectable() const { return advertising_event_properties.connectable_; }

  bool IsDirected() const { return advertising_event_properties.directed_; }

  // Host configuration parameters. Gather the configuration from the
  // extended advertising HCI commands.
  uint8_t advertising_handle;
  AdvertisingEventProperties advertising_event_properties{};
  slots primary_advertising_interval{};
  uint8_t primary_advertising_channel_map{};
  OwnAddressType own_address_type{};
  PeerAddressType peer_address_type{};
  Address peer_address{};
  std::optional<Address> random_address{};
  AdvertisingFilterPolicy advertising_filter_policy{};
  uint8_t advertising_tx_power{};
  PrimaryPhyType primary_advertising_phy{};
  uint8_t secondary_max_skip{};
  SecondaryPhyType secondary_advertising_phy{};
  uint8_t advertising_sid{};
  bool scan_request_notification_enable{};
  std::vector<uint8_t> advertising_data{};
  std::vector<uint8_t> scan_response_data{};
  bool partial_advertising_data{false};
  bool partial_scan_response_data{false};

  // Periodic advertising configuration.
  // Note: the enable flag has a latch because of the semantic describe in the
  // specification:
  //
  // If the advertising set is not currently enabled, the periodic advertising
  // is not started until the advertising set is enabled. Once the advertising
  // set has been enabled, the Controller shall continue periodic advertising
  // until the Host issues an HCI_LE_Set_Periodic_Advertising_Enable command
  // with bit 0 of Enable set to 0 (periodic advertising is disabled).
  // Disabling the advertising set has no effect on the periodic advertising
  // once the advertising set has been enabled.
  //
  // Thus the enable latch is set when the advertising set is enabled and
  // periodic advertising is enabled, and cleared when periodic advertising
  // gets disabled.
  bool periodic_advertising_enable{false};
  bool periodic_advertising_enable_latch{false};
  slots periodic_advertising_interval{};
  std::vector<uint8_t> periodic_advertising_data{};
  bool partial_periodic_advertising_data{false};

  // Time keeping for periodic advertising.
  std::chrono::steady_clock::time_point next_periodic_event{};

  // Enabled state.
  uint8_t max_extended_advertising_events{0};
  uint8_t num_completed_extended_advertising_events{0};

  // Not implemented at the moment.
  bool constant_tone_extensions{false};

  // Compute the maximum advertising data payload size for the selected
  // advertising event properties. The advertising data is not present if
  // 0 is returned.
  static uint16_t GetMaxAdvertisingDataLength(const AdvertisingEventProperties& properties);

  // Compute the maximum scan response data payload size for the selected
  // advertising event properties. The scan response data is not present if
  // 0 is returned.
  static uint16_t GetMaxScanResponseDataLength(const AdvertisingEventProperties& properties);

  // Reconstitute the raw Advertising_Event_Properties bitmask.
  static uint16_t GetRawAdvertisingEventProperties(const AdvertisingEventProperties& properties);

  // Compute the maximum periodic advertising data payload size for the
  // selected periodic advertising interval.
  static uint16_t GetMaxPeriodicAdvertisingDataLength(slots periodic_advertising_interval);
};

}  // namespace rootcanal
