/*
 *  Copyright (c) 2022 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.
 *
 */

#include <bluetooth/log.h>
#include <stdio.h>

#include <algorithm>
#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "audio_hal_client/audio_hal_client.h"
#include "audio_set_configurations_generated.h"
#include "audio_set_scenarios_generated.h"
#include "btm_iso_api_types.h"
#include "flatbuffers/buffer.h"
#include "flatbuffers/idl.h"
#include "flatbuffers/util.h"
#include "flatbuffers/vector.h"
#include "le_audio/le_audio_types.h"
#include "le_audio_set_configuration_provider.h"

using bluetooth::le_audio::set_configurations::AseConfiguration;
using bluetooth::le_audio::set_configurations::AudioSetConfiguration;
using bluetooth::le_audio::set_configurations::AudioSetConfigurations;
using bluetooth::le_audio::set_configurations::CodecConfigSetting;
using bluetooth::le_audio::set_configurations::QosConfigSetting;
using bluetooth::le_audio::types::LeAudioContextType;

namespace bluetooth::le_audio {

#ifdef __ANDROID__
static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
        kLeAudioSetConfigs = {{"/apex/com.android.btservices/etc/bluetooth/le_audio/"
                               "audio_set_configurations.bfbs",
                               "/apex/com.android.btservices/etc/bluetooth/le_audio/"
                               "audio_set_configurations.json"}};
static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
        kLeAudioSetScenarios = {{"/apex/com.android.btservices/etc/bluetooth/"
                                 "le_audio/audio_set_scenarios.bfbs",
                                 "/apex/com.android.btservices/etc/bluetooth/"
                                 "le_audio/audio_set_scenarios.json"}};
#elif defined(TARGET_FLOSS)
static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
        kLeAudioSetConfigs = {{"/etc/bluetooth/le_audio/audio_set_configurations.bfbs",
                               "/etc/bluetooth/le_audio/audio_set_configurations.json"}};
static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
        kLeAudioSetScenarios = {{"/etc/bluetooth/le_audio/audio_set_scenarios.bfbs",
                                 "/etc/bluetooth/le_audio/audio_set_scenarios.json"}};
#else
static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
        kLeAudioSetConfigs = {{"audio_set_configurations.bfbs", "audio_set_configurations.json"}};
static const std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
        kLeAudioSetScenarios = {{"audio_set_scenarios.bfbs", "audio_set_scenarios.json"}};
#endif

/** Provides a set configurations for the given context type */
struct AudioSetConfigurationProviderJson {
  static constexpr auto kDefaultScenario = "Media";

  AudioSetConfigurationProviderJson(types::CodecLocation location) {
    log::assert_that(LoadContent(kLeAudioSetConfigs, kLeAudioSetScenarios, location),
                     ": Unable to load le audio set configuration files.");
  }

  /* Use the same scenario configurations for different contexts to avoid
   * internal reconfiguration and handover that produces time gap. When using
   * the same scenario for different contexts, quality and configuration remains
   * the same while changing to same scenario based context type.
   */
  static auto ScenarioToContextTypes(const std::string& scenario) {
    static const std::multimap<std::string, ::bluetooth::le_audio::types::LeAudioContextType>
            scenarios = {
                    {"Media", types::LeAudioContextType::ALERTS},
                    {"Media", types::LeAudioContextType::INSTRUCTIONAL},
                    {"Media", types::LeAudioContextType::NOTIFICATIONS},
                    {"Media", types::LeAudioContextType::EMERGENCYALARM},
                    {"Media", types::LeAudioContextType::UNSPECIFIED},
                    {"Media", types::LeAudioContextType::MEDIA},
                    {"Conversational", types::LeAudioContextType::RINGTONE},
                    {"Conversational", types::LeAudioContextType::CONVERSATIONAL},
                    {"Live", types::LeAudioContextType::LIVE},
                    {"Game", types::LeAudioContextType::GAME},
                    {"VoiceAssistants", types::LeAudioContextType::VOICEASSISTANTS},
            };
    return scenarios.equal_range(scenario);
  }

