/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "webrtc/voice_engine/voe_volume_control_impl.h"

#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
#include "webrtc/system_wrappers/include/trace.h"
#include "webrtc/voice_engine/channel.h"
#include "webrtc/voice_engine/include/voe_errors.h"
#include "webrtc/voice_engine/output_mixer.h"
#include "webrtc/voice_engine/transmit_mixer.h"
#include "webrtc/voice_engine/voice_engine_impl.h"

namespace webrtc {

VoEVolumeControl* VoEVolumeControl::GetInterface(VoiceEngine* voiceEngine) {
#ifndef WEBRTC_VOICE_ENGINE_VOLUME_CONTROL_API
  return NULL;
#else
  if (NULL == voiceEngine) {
    return NULL;
  }
  VoiceEngineImpl* s = static_cast<VoiceEngineImpl*>(voiceEngine);
  s->AddRef();
  return s;
#endif
}

#ifdef WEBRTC_VOICE_ENGINE_VOLUME_CONTROL_API

VoEVolumeControlImpl::VoEVolumeControlImpl(voe::SharedData* shared)
    : _shared(shared) {
  WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "VoEVolumeControlImpl::VoEVolumeControlImpl() - ctor");
}

VoEVolumeControlImpl::~VoEVolumeControlImpl() {
  WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "VoEVolumeControlImpl::~VoEVolumeControlImpl() - dtor");
}

int VoEVolumeControlImpl::SetSpeakerVolume(unsigned int volume) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetSpeakerVolume(volume=%u)", volume);

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (volume > kMaxVolumeLevel) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "SetSpeakerVolume() invalid argument");
    return -1;
  }

  uint32_t maxVol(0);
  uint32_t spkrVol(0);

  // scale: [0,kMaxVolumeLevel] -> [0,MaxSpeakerVolume]
  if (_shared->audio_device()->MaxSpeakerVolume(&maxVol) != 0) {
    _shared->SetLastError(VE_MIC_VOL_ERROR, kTraceError,
                          "SetSpeakerVolume() failed to get max volume");
    return -1;
  }
  // Round the value and avoid floating computation.
  spkrVol = (uint32_t)((volume * maxVol + (int)(kMaxVolumeLevel / 2)) /
                       (kMaxVolumeLevel));

  // set the actual volume using the audio mixer
  if (_shared->audio_device()->SetSpeakerVolume(spkrVol) != 0) {
    _shared->SetLastError(VE_MIC_VOL_ERROR, kTraceError,
                          "SetSpeakerVolume() failed to set speaker volume");
    return -1;
  }
  return 0;
}

int VoEVolumeControlImpl::GetSpeakerVolume(unsigned int& volume) {

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  uint32_t spkrVol(0);
  uint32_t maxVol(0);

  if (_shared->audio_device()->SpeakerVolume(&spkrVol) != 0) {
    _shared->SetLastError(VE_GET_MIC_VOL_ERROR, kTraceError,
                          "GetSpeakerVolume() unable to get speaker volume");
    return -1;
  }

  // scale: [0, MaxSpeakerVolume] -> [0, kMaxVolumeLevel]
  if (_shared->audio_device()->MaxSpeakerVolume(&maxVol) != 0) {
    _shared->SetLastError(
        VE_GET_MIC_VOL_ERROR, kTraceError,
        "GetSpeakerVolume() unable to get max speaker volume");
    return -1;
  }
  // Round the value and avoid floating computation.
  volume =
      (uint32_t)((spkrVol * kMaxVolumeLevel + (int)(maxVol / 2)) / (maxVol));

  return 0;
}

