/*
 * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
 * www.ehima.com
 *
 * 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 <bluetooth/log.h>

#include <array>
#include <map>
#include <optional>
#include <ostream>
#include <vector>

#include "types/raw_address.h"

namespace bluetooth {
namespace le_audio {

enum class LeAudioHealthBasedAction {
  NONE = 0,
  DISABLE,
  CONSIDER_DISABLING,
  INACTIVATE_GROUP,
};

inline std::ostream& operator<<(std::ostream& os, const LeAudioHealthBasedAction action) {
  switch (action) {
    case LeAudioHealthBasedAction::NONE:
      os << "NONE";
      break;
    case LeAudioHealthBasedAction::DISABLE:
      os << "DISABLE";
      break;
    case LeAudioHealthBasedAction::CONSIDER_DISABLING:
      os << "CONSIDER_DISABLING";
      break;
    case LeAudioHealthBasedAction::INACTIVATE_GROUP:
      os << "INACTIVATE_GROUP";
      break;
    default:
      os << "UNKNOWN";
      break;
  }
  return os;
}

enum class ConnectionState { DISCONNECTED = 0, CONNECTING, CONNECTED, DISCONNECTING };

enum class GroupStatus {
  INACTIVE = 0,
  ACTIVE,
  TURNED_IDLE_DURING_CALL,
};

enum class GroupStreamStatus {
  IDLE = 0,
  STREAMING,
  RELEASING,
  SUSPENDING,
  SUSPENDED,
  CONFIGURED_AUTONOMOUS,
  CONFIGURED_BY_USER,
  DESTROYED,
};

enum class GroupNodeStatus {
  ADDED = 1,
  REMOVED,
};

enum class UnicastMonitorModeStatus {
  STREAMING_REQUESTED = 0,
  STREAMING,
  STREAMING_SUSPENDED,
  STREAMING_REQUESTED_NO_CONTEXT_VALIDATE,
};

typedef enum {
  LE_AUDIO_CODEC_INDEX_SOURCE_LC3 = 0,
  LE_AUDIO_CODEC_INDEX_SOURCE_INVALID = 1000 * 1000,
} btle_audio_codec_index_t;

typedef enum { QUALITY_STANDARD = 0, QUALITY_HIGH } btle_audio_quality_t;

typedef enum {
  LE_AUDIO_SAMPLE_RATE_INDEX_NONE = 0,
  LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ = 0x01 << 0,
  LE_AUDIO_SAMPLE_RATE_INDEX_11025HZ = 0x01 << 1,
  LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ = 0x01 << 2,
  LE_AUDIO_SAMPLE_RATE_INDEX_22050HZ = 0x01 << 3,
  LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ = 0x01 << 4,
  LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ = 0x01 << 5,
  LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ = 0x01 << 6,
  LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ = 0x01 << 7,
  LE_AUDIO_SAMPLE_RATE_INDEX_88200HZ = 0x01 << 8,
  LE_AUDIO_SAMPLE_RATE_INDEX_96000HZ = 0x01 << 9,
  LE_AUDIO_SAMPLE_RATE_INDEX_176400HZ = 0x01 << 10,
  LE_AUDIO_SAMPLE_RATE_INDEX_192000HZ = 0x01 << 11,
  LE_AUDIO_SAMPLE_RATE_INDEX_384000HZ = 0x01 << 12
} btle_audio_sample_rate_index_t;

typedef enum {
  LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE = 0,
  LE_AUDIO_BITS_PER_SAMPLE_INDEX_16 = 0x01 << 0,
  LE_AUDIO_BITS_PER_SAMPLE_INDEX_24 = 0x01 << 1,
  LE_AUDIO_BITS_PER_SAMPLE_INDEX_32 = 0x01 << 3
} btle_audio_bits_per_sample_index_t;

typedef enum {
  LE_AUDIO_CHANNEL_COUNT_INDEX_NONE = 0,
  LE_AUDIO_CHANNEL_COUNT_INDEX_1 = 0x01 << 0,
  LE_AUDIO_CHANNEL_COUNT_INDEX_2 = 0x01 << 1
} btle_audio_channel_count_index_t;

typedef enum {
  LE_AUDIO_FRAME_DURATION_INDEX_NONE = 0,
  LE_AUDIO_FRAME_DURATION_INDEX_7500US = 0x01 << 0,
  LE_AUDIO_FRAME_DURATION_INDEX_10000US = 0x01 << 1
} btle_audio_frame_duration_index_t;

typedef struct btle_audio_codec_config {
  btle_audio_codec_index_t codec_type = LE_AUDIO_CODEC_INDEX_SOURCE_INVALID;
  btle_audio_sample_rate_index_t sample_rate = LE_AUDIO_SAMPLE_RATE_INDEX_NONE;
  btle_audio_bits_per_sample_index_t bits_per_sample = LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE;
  btle_audio_channel_count_index_t channel_count = LE_AUDIO_CHANNEL_COUNT_INDEX_NONE;
  btle_audio_frame_duration_index_t frame_duration = LE_AUDIO_FRAME_DURATION_INDEX_NONE;
  uint16_t octets_per_frame = 0;
  int32_t codec_priority = 0;

  bool operator!=(const btle_audio_codec_config& other) const {
    if (codec_type != other.codec_type) {
      return true;
    }
    if (sample_rate != other.sample_rate) {
      return true;
    }
    if (bits_per_sample != other.bits_per_sample) {
      return true;
    }
    if (channel_count != other.channel_count) {
      return true;
    }
    if (frame_duration != other.frame_duration) {
      return true;
    }
    if (octets_per_frame != other.octets_per_frame) {
      return true;
    }
    if (codec_priority != other.codec_priority) {
      return true;
    }
    return false;
  }
  bool operator==(const btle_audio_codec_config& other) const { return !(*this != other); }

  std::string ToString() const {
    std::string codec_name_str;
    std::string sample_rate_str;
    std::string bits_per_sample_str;
    std::string channel_count_str;
    std::string frame_duration_str;
    std::string octets_per_frame_str;
    std::string codec_priority_str;

    switch (codec_type) {
      case LE_AUDIO_CODEC_INDEX_SOURCE_LC3:
        codec_name_str = "LC3";
        break;
      default:
        codec_name_str = "Unknown LE codec " + std::to_string(codec_type);
        break;
    }

    switch (sample_rate) {
      case LE_AUDIO_SAMPLE_RATE_INDEX_NONE:
        sample_rate_str = "none";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ:
        sample_rate_str = "8000 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_11025HZ:
        sample_rate_str = "11025 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ:
        sample_rate_str = "16000 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_22050HZ:
        sample_rate_str = "22050 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ:
        sample_rate_str = "24000 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ:
        sample_rate_str = "32000 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ:
        sample_rate_str = "44100 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ:
        sample_rate_str = "48000 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_88200HZ:
        sample_rate_str = "88200 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_96000HZ:
        sample_rate_str = "96000 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_176400HZ:
        sample_rate_str = "176400 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_192000HZ:
        sample_rate_str = "192000 hz";
        break;
      case LE_AUDIO_SAMPLE_RATE_INDEX_384000HZ:
        sample_rate_str = "384000 hz";
        break;
      default:
        sample_rate_str = "Unknown LE sample rate " + std::to_string(sample_rate);
        break;
    }

    switch (bits_per_sample) {
      case LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE:
        bits_per_sample_str = "none";
        break;
      case LE_AUDIO_BITS_PER_SAMPLE_INDEX_16:
        bits_per_sample_str = "16";
        break;
      case LE_AUDIO_BITS_PER_SAMPLE_INDEX_24:
        bits_per_sample_str = "24";
        break;
      case LE_AUDIO_BITS_PER_SAMPLE_INDEX_32:
        bits_per_sample_str = "32";
        break;
      default:
        bits_per_sample_str = "Unknown LE bits per sample " + std::to_string(bits_per_sample);
        break;
    }

    switch (channel_count) {
      case LE_AUDIO_CHANNEL_COUNT_INDEX_NONE:
        channel_count_str = "none";
        break;
      case LE_AUDIO_CHANNEL_COUNT_INDEX_1:
        channel_count_str = "1";
        break;
      case LE_AUDIO_CHANNEL_COUNT_INDEX_2:
        channel_count_str = "2";
        break;
      default:
        channel_count_str = "Unknown LE channel count " + std::to_string(channel_count);
        break;
    }

    switch (frame_duration) {
      case LE_AUDIO_FRAME_DURATION_INDEX_NONE:
        frame_duration_str = "none";
        break;
      case LE_AUDIO_FRAME_DURATION_INDEX_7500US:
        frame_duration_str = "7500 us";
        break;
      case LE_AUDIO_FRAME_DURATION_INDEX_10000US:
        frame_duration_str = "10000 us";
        break;
      default:
        frame_duration_str = "Unknown LE frame duration " + std::to_string(frame_duration);
        break;
    }

    if (octets_per_frame < 0) {
      octets_per_frame_str = "Unknown LE octets per frame " + std::to_string(octets_per_frame);
    } else {
      octets_per_frame_str = std::to_string(octets_per_frame);
    }

    if (codec_priority < -1) {
      codec_priority_str = "Unknown LE codec priority " + std::to_string(codec_priority);
    } else {
      codec_priority_str = std::to_string(codec_priority);
    }

    return "codec: " + codec_name_str + ", sample rate: " + sample_rate_str +
           ", bits per sample: " + bits_per_sample_str + ", channel count: " + channel_count_str +
           ", frame duration: " + frame_duration_str +
           ", octets per frame: " + octets_per_frame_str + ", codec priroty: " + codec_priority_str;
  }
} btle_audio_codec_config_t;

class LeAudioClientCallbacks {
public:
  virtual ~LeAudioClientCallbacks() = default;

  /* Callback to notify Java that stack is ready */
  virtual void OnInitialized(void) = 0;

  /** Callback for profile connection state change */
  virtual void OnConnectionState(ConnectionState state, const RawAddress& address) = 0;

  /* Callback with group status update */
  virtual void OnGroupStatus(int group_id, GroupStatus group_status) = 0;

  /* Callback with node status update */
  virtual void OnGroupNodeStatus(const RawAddress& bd_addr, int group_id,
                                 GroupNodeStatus node_status) = 0;
  /* Callback for newly recognized or reconfigured existing le audio group */
  virtual void OnAudioConf(uint8_t direction, int group_id, uint32_t snk_audio_location,
                           uint32_t src_audio_location, uint16_t avail_cont) = 0;
  /* Callback for sink audio location recognized */
  virtual void OnSinkAudioLocationAvailable(const RawAddress& address,
                                            uint32_t snk_audio_locations) = 0;
  /* Callback with local codec capabilities */
  virtual void OnAudioLocalCodecCapabilities(
          std::vector<btle_audio_codec_config_t> local_input_capa_codec_conf,
          std::vector<btle_audio_codec_config_t> local_output_capa_codec_conf) = 0;
  /* Callback with current group codec configurations. Should change when PACs
   * changes */
  virtual void OnAudioGroupCurrentCodecConf(int group_id,
                                            btle_audio_codec_config_t input_codec_conf,
                                            btle_audio_codec_config_t output_codec_conf) = 0;
  /* Callback with selectable group codec configurations. Should change when
   * context changes */
  virtual void OnAudioGroupSelectableCodecConf(
          int group_id, std::vector<btle_audio_codec_config_t> input_selectable_codec_conf,
          std::vector<btle_audio_codec_config_t> output_selectable_codec_conf) = 0;
  virtual void OnHealthBasedRecommendationAction(const RawAddress& address,
                                                 LeAudioHealthBasedAction action) = 0;
  virtual void OnHealthBasedGroupRecommendationAction(int group_id,
                                                      LeAudioHealthBasedAction action) = 0;

  virtual void OnUnicastMonitorModeStatus(uint8_t direction, UnicastMonitorModeStatus status) = 0;

  /* Callback with group stream status update */
  virtual void OnGroupStreamStatus(int group_id, GroupStreamStatus group_stream_status) = 0;
};