  static std::string ContextTypeToScenario(
          ::bluetooth::le_audio::types::LeAudioContextType context_type) {
    switch (context_type) {
      case types::LeAudioContextType::ALERTS:
      case types::LeAudioContextType::INSTRUCTIONAL:
      case types::LeAudioContextType::NOTIFICATIONS:
      case types::LeAudioContextType::EMERGENCYALARM:
      case types::LeAudioContextType::UNSPECIFIED:
      case types::LeAudioContextType::SOUNDEFFECTS:
      case types::LeAudioContextType::MEDIA:
        return "Media";
      case types::LeAudioContextType::RINGTONE:
      case types::LeAudioContextType::CONVERSATIONAL:
        return "Conversational";
      case types::LeAudioContextType::LIVE:
        return "Live";
      case types::LeAudioContextType::GAME:
        return "Game";
      case types::LeAudioContextType::VOICEASSISTANTS:
        return "VoiceAssistants";
      default:
        return kDefaultScenario;
    }
  }

  const AudioSetConfigurations* GetConfigurationsByContextType(
          LeAudioContextType context_type) const {
    if (context_configurations_.count(context_type)) {
      return &context_configurations_.at(context_type);
    }

    log::warn(": No predefined scenario for the context {} was found.", (int)context_type);

    auto [it_begin, it_end] = ScenarioToContextTypes(kDefaultScenario);
    if (it_begin != it_end) {
      log::warn(": Using '{}' scenario by default.", kDefaultScenario);
      return &context_configurations_.at(it_begin->second);
    }

    log::error(
            ": No valid configuration for the default '{}' scenario, or no audio "
            "set configurations loaded at all.",
            kDefaultScenario);
    return nullptr;
  }

private:
  /* Codec configurations */
  std::map<std::string, const AudioSetConfiguration> configurations_;

  /* Maps of context types to a set of configuration structs */
  std::map<::bluetooth::le_audio::types::LeAudioContextType, AudioSetConfigurations>
          context_configurations_;

  static CodecConfigSetting CodecConfigSettingFromFlat(
          const fbs::le_audio::CodecId* flat_codec_id,
          const flatbuffers::Vector<flatbuffers::Offset<fbs::le_audio::CodecSpecificConfiguration>>*
                  flat_codec_specific_params) {
    CodecConfigSetting codec;

    /* Cache the bluetooth::le_audio::types::CodecId type value */
    codec.id = types::LeAudioCodecId({
            .coding_format = flat_codec_id->coding_format(),
            .vendor_company_id = flat_codec_id->vendor_company_id(),
            .vendor_codec_id = flat_codec_id->vendor_codec_id(),
    });

    /* Cache all the codec specific parameters */
    for (auto const& param : *flat_codec_specific_params) {
      auto const value = param->compound_value()->value();
      codec.params.Add(param->type(),
                       std::vector<uint8_t>(value->data(), value->data() + value->size()));
    }
    return codec;
  }

  void SetConfigurationFromFlatSubconfig(
          const fbs::le_audio::AudioSetSubConfiguration* flat_subconfig, QosConfigSetting qos,
          std::vector<AseConfiguration>& subconfigs, types::CodecLocation location) {
    auto codec_config = CodecConfigSettingFromFlat(flat_subconfig->codec_id(),
                                                   flat_subconfig->codec_configuration());

    // Fill in the remaining params
    codec_config.channel_count_per_iso_stream = flat_subconfig->ase_channel_cnt();

    auto config = AseConfiguration(codec_config, qos);

    // Note that these parameters are set here since for now, we are using the
    // common configuration source for all the codec locations.
    switch (location) {
      case types::CodecLocation::ADSP:
        config.data_path_configuration.dataPathId =
                bluetooth::hci::iso_manager::kIsoDataPathPlatformDefault;
        config.data_path_configuration.dataPathConfig = {};
        config.data_path_configuration.isoDataPathConfig.isTransparent = true;
        config.data_path_configuration.isoDataPathConfig.controllerDelayUs = 0;
        config.data_path_configuration.isoDataPathConfig.codecId = {
                .coding_format = bluetooth::hci::kIsoCodingFormatTransparent,
                .vendor_company_id = 0,
                .vendor_codec_id = 0};
        config.data_path_configuration.isoDataPathConfig.configuration = {};
        break;
      case types::CodecLocation::HOST:
        config.data_path_configuration.dataPathId = bluetooth::hci::iso_manager::kIsoDataPathHci;
        config.data_path_configuration.dataPathConfig = {};
        config.data_path_configuration.isoDataPathConfig.isTransparent = true;
        config.data_path_configuration.isoDataPathConfig.codecId = {
                .coding_format = bluetooth::hci::kIsoCodingFormatTransparent,
                .vendor_company_id = 0,
                .vendor_codec_id = 0};
        config.data_path_configuration.isoDataPathConfig.controllerDelayUs = 0;
        config.data_path_configuration.isoDataPathConfig.configuration = {};
        break;
      case types::CodecLocation::CONTROLLER:
        config.data_path_configuration.dataPathId =
                bluetooth::hci::iso_manager::kIsoDataPathPlatformDefault;
        config.data_path_configuration.dataPathConfig = {};
        config.data_path_configuration.isoDataPathConfig.isTransparent = false;
        // Note: The data path codecId matches the used codec, but for now there
        //       is no support for the custom path configuration data buffer.
        config.data_path_configuration.isoDataPathConfig.codecId = codec_config.id;
        config.data_path_configuration.isoDataPathConfig.controllerDelayUs = 0;
        config.data_path_configuration.isoDataPathConfig.configuration = {};
        break;
    }

    // Store each ASE configuration
    for (auto i = flat_subconfig->ase_cnt(); i; --i) {
      subconfigs.push_back(std::move(config));
    }
  }