int VoEVolumeControlImpl::SetMicVolume(unsigned int volume) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetMicVolume(volume=%u)", volume);

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (volume > kMaxVolumeLevel) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "SetMicVolume() invalid argument");
    return -1;
  }

  uint32_t maxVol(0);
  uint32_t micVol(0);

  // scale: [0, kMaxVolumeLevel] -> [0,MaxMicrophoneVolume]
  if (_shared->audio_device()->MaxMicrophoneVolume(&maxVol) != 0) {
    _shared->SetLastError(VE_MIC_VOL_ERROR, kTraceError,
                          "SetMicVolume() failed to get max volume");
    return -1;
  }

  if (volume == kMaxVolumeLevel) {
    // On Linux running pulse, users are able to set the volume above 100%
    // through the volume control panel, where the +100% range is digital
    // scaling. WebRTC does not support setting the volume above 100%, and
    // simply ignores changing the volume if the user tries to set it to
    // |kMaxVolumeLevel| while the current volume is higher than |maxVol|.
    if (_shared->audio_device()->MicrophoneVolume(&micVol) != 0) {
      _shared->SetLastError(VE_GET_MIC_VOL_ERROR, kTraceError,
                            "SetMicVolume() unable to get microphone volume");
      return -1;
    }
    if (micVol >= maxVol)
      return 0;
  }

  // Round the value and avoid floating point computation.
  micVol = (uint32_t)((volume * maxVol + (int)(kMaxVolumeLevel / 2)) /
                      (kMaxVolumeLevel));

  // set the actual volume using the audio mixer
  if (_shared->audio_device()->SetMicrophoneVolume(micVol) != 0) {
    _shared->SetLastError(VE_MIC_VOL_ERROR, kTraceError,
                          "SetMicVolume() failed to set mic volume");
    return -1;
  }
  return 0;
}

int VoEVolumeControlImpl::GetMicVolume(unsigned int& volume) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  uint32_t micVol(0);
  uint32_t maxVol(0);

  if (_shared->audio_device()->MicrophoneVolume(&micVol) != 0) {
    _shared->SetLastError(VE_GET_MIC_VOL_ERROR, kTraceError,
                          "GetMicVolume() unable to get microphone volume");
    return -1;
  }

  // scale: [0, MaxMicrophoneVolume] -> [0, kMaxVolumeLevel]
  if (_shared->audio_device()->MaxMicrophoneVolume(&maxVol) != 0) {
    _shared->SetLastError(VE_GET_MIC_VOL_ERROR, kTraceError,
                          "GetMicVolume() unable to get max microphone volume");
    return -1;
  }
  if (micVol < maxVol) {
    // Round the value and avoid floating point calculation.
    volume =
        (uint32_t)((micVol * kMaxVolumeLevel + (int)(maxVol / 2)) / (maxVol));
  } else {
    // Truncate the value to the kMaxVolumeLevel.
    volume = kMaxVolumeLevel;
  }
  return 0;
}

int VoEVolumeControlImpl::SetInputMute(int channel, bool enable) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetInputMute(channel=%d, enable=%d)", channel, enable);

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (channel == -1) {
    // Mute before demultiplexing <=> affects all channels
    return _shared->transmit_mixer()->SetMute(enable);
  }
  // Mute after demultiplexing <=> affects one channel only
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
                          "SetInputMute() failed to locate channel");
    return -1;
  }
  return channelPtr->SetMute(enable);
}

int VoEVolumeControlImpl::GetInputMute(int channel, bool& enabled) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (channel == -1) {
    enabled = _shared->transmit_mixer()->Mute();
  } else {
    voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
    voe::Channel* channelPtr = ch.channel();
    if (channelPtr == NULL) {
      _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
                            "SetInputMute() failed to locate channel");
      return -1;
    }
    enabled = channelPtr->Mute();
  }
  return 0;
}

int VoEVolumeControlImpl::GetSpeechInputLevel(unsigned int& level) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  int8_t currentLevel = _shared->transmit_mixer()->AudioLevel();
  level = static_cast<unsigned int>(currentLevel);
  return 0;
}

int VoEVolumeControlImpl::GetSpeechOutputLevel(int channel,
                                               unsigned int& level) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (channel == -1) {
    return _shared->output_mixer()->GetSpeechOutputLevel((uint32_t&)level);
  } else {
    voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
    voe::Channel* channelPtr = ch.channel();
    if (channelPtr == NULL) {
      _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
                            "GetSpeechOutputLevel() failed to locate channel");
      return -1;
    }
    channelPtr->GetSpeechOutputLevel((uint32_t&)level);
  }
  return 0;
}

