// Copyright 2016 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.
//

// Implementation of audio_volume_handler.h

#include "audio_volume_handler.h"

#include <base/files/file.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <brillo/map_utils.h>
#include <brillo/message_loops/message_loop.h>
#include <brillo/strings/string_utils.h>

#include "audio_device_handler.h"

namespace brillo {

static const char kVolumeStateFilePath[] =
    "/data/misc/brilloaudioservice/volume.dat";

AudioVolumeHandler::AudioVolumeHandler() {
  for (auto stream : kSupportedStreams_) {
    step_sizes_.emplace(stream, kDefaultStepSize_);
  }
  selected_stream_ = AUDIO_STREAM_DEFAULT;
  volume_state_file_ = base::FilePath(kVolumeStateFilePath);
}

AudioVolumeHandler::~AudioVolumeHandler() {}

void AudioVolumeHandler::APSDisconnect() { aps_.clear(); }

void AudioVolumeHandler::APSConnect(
    android::sp<android::IAudioPolicyService> aps) {
  aps_ = aps;
  InitAPSAllStreams();
}

void AudioVolumeHandler::RegisterCallback(
    base::Callback<void(audio_stream_type_t, int, int)>& callback) {
  callback_ = callback;
}

int AudioVolumeHandler::ConvertToUserDefinedIndex(audio_stream_type_t stream,
                                                  int index) {
  return index / step_sizes_[stream];
}

int AudioVolumeHandler::ConvertToInternalIndex(audio_stream_type_t stream,
                                               int index) {
  return index * step_sizes_[stream];
}

void AudioVolumeHandler::TriggerCallback(audio_stream_type_t stream,
                                         int previous_index,
                                         int current_index) {
  int user_defined_previous_index =
      ConvertToUserDefinedIndex(stream, previous_index);
  int user_defined_current_index =
      ConvertToUserDefinedIndex(stream, current_index);
  MessageLoop::current()->PostTask(base::Bind(callback_,
                                              stream,
                                              user_defined_previous_index,
                                              user_defined_current_index));
}

void AudioVolumeHandler::GenerateVolumeFile() {
  for (auto stream : kSupportedStreams_) {
    for (auto device : AudioDeviceHandler::kSupportedOutputDevices_) {
      PersistVolumeConfiguration(stream, device, kDefaultCurrentIndex_);
    }
  }
  if (!kv_store_->Save(volume_state_file_)) {
    LOG(ERROR) << "Could not save volume data file!";
  }
}

int AudioVolumeHandler::GetVolumeMaxSteps(audio_stream_type_t stream) {
  return ConvertToUserDefinedIndex(stream, kMaxIndex_);
}

int AudioVolumeHandler::SetVolumeMaxSteps(audio_stream_type_t stream,
                                          int max_steps) {
  if (max_steps <= kMinIndex_ || max_steps > kMaxIndex_)
    return EINVAL;
  step_sizes_[stream] = kMaxIndex_ / max_steps;
  return 0;
}

int AudioVolumeHandler::GetVolumeCurrentIndex(audio_stream_type_t stream,
                                              audio_devices_t device) {
  auto key = kCurrentIndexKey_ + "." + string_utils::ToString(stream) + "." +
             string_utils::ToString(device);
  std::string value;
  kv_store_->GetString(key, &value);
  return std::stoi(value);
}

int AudioVolumeHandler::GetVolumeIndex(audio_stream_type_t stream,
                                       audio_devices_t device) {
  return ConvertToUserDefinedIndex(stream,
                                   GetVolumeCurrentIndex(stream, device));
}

int AudioVolumeHandler::SetVolumeIndex(audio_stream_type_t stream,
                                       audio_devices_t device,
                                       int index) {
  if (index < kMinIndex_ ||
      index > ConvertToUserDefinedIndex(stream, kMaxIndex_))
    return EINVAL;
  int previous_index = GetVolumeCurrentIndex(stream, device);
  int current_absolute_index = ConvertToInternalIndex(stream, index);
  PersistVolumeConfiguration(stream, device, current_absolute_index);
  TriggerCallback(stream, previous_index, current_absolute_index);
  return 0;
}

void AudioVolumeHandler::PersistVolumeConfiguration(audio_stream_type_t stream,
                                                    audio_devices_t device,
                                                    int index) {
  auto key = kCurrentIndexKey_ + "." + string_utils::ToString(stream) + "." +
             string_utils::ToString(device);
  kv_store_->SetString(key, string_utils::ToString(index));
  kv_store_->Save(volume_state_file_);
}

void AudioVolumeHandler::InitAPSAllStreams() {
  for (auto stream : kSupportedStreams_) {
    aps_->initStreamVolume(stream, kMinIndex_, kMaxIndex_);
    for (auto device : AudioDeviceHandler::kSupportedOutputDevices_) {
      int current_index = GetVolumeCurrentIndex(stream, device);
      aps_->setStreamVolumeIndex(stream, current_index, device);
    }
  }
}

void AudioVolumeHandler::SetVolumeFilePathForTesting(
    const base::FilePath& path) {
  volume_state_file_ = path;
}

void AudioVolumeHandler::Init(android::sp<android::IAudioPolicyService> aps) {
  aps_ = aps;
  kv_store_ = std::unique_ptr<KeyValueStore>(new KeyValueStore());
  if (!base::PathExists(volume_state_file_)) {
    // Generate key-value store and save it to a file.
    GenerateVolumeFile();
  } else {
    // Load the file. If loading fails, generate the file.
    if (!kv_store_->Load(volume_state_file_)) {
      LOG(ERROR) << "Could not load volume data file!";
      GenerateVolumeFile();
    }
  }
  // Inform APS.
  InitAPSAllStreams();
}

audio_stream_type_t AudioVolumeHandler::GetVolumeControlStream() {
  return selected_stream_;
}

void AudioVolumeHandler::SetVolumeControlStream(audio_stream_type_t stream) {
  selected_stream_ = stream;
}

int AudioVolumeHandler::GetNewVolumeIndex(int previous_index, int direction,
                                          audio_stream_type_t stream) {
  int current_index =
      previous_index + ConvertToInternalIndex(stream, direction);
  if (current_index < kMinIndex_) {
    return kMinIndex_;
  } else if (current_index > kMaxIndex_) {
    return kMaxIndex_;
  } else
    return current_index;
}

void AudioVolumeHandler::AdjustStreamVolume(audio_stream_type_t stream,
                                            int direction) {
  VLOG(1) << "Adjusting volume of stream " << selected_stream_
          << " in direction " << direction;
  auto device = aps_->getDevicesForStream(stream);
  int previous_index = GetVolumeCurrentIndex(stream, device);
  int current_index = GetNewVolumeIndex(previous_index, direction, stream);
  VLOG(1) << "Current index is " << current_index << " for stream " << stream
          << " and device " << device;
  aps_->setStreamVolumeIndex(stream, current_index, device);
  PersistVolumeConfiguration(selected_stream_, device, current_index);
  TriggerCallback(stream, previous_index, current_index);
}

void AudioVolumeHandler::AdjustVolumeActiveStreams(int direction) {
  if (selected_stream_ != AUDIO_STREAM_DEFAULT) {
    AdjustStreamVolume(selected_stream_, direction);
    return;
  }
  for (auto stream : kSupportedStreams_) {
    if (aps_->isStreamActive(stream)) {
      AdjustStreamVolume(stream, direction);
      return;
    }
  }
}

void AudioVolumeHandler::ProcessEvent(const struct input_event& event) {
  VLOG(1) << event.type << " " << event.code << " " << event.value;
  if (event.type == EV_KEY) {
    switch (event.code) {
      case KEY_VOLUMEDOWN:
        AdjustVolumeActiveStreams(-1);
        break;
      case KEY_VOLUMEUP:
        AdjustVolumeActiveStreams(1);
        break;
      default:
        // This event code is not supported by this handler.
        break;
    }
  }
}

}  // namespace brillo