  static uint8_t ValidateTargetLatency(int flat_target_latency) {
    auto target_latency_int = static_cast<int>(flat_target_latency);

    bool valid_target_latency = (target_latency_int >= (int)types::kTargetLatencyLower &&
                                 target_latency_int <= (int)types::kTargetLatencyHigherReliability);

    return valid_target_latency ? static_cast<uint8_t>(target_latency_int)
                                : types::kTargetLatencyBalancedLatencyReliability;
  }

  AudioSetConfiguration AudioSetConfigurationFromFlat(
          const fbs::le_audio::AudioSetConfiguration* flat_cfg,
          std::vector<const fbs::le_audio::CodecConfiguration*>* codec_cfgs,
          std::vector<const fbs::le_audio::QosConfiguration*>* qos_cfgs,
          types::CodecLocation location) {
    log::assert_that(flat_cfg != nullptr, "flat_cfg cannot be null");
    std::string codec_config_key = flat_cfg->codec_config_name()->str();
    auto* qos_config_key_array = flat_cfg->qos_config_name();

    constexpr std::string_view default_qos = "QoS_Config_Balanced_Reliability";

    std::string qos_sink_key(default_qos);
    std::string qos_source_key(default_qos);

    /* We expect maximum two QoS settings. First for Sink and second for Source
     */
    if (qos_config_key_array->size() > 0) {
      qos_sink_key = qos_config_key_array->Get(0)->str();
      if (qos_config_key_array->size() > 1) {
        qos_source_key = qos_config_key_array->Get(1)->str();
      } else {
        qos_source_key = qos_sink_key;
      }
    }

    log::info("Audio set config {}: codec config {}, qos_sink {}, qos_source {}",
              flat_cfg->name()->c_str(), codec_config_key, qos_sink_key, qos_source_key);

    const fbs::le_audio::QosConfiguration* qos_sink_cfg = nullptr;
    for (auto i = qos_cfgs->begin(); i != qos_cfgs->end(); ++i) {
      if ((*i)->name()->str() == qos_sink_key) {
        qos_sink_cfg = *i;
        break;
      }
    }

    const fbs::le_audio::QosConfiguration* qos_source_cfg = nullptr;
    for (auto i = qos_cfgs->begin(); i != qos_cfgs->end(); ++i) {
      if ((*i)->name()->str() == qos_source_key) {
        qos_source_cfg = *i;
        break;
      }
    }

    types::BidirectionalPair<QosConfigSetting> qos;

    if (qos_sink_cfg != nullptr) {
      qos.sink.target_latency = ValidateTargetLatency(qos_sink_cfg->target_latency());
      qos.sink.retransmission_number = qos_sink_cfg->retransmission_number();
      qos.sink.max_transport_latency = qos_sink_cfg->max_transport_latency();
    } else {
      log::error("No qos config matching key {} found", qos_sink_key);
    }

    if (qos_source_cfg != nullptr) {
      qos.source.target_latency = ValidateTargetLatency(qos_source_cfg->target_latency());
      qos.source.retransmission_number = qos_source_cfg->retransmission_number();
      qos.source.max_transport_latency = qos_source_cfg->max_transport_latency();
    } else {
      log::error("No qos config matching key {} found", qos_source_key);
    }

    const fbs::le_audio::CodecConfiguration* codec_cfg = nullptr;
    for (auto i = codec_cfgs->begin(); i != codec_cfgs->end(); ++i) {
      if ((*i)->name()->str() == codec_config_key) {
        codec_cfg = *i;
        break;
      }
    }

    types::BidirectionalPair<std::vector<AseConfiguration>> subconfigs;
    if (codec_cfg != nullptr && codec_cfg->subconfigurations()) {
      /* Load subconfigurations */
      for (auto subconfig : *codec_cfg->subconfigurations()) {
        auto direction = subconfig->direction();
        processSubconfig(*subconfig, qos.get(direction), subconfigs.get(direction), location);
      }
    } else {
      if (codec_cfg == nullptr) {
        log::error("No codec config matching key {} found", codec_config_key);
      } else {
        log::error("Configuration '{}' has no valid subconfigurations.", flat_cfg->name()->c_str());
      }
    }

    return {
            .name = flat_cfg->name()->c_str(),
            .packing = bluetooth::hci::kIsoCigPackingSequential,
            .confs = std::move(subconfigs),
    };
  }

