text: // Copyright 2020 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/media/audio/audio_core/thermal_agent.h"
  
  #include <lib/syslog/cpp/macros.h>
  
  #include <set>
  
  #include "src/media/audio/audio_core/audio_device_manager.h"
  
  namespace media::audio {
  namespace {
  
  std::vector<std::string> ConfigsByState(const std::vector<ThermalConfig::State>& config_states,
                                          const std::string& nominal_config,
                                          const std::vector<uint32_t>& trip_points) {
    std::vector<std::string> result;
    // The number of states is one greater than the number of trip points.
    result.reserve(trip_points.size() + 1);
  
    // Push the nominal config as the config for state 0.
    result.push_back(nominal_config);
  
    // Push the rest of the state configs. Configured states correspond to one or more merged states.
    auto config_state_iter = config_states.begin();
    for (auto trip_point : trip_points) {
      // If there are no more config states, just copy the previous config.
      if (config_state_iter != config_states.end()) {
        auto& config_state = *config_state_iter;
  
        // If we haven't hit the trip point of the current config state, just copy the previous
        // config.
        if (config_state.trip_point() == trip_point) {
          // We've reached the trip point of the current config state. Use its config.
          result.push_back(config_state.config());
          ++config_state_iter;
          continue;
        }
      }
  
      result.push_back(result.back());
    }
  
    return result;
  }
  
  }  // namespace
  
  // static
  std::unique_ptr<ThermalAgent> ThermalAgent::CreateAndServe(Context* context) {
    auto& thermal_config = context->process_config().thermal_config();
    if (thermal_config.entries().empty()) {
      // No thermal config so we don't start the thermal agent.
      return nullptr;
    }
    return std::make_unique<ThermalAgent>(
        context->component_context().svc()->Connect<fuchsia::thermal::Controller>(), thermal_config,
        context->process_config().device_config(),
        [context](const std::string& target_name, const std::string& config) {
          auto promise = context->device_manager().UpdateEffect(target_name, config);
          context->threading_model().FidlDomain().executor()->schedule_task(
              promise.then([target_name, config](
                               fit::result<void, fuchsia::media::audio::UpdateEffectError>& result) {
                if (result.is_error()) {
                  std::ostringstream err;
                  if (result.error() == fuchsia::media::audio::UpdateEffectError::NOT_FOUND) {
                    err << "effect with name " << target_name << " was not found";
                  } else {
                    err << "message " << config << " was rejected";
                  }
                  FX_LOGS_FIRST_N(ERROR, 10) << "Unable to apply thermal policy: " << err.str();
                }
              }));
        });
  }
  
  ThermalAgent::ThermalAgent(fuchsia::thermal::ControllerPtr thermal_controller,
                             const ThermalConfig& thermal_config, const DeviceConfig& device_config,
                             SetConfigCallback set_config_callback)
      : thermal_controller_(std::move(thermal_controller)),
        binding_(this),
        set_config_callback_(std::move(set_config_callback)) {
    FX_DCHECK(thermal_controller_);
    FX_DCHECK(set_config_callback_);
  
    if (thermal_config.entries().empty()) {
      // No thermal config. Nothing to do.
      thermal_controller_ = nullptr;
      return;
    }
  
    std::set<uint32_t> trip_points_set;
    for (auto& entry : thermal_config.entries()) {
      for (auto& state : entry.states()) {
        trip_points_set.insert(state.trip_point());
      }
    }
  
    std::vector<uint32_t> trip_points(trip_points_set.begin(), trip_points_set.end());
  
    for (auto& entry : thermal_config.entries()) {
      auto nominal_config = FindNominalConfigForTarget(entry.target_name(), device_config);
      if (nominal_config.has_value()) {
        targets_.emplace_back(entry.target_name(),
                              ConfigsByState(entry.states(), nominal_config.value(), trip_points));
      } else {
        FX_LOGS(ERROR) << "Thermal config references unknown target '" << entry.target_name() << "'.";
      }
    }
  
    thermal_controller_.set_error_handler([this](zx_status_t status) {
      FX_PLOGS(ERROR, status) << "Connection to fuchsia.thermal.Controller failed: ";
      thermal_controller_.set_error_handler(nullptr);
      thermal_controller_.Unbind();
    });
  
    thermal_controller_->Subscribe(
        binding_.NewBinding(), fuchsia::thermal::ActorType::AUDIO, std::move(trip_points),
        [this](fuchsia::thermal::Controller_Subscribe_Result result) {
          if (result.is_err()) {
            FX_CHECK(result.err() != fuchsia::thermal::Error::INVALID_ARGUMENTS);
            FX_LOGS(ERROR) << "fuchsia.thermal.Controller/Subscribe failed";
          }
  
          thermal_controller_.set_error_handler(nullptr);
          thermal_controller_.Unbind();
        });
  }
  
  void ThermalAgent::SetThermalState(uint32_t state, SetThermalStateCallback callback) {
    if (current_state_ != state) {
      for (auto& target : targets_) {
        FX_CHECK(state < target.configs_by_state().size());
        FX_CHECK(current_state_ < target.configs_by_state().size());
        if (target.configs_by_state()[state] != target.configs_by_state()[current_state_]) {
          set_config_callback_(target.name(), target.configs_by_state()[state]);
        }
      }
  
      current_state_ = state;
    }
  
    callback();
  }
  
  std::optional<std::string> ThermalAgent::FindNominalConfigForTarget(
      const std::string& target_name, const DeviceConfig& device_config) {
    // For 'special' target names (not effect names), this method must return a string. An empty
    // string is fine. The remainder of this method assumes the |target_name| references an effect.
  
    const PipelineConfig::Effect* effect = device_config.FindEffect(target_name);
    return effect ? std::optional(effect->effect_config) : std::nullopt;
  }
  
  }  // namespace media::audio
  