class LeAudioClientInterface {
public:
  virtual ~LeAudioClientInterface() = default;

  /* Register the LeAudio callbacks */
  virtual void Initialize(LeAudioClientCallbacks* callbacks,
                          const std::vector<btle_audio_codec_config_t>& offloading_preference) = 0;

  /** Connect to LEAudio */
  virtual void Connect(const RawAddress& address) = 0;

  /** Disconnect from LEAudio */
  virtual void Disconnect(const RawAddress& address) = 0;

  /* Set enable/disable State for the LeAudio device */
  virtual void SetEnableState(const RawAddress& address, bool enabled) = 0;

  /* Cleanup the LeAudio */
  virtual void Cleanup(void) = 0;

  /* Called when LeAudio is unbonded. */
  virtual void RemoveDevice(const RawAddress& address) = 0;

  /* Attach le audio node to group */
  virtual void GroupAddNode(int group_id, const RawAddress& addr) = 0;

  /* Detach le audio node from a group */
  virtual void GroupRemoveNode(int group_id, const RawAddress& addr) = 0;

  /* Set active le audio group */
  virtual void GroupSetActive(int group_id) = 0;

  /* Set codec config preference */
  virtual void SetCodecConfigPreference(int group_id, btle_audio_codec_config_t input_codec_config,
                                        btle_audio_codec_config_t output_codec_config) = 0;