  void processSubconfig(const fbs::le_audio::AudioSetSubConfiguration& subconfig,
                        const QosConfigSetting& qos_setting,
                        std::vector<AseConfiguration>& subconfigs, types::CodecLocation location) {
    SetConfigurationFromFlatSubconfig(&subconfig, qos_setting, subconfigs, location);

    // Recalculate some qos params based on the Core Codec Configuration
    for (auto& subconfig : subconfigs) {
      const auto& core_config = subconfig.codec.params.GetAsCoreCodecConfig();
      subconfig.qos.maxSdu = subconfig.codec.GetChannelCountPerIsoStream() *
                             core_config.octets_per_codec_frame.value_or(0) *
                             core_config.codec_frames_blocks_per_sdu.value_or(1);
      subconfig.qos.sduIntervalUs = core_config.GetFrameDurationUs() *
                                    core_config.codec_frames_blocks_per_sdu.value_or(1);
    }
  }

  bool LoadConfigurationsFromFiles(const char* schema_file, const char* content_file,
                                   types::CodecLocation location) {
    flatbuffers::Parser configurations_parser_;
    std::string configurations_schema_binary_content;
    bool ok = flatbuffers::LoadFile(schema_file, true, &configurations_schema_binary_content);
    if (!ok) {
      return ok;
    }

    /* Load the binary schema */
    ok = configurations_parser_.Deserialize((uint8_t*)configurations_schema_binary_content.c_str(),
                                            configurations_schema_binary_content.length());
    if (!ok) {
      return ok;
    }

    /* Load the content from JSON */
    std::string configurations_json_content;
    ok = flatbuffers::LoadFile(content_file, false, &configurations_json_content);
    if (!ok) {
      return ok;
    }

    /* Parse */
    ok = configurations_parser_.Parse(configurations_json_content.c_str());
    if (!ok) {
      return ok;
    }

    /* Import from flatbuffers */
    auto configurations_root = fbs::le_audio::GetAudioSetConfigurations(
            configurations_parser_.builder_.GetBufferPointer());
    if (!configurations_root) {
      return false;
    }

    auto flat_qos_configs = configurations_root->qos_configurations();
    if ((flat_qos_configs == nullptr) || (flat_qos_configs->size() == 0)) {
      return false;
    }

    log::debug(": Updating {} qos config entries.", flat_qos_configs->size());
    std::vector<const fbs::le_audio::QosConfiguration*> qos_cfgs;
    for (auto const& flat_qos_cfg : *flat_qos_configs) {
      qos_cfgs.push_back(flat_qos_cfg);
    }

    auto flat_codec_configs = configurations_root->codec_configurations();
    if ((flat_codec_configs == nullptr) || (flat_codec_configs->size() == 0)) {
      return false;
    }

    log::debug(": Updating {} codec config entries.", flat_codec_configs->size());
    std::vector<const fbs::le_audio::CodecConfiguration*> codec_cfgs;
    for (auto const& flat_codec_cfg : *flat_codec_configs) {
      codec_cfgs.push_back(flat_codec_cfg);
    }

    auto flat_configs = configurations_root->configurations();
    if ((flat_configs == nullptr) || (flat_configs->size() == 0)) {
      return false;
    }

    log::debug(": Updating {} config entries.", flat_configs->size());
    for (auto const& flat_cfg : *flat_configs) {
      auto configuration =
              AudioSetConfigurationFromFlat(flat_cfg, &codec_cfgs, &qos_cfgs, location);
      if (!configuration.confs.sink.empty() || !configuration.confs.source.empty()) {
        configurations_.insert({flat_cfg->name()->str(), configuration});
      }
    }

    return true;
  }