int VoEVolumeControlImpl::GetSpeechInputLevelFullRange(unsigned int& level) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  int16_t currentLevel = _shared->transmit_mixer()->AudioLevelFullRange();
  level = static_cast<unsigned int>(currentLevel);
  return 0;
}

int VoEVolumeControlImpl::GetSpeechOutputLevelFullRange(int channel,
                                                        unsigned int& level) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (channel == -1) {
    return _shared->output_mixer()->GetSpeechOutputLevelFullRange(
        (uint32_t&)level);
  } else {
    voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
    voe::Channel* channelPtr = ch.channel();
    if (channelPtr == NULL) {
      _shared->SetLastError(
          VE_CHANNEL_NOT_VALID, kTraceError,
          "GetSpeechOutputLevelFullRange() failed to locate channel");
      return -1;
    }
    channelPtr->GetSpeechOutputLevelFullRange((uint32_t&)level);
  }
  return 0;
}

int VoEVolumeControlImpl::SetChannelOutputVolumeScaling(int channel,
                                                        float scaling) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetChannelOutputVolumeScaling(channel=%d, scaling=%3.2f)",
               channel, scaling);
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (scaling < kMinOutputVolumeScaling || scaling > kMaxOutputVolumeScaling) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "SetChannelOutputVolumeScaling() invalid parameter");
    return -1;
  }
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(
        VE_CHANNEL_NOT_VALID, kTraceError,
        "SetChannelOutputVolumeScaling() failed to locate channel");
    return -1;
  }
  return channelPtr->SetChannelOutputVolumeScaling(scaling);
}

int VoEVolumeControlImpl::GetChannelOutputVolumeScaling(int channel,
                                                        float& scaling) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(
        VE_CHANNEL_NOT_VALID, kTraceError,
        "GetChannelOutputVolumeScaling() failed to locate channel");
    return -1;
  }
  return channelPtr->GetChannelOutputVolumeScaling(scaling);
}

int VoEVolumeControlImpl::SetOutputVolumePan(int channel,
                                             float left,
                                             float right) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetOutputVolumePan(channel=%d, left=%2.1f, right=%2.1f)",
               channel, left, right);

  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  bool available(false);
  _shared->audio_device()->StereoPlayoutIsAvailable(&available);
  if (!available) {
    _shared->SetLastError(VE_FUNC_NO_STEREO, kTraceError,
                          "SetOutputVolumePan() stereo playout not supported");
    return -1;
  }
  if ((left < kMinOutputVolumePanning) || (left > kMaxOutputVolumePanning) ||
      (right < kMinOutputVolumePanning) || (right > kMaxOutputVolumePanning)) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "SetOutputVolumePan() invalid parameter");
    return -1;
  }

  if (channel == -1) {
    // Master balance (affectes the signal after output mixing)
    return _shared->output_mixer()->SetOutputVolumePan(left, right);
  }
  // Per-channel balance (affects the signal before output mixing)
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
                          "SetOutputVolumePan() failed to locate channel");
    return -1;
  }
  return channelPtr->SetOutputVolumePan(left, right);
}

int VoEVolumeControlImpl::GetOutputVolumePan(int channel,
                                             float& left,
                                             float& right) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  bool available(false);
  _shared->audio_device()->StereoPlayoutIsAvailable(&available);
  if (!available) {
    _shared->SetLastError(VE_FUNC_NO_STEREO, kTraceError,
                          "GetOutputVolumePan() stereo playout not supported");
    return -1;
  }

  if (channel == -1) {
    return _shared->output_mixer()->GetOutputVolumePan(left, right);
  }
  voe::ChannelOwner ch = _shared->channel_manager().GetChannel(channel);
  voe::Channel* channelPtr = ch.channel();
  if (channelPtr == NULL) {
    _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError,
                          "GetOutputVolumePan() failed to locate channel");
    return -1;
  }
  return channelPtr->GetOutputVolumePan(left, right);
}

#endif  // #ifdef WEBRTC_VOICE_ENGINE_VOLUME_CONTROL_API

}  // namespace webrtc