  /* Set Ccid for context type */
  virtual void SetCcidInformation(int ccid, int context_type) = 0;

  /* Set In call flag */
  virtual void SetInCall(bool in_call) = 0;

  /* Set Sink listening mode flag */
  virtual void SetUnicastMonitorMode(uint8_t direction, bool enable) = 0;

  /* Sends a preferred audio profiles change */
  virtual void SendAudioProfilePreferences(int group_id, bool is_output_preference_le_audio,
                                           bool is_duplex_preference_le_audio) = 0;

  /* Set allowed to stream context */
  virtual void SetGroupAllowedContextMask(int group_id, int sink_context_types,
                                          int source_context_types) = 0;
};

/* Represents the broadcast source state. */
enum class BroadcastState {
  STOPPED = 0,
  CONFIGURING,
  CONFIGURED,
  ENABLING,
  DISABLING,
  STOPPING,
  STREAMING,
};

using BroadcastId = uint32_t;
static constexpr BroadcastId kBroadcastIdInvalid = 0x00000000;
using BroadcastCode = std::array<uint8_t, 16>;

/* Content Metadata LTV Types */
constexpr uint8_t kLeAudioMetadataTypePreferredAudioContext = 0x01;
constexpr uint8_t kLeAudioMetadataTypeStreamingAudioContext = 0x02;
constexpr uint8_t kLeAudioMetadataTypeProgramInfo = 0x03;
constexpr uint8_t kLeAudioMetadataTypeLanguage = 0x04;
constexpr uint8_t kLeAudioMetadataTypeCcidList = 0x05;