  AudioSetConfigurations AudioSetConfigurationsFromFlatScenario(
          const fbs::le_audio::AudioSetScenario* const flat_scenario) {
    AudioSetConfigurations items;
    if (!flat_scenario->configurations()) {
      return items;
    }

    for (auto config_name : *flat_scenario->configurations()) {
      if (configurations_.count(config_name->str()) == 0) {
        continue;
      }

      auto& cfg = configurations_.at(config_name->str());
      items.push_back(&cfg);
    }

    return items;
  }

  bool LoadScenariosFromFiles(const char* schema_file, const char* content_file) {
    flatbuffers::Parser scenarios_parser_;
    std::string scenarios_schema_binary_content;
    bool ok = flatbuffers::LoadFile(schema_file, true, &scenarios_schema_binary_content);
    if (!ok) {
      return ok;
    }

    /* Load the binary schema */
    ok = scenarios_parser_.Deserialize((uint8_t*)scenarios_schema_binary_content.c_str(),
                                       scenarios_schema_binary_content.length());
    if (!ok) {
      return ok;
    }

    /* Load the content from JSON */
    std::string scenarios_json_content;
    ok = flatbuffers::LoadFile(content_file, false, &scenarios_json_content);
    if (!ok) {
      return ok;
    }

    /* Parse */
    ok = scenarios_parser_.Parse(scenarios_json_content.c_str());
    if (!ok) {
      return ok;
    }

    /* Import from flatbuffers */
    auto scenarios_root =
            fbs::le_audio::GetAudioSetScenarios(scenarios_parser_.builder_.GetBufferPointer());
    if (!scenarios_root) {
      return false;
    }

    auto flat_scenarios = scenarios_root->scenarios();
    if ((flat_scenarios == nullptr) || (flat_scenarios->size() == 0)) {
      return false;
    }

    log::debug(": Updating {} scenarios.", flat_scenarios->size());
    for (auto const& scenario : *flat_scenarios) {
      log::debug("Scenario {} configs:", scenario->name()->c_str());
      auto configs = AudioSetConfigurationsFromFlatScenario(scenario);
      for (auto& config : configs) {
        log::debug("\t\t Audio set config: {}", config->name);
      }

      auto [it_begin, it_end] = ScenarioToContextTypes(scenario->name()->c_str());
      for (auto it = it_begin; it != it_end; ++it) {
        context_configurations_.insert_or_assign(it->second,
                                                 AudioSetConfigurationsFromFlatScenario(scenario));
      }
    }

    return true;
  }

  bool LoadContent(
          std::vector<std::pair<const char* /*schema*/, const char* /*content*/>> config_files,
          std::vector<std::pair<const char* /*schema*/, const char* /*content*/>> scenario_files,
          types::CodecLocation location) {
    for (auto [schema, content] : config_files) {
      if (!LoadConfigurationsFromFiles(schema, content, location)) {
        return false;
      }
    }

    for (auto [schema, content] : scenario_files) {
      if (!LoadScenariosFromFiles(schema, content)) {
        return false;
      }
    }
    return true;
  }
};

struct AudioSetConfigurationProvider::impl {
  impl(const AudioSetConfigurationProvider& config_provider) : config_provider_(config_provider) {}

  void Initialize(types::CodecLocation location) {
    log::assert_that(!config_provider_impl_, "Config provider not available.");
    config_provider_impl_ = std::make_unique<AudioSetConfigurationProviderJson>(location);
  }

  void Cleanup() {
    log::assert_that(config_provider_impl_ != nullptr, "Config provider not available.");
    config_provider_impl_.reset();
  }

  bool IsRunning() { return config_provider_impl_ ? true : false; }

  void Dump(int fd) {
    std::stringstream stream;

    for (LeAudioContextType context : types::kLeAudioContextAllTypesArray) {
      auto confs = Get()->GetConfigurations(context);
      stream << "\n  === Configurations for context type: " << (int)context
             << ", num: " << (confs == nullptr ? 0 : confs->size()) << " \n";
      if (confs && confs->size() > 0) {
        for (const auto& conf : *confs) {
          stream << "  name: " << conf->name << " \n";
          for (const auto direction :
               {types::kLeAudioDirectionSink, types::kLeAudioDirectionSource}) {
            stream << "   ASE configs for direction: "
                   << (direction == types::kLeAudioDirectionSink ? "Sink (speaker)\n"
                                                                 : "Source (microphone)\n");
            for (const auto& ent : conf->confs.get(direction)) {
              stream << "    ASE config: " << "     qos->target latency: "
                     << +ent.qos.target_latency << " \n"
                     << "     qos->retransmission_number: " << +ent.qos.retransmission_number
                     << " \n"
                     << "     qos->max_transport_latency: " << +ent.qos.max_transport_latency
                     << " \n"
                     << "     channel count per ISO stream: "
                     << +ent.codec.GetChannelCountPerIsoStream() << "\n";
            }
          }
        }
      }
    }
    dprintf(fd, "%s", stream.str().c_str());
  }

  const AudioSetConfigurationProvider& config_provider_;
  std::unique_ptr<AudioSetConfigurationProviderJson> config_provider_impl_;
};

static std::unique_ptr<AudioSetConfigurationProvider> config_provider;
std::mutex instance_mutex;

AudioSetConfigurationProvider::AudioSetConfigurationProvider()
    : pimpl_(std::make_unique<AudioSetConfigurationProvider::impl>(*this)) {}

void AudioSetConfigurationProvider::Initialize(types::CodecLocation location) {
  std::scoped_lock<std::mutex> lock(instance_mutex);
  if (!config_provider) {
    config_provider = std::make_unique<AudioSetConfigurationProvider>();
  }

  if (!config_provider->pimpl_->IsRunning()) {
    config_provider->pimpl_->Initialize(location);
  }
}

void AudioSetConfigurationProvider::DebugDump(int fd) {
  std::scoped_lock<std::mutex> lock(instance_mutex);
  if (!config_provider || !config_provider->pimpl_->IsRunning()) {
    dprintf(fd,
            "\n AudioSetConfigurationProvider not initialized: config provider: "
            "%d, pimpl: %d \n",
            config_provider != nullptr,
            (config_provider == nullptr ? 0 : config_provider->pimpl_->IsRunning()));
    return;
  }
  dprintf(fd, "\n AudioSetConfigurationProvider: \n");
  config_provider->pimpl_->Dump(fd);
}

void AudioSetConfigurationProvider::Cleanup() {
  std::scoped_lock<std::mutex> lock(instance_mutex);
  if (!config_provider) {
    return;
  }
  if (config_provider->pimpl_->IsRunning()) {
    config_provider->pimpl_->Cleanup();
  }
  config_provider.reset();
}

AudioSetConfigurationProvider* AudioSetConfigurationProvider::Get() {
  return config_provider.get();
}

const set_configurations::AudioSetConfigurations* AudioSetConfigurationProvider::GetConfigurations(
        ::bluetooth::le_audio::types::LeAudioContextType content_type) const {
  if (pimpl_->IsRunning()) {
    return pimpl_->config_provider_impl_->GetConfigurationsByContextType(content_type);
  }

  return nullptr;
}

bool AudioSetConfigurationProvider::CheckConfigurationIsBiDirSwb(
        const set_configurations::AudioSetConfiguration& set_configuration) const {
  uint8_t dir = 0;

  for (auto direction :
       {le_audio::types::kLeAudioDirectionSink, le_audio::types::kLeAudioDirectionSource}) {
    for (const auto& conf : set_configuration.confs.get(direction)) {
      if (conf.codec.GetSamplingFrequencyHz() >=
          bluetooth::le_audio::LeAudioCodecConfiguration::kSampleRate32000) {
        dir |= direction;
      }
    }
  }
  return dir == bluetooth::le_audio::types::kLeAudioDirectionBoth;
}

bool AudioSetConfigurationProvider::CheckConfigurationIsDualBiDirSwb(
        const set_configurations::AudioSetConfiguration& set_configuration) const {
  types::BidirectionalPair<uint8_t> swb_direction_counter = {0, 0};

  for (auto direction :
       {le_audio::types::kLeAudioDirectionSink, le_audio::types::kLeAudioDirectionSource}) {
    auto const& confs = set_configuration.confs.get(direction);
    swb_direction_counter.get(direction) +=
            std::count_if(confs.begin(), confs.end(), [](auto const& cfg) {
              return cfg.codec.GetSamplingFrequencyHz() >=
                     bluetooth::le_audio::LeAudioCodecConfiguration::kSampleRate32000;
            });
  }

  return (swb_direction_counter.sink > 1) && (swb_direction_counter.source > 1);
}

}  // namespace bluetooth::le_audio