/* Codec specific LTV Types */
constexpr uint8_t kLeAudioLtvTypeSamplingFreq = 0x01;
constexpr uint8_t kLeAudioLtvTypeFrameDuration = 0x02;
constexpr uint8_t kLeAudioLtvTypeAudioChannelAllocation = 0x03;
constexpr uint8_t kLeAudioLtvTypeOctetsPerCodecFrame = 0x04;
constexpr uint8_t kLeAudioLtvTypeCodecFrameBlocksPerSdu = 0x05;

/* Audio quality configuration in public broadcast announcement */
constexpr uint8_t kLeAudioQualityStandard = 0x1 << 1;
constexpr uint8_t kLeAudioQualityHigh = 0x1 << 2;

/* Unknown RSSI value 0x7F - 127 */
constexpr uint8_t kLeAudioSourceRssiUnknown = 0x7F;

struct BasicAudioAnnouncementCodecConfig {
  /* 5 octets for the Codec ID */
  uint8_t codec_id;
  uint16_t vendor_company_id;
  uint16_t vendor_codec_id;

  /* Codec params - series of LTV formatted triplets */
  std::map<uint8_t, std::vector<uint8_t>> codec_specific_params;
  std::optional<std::vector<uint8_t>> vendor_codec_specific_params;
};

struct BasicAudioAnnouncementBisConfig {
  std::map<uint8_t, std::vector<uint8_t>> codec_specific_params;
  std::optional<std::vector<uint8_t>> vendor_codec_specific_params;

  uint8_t bis_index;
};

struct BasicAudioAnnouncementSubgroup {
  /* Subgroup specific codec configuration and metadata */
  BasicAudioAnnouncementCodecConfig codec_config;
  // Content metadata
  std::map<uint8_t, std::vector<uint8_t>> metadata;
  // Broadcast channel configuration
  std::vector<BasicAudioAnnouncementBisConfig> bis_configs;
};

struct BasicAudioAnnouncementData {
  /* Announcement Header fields */
  uint32_t presentation_delay_us;

  /* Subgroup specific configurations */
  std::vector<BasicAudioAnnouncementSubgroup> subgroup_configs;
};

struct PublicBroadcastAnnouncementData {
  // Public Broadcast Announcement features bitmap
  uint8_t features;
  // Metadata
  std::map<uint8_t, std::vector<uint8_t>> metadata;
};

struct BroadcastMetadata {
  bool is_public;
  uint16_t pa_interval;
  RawAddress addr;
  uint8_t addr_type;
  uint8_t adv_sid;

  BroadcastId broadcast_id;
  std::string broadcast_name;
  std::optional<BroadcastCode> broadcast_code;

  PublicBroadcastAnnouncementData public_announcement;
  /* Presentation delay and subgroup configurations */
  BasicAudioAnnouncementData basic_audio_announcement;
};

class LeAudioBroadcasterCallbacks {
public:
  virtual ~LeAudioBroadcasterCallbacks() = default;
  /* Callback for the newly created broadcast event. */
  virtual void OnBroadcastCreated(uint32_t broadcast_id, bool success) = 0;

  /* Callback for the destroyed broadcast event. */
  virtual void OnBroadcastDestroyed(uint32_t broadcast_id) = 0;
  /* Callback for the broadcast source state event. */
  virtual void OnBroadcastStateChanged(uint32_t broadcast_id, BroadcastState state) = 0;
  /* Callback for the broadcast metadata change. */
  virtual void OnBroadcastMetadataChanged(uint32_t broadcast_id,
                                          const BroadcastMetadata& broadcast_metadata) = 0;
  /* Callback for broadcast audio session create event. */
  virtual void OnBroadcastAudioSessionCreated(bool success) = 0;
};

class LeAudioBroadcasterInterface {
public:
  virtual ~LeAudioBroadcasterInterface() = default;
  /* Register the LeAudio Broadcaster callbacks */
  virtual void Initialize(LeAudioBroadcasterCallbacks* callbacks) = 0;
  /* Stop the LeAudio Broadcaster and all active broadcasts */
  virtual void Stop(void) = 0;
  /* Cleanup the LeAudio Broadcaster */
  virtual void Cleanup(void) = 0;
  /* Create Broadcast instance */
  virtual void CreateBroadcast(bool is_public, std::string broadcast_name,
                               std::optional<BroadcastCode> broadcast_code,
                               std::vector<uint8_t> public_metadata,
                               std::vector<uint8_t> subgroup_quality,
                               std::vector<std::vector<uint8_t>> subgroup_metadata) = 0;
  /* Update the ongoing Broadcast metadata */
  virtual void UpdateMetadata(uint32_t broadcast_id, std::string broadcast_name,
                              std::vector<uint8_t> public_metadata,
                              std::vector<std::vector<uint8_t>> subgroup_metadata) = 0;

  /* Start the existing Broadcast stream */
  virtual void StartBroadcast(uint32_t broadcast_id) = 0;
  /* Pause the ongoing Broadcast stream */
  virtual void PauseBroadcast(uint32_t broadcast_id) = 0;
  /* Stop the Broadcast (no stream, no periodic advertisements */
  virtual void StopBroadcast(uint32_t broadcast_id) = 0;
  /* Destroy the existing Broadcast instance */
  virtual void DestroyBroadcast(uint32_t broadcast_id) = 0;
  /* Get Broadcast Metadata */
  virtual void GetBroadcastMetadata(uint32_t broadcast_id) = 0;
};

} /* namespace le_audio */
} /* namespace bluetooth */

namespace std {
template <>
struct formatter<bluetooth::le_audio::btle_audio_codec_index_t>
    : enum_formatter<bluetooth::le_audio::btle_audio_codec_index_t> {};
template <>
struct formatter<bluetooth::le_audio::btle_audio_sample_rate_index_t>
    : enum_formatter<bluetooth::le_audio::btle_audio_sample_rate_index_t> {};
template <>
struct formatter<bluetooth::le_audio::btle_audio_bits_per_sample_index_t>
    : enum_formatter<bluetooth::le_audio::btle_audio_bits_per_sample_index_t> {};
template <>
struct formatter<bluetooth::le_audio::btle_audio_channel_count_index_t>
    : enum_formatter<bluetooth::le_audio::btle_audio_channel_count_index_t> {};
template <>
struct formatter<bluetooth::le_audio::btle_audio_frame_duration_index_t>
    : enum_formatter<bluetooth::le_audio::btle_audio_frame_duration_index_t> {};
template <>
struct formatter<bluetooth::le_audio::GroupStreamStatus>
    : enum_formatter<bluetooth::le_audio::GroupStreamStatus> {};
}  // namespace std
