/*
 *  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_hardware_impl.h"

#include <assert.h>

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

namespace webrtc {

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

#ifdef WEBRTC_VOICE_ENGINE_HARDWARE_API

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

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

int VoEHardwareImpl::SetAudioDeviceLayer(AudioLayers audioLayer) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetAudioDeviceLayer(audioLayer=%d)", audioLayer);

  // Don't allow a change if VoE is initialized
  if (_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_ALREADY_INITED, kTraceError);
    return -1;
  }

  // Map to AudioDeviceModule::AudioLayer
  AudioDeviceModule::AudioLayer wantedLayer(
      AudioDeviceModule::kPlatformDefaultAudio);
  switch (audioLayer) {
    case kAudioPlatformDefault:
      // already set above
      break;
    case kAudioWindowsCore:
      wantedLayer = AudioDeviceModule::kWindowsCoreAudio;
      break;
    case kAudioWindowsWave:
      wantedLayer = AudioDeviceModule::kWindowsWaveAudio;
      break;
    case kAudioLinuxAlsa:
      wantedLayer = AudioDeviceModule::kLinuxAlsaAudio;
      break;
    case kAudioLinuxPulse:
      wantedLayer = AudioDeviceModule::kLinuxPulseAudio;
      break;
  }

  // Save the audio device layer for Init()
  _shared->set_audio_device_layer(wantedLayer);

  return 0;
}

int VoEHardwareImpl::GetAudioDeviceLayer(AudioLayers& audioLayer) {
  // Can always be called regardless of VoE state

  AudioDeviceModule::AudioLayer activeLayer(
      AudioDeviceModule::kPlatformDefaultAudio);

  if (_shared->audio_device()) {
    // Get active audio layer from ADM
    if (_shared->audio_device()->ActiveAudioLayer(&activeLayer) != 0) {
      _shared->SetLastError(VE_UNDEFINED_SC_ERR, kTraceError,
                            "  Audio Device error");
      return -1;
    }
  } else {
    // Return VoE's internal layer setting
    activeLayer = _shared->audio_device_layer();
  }

  // Map to AudioLayers
  switch (activeLayer) {
    case AudioDeviceModule::kPlatformDefaultAudio:
      audioLayer = kAudioPlatformDefault;
      break;
    case AudioDeviceModule::kWindowsCoreAudio:
      audioLayer = kAudioWindowsCore;
      break;
    case AudioDeviceModule::kWindowsWaveAudio:
      audioLayer = kAudioWindowsWave;
      break;
    case AudioDeviceModule::kLinuxAlsaAudio:
      audioLayer = kAudioLinuxAlsa;
      break;
    case AudioDeviceModule::kLinuxPulseAudio:
      audioLayer = kAudioLinuxPulse;
      break;
    default:
      _shared->SetLastError(VE_UNDEFINED_SC_ERR, kTraceError,
                            "  unknown audio layer");
  }

  return 0;
}
int VoEHardwareImpl::GetNumOfRecordingDevices(int& devices) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  devices = static_cast<int>(_shared->audio_device()->RecordingDevices());

  return 0;
}

int VoEHardwareImpl::GetNumOfPlayoutDevices(int& devices) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }

  devices = static_cast<int>(_shared->audio_device()->PlayoutDevices());

  return 0;
}

int VoEHardwareImpl::GetRecordingDeviceName(int index,
                                            char strNameUTF8[128],
                                            char strGuidUTF8[128]) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (strNameUTF8 == NULL) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "GetRecordingDeviceName() invalid argument");
    return -1;
  }

  // Note that strGuidUTF8 is allowed to be NULL

  // Init len variable to length of supplied vectors
  const uint16_t strLen = 128;

  // Check if length has been changed in module
  static_assert(strLen == kAdmMaxDeviceNameSize, "");
  static_assert(strLen == kAdmMaxGuidSize, "");

  char name[strLen];
  char guid[strLen];

  // Get names from module
  if (_shared->audio_device()->RecordingDeviceName(index, name, guid) != 0) {
    _shared->SetLastError(VE_CANNOT_RETRIEVE_DEVICE_NAME, kTraceError,
                          "GetRecordingDeviceName() failed to get device name");
    return -1;
  }

  // Copy to vectors supplied by user
  strncpy(strNameUTF8, name, strLen);

  if (strGuidUTF8 != NULL) {
    strncpy(strGuidUTF8, guid, strLen);
  }

  return 0;
}

int VoEHardwareImpl::GetPlayoutDeviceName(int index,
                                          char strNameUTF8[128],
                                          char strGuidUTF8[128]) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  if (strNameUTF8 == NULL) {
    _shared->SetLastError(VE_INVALID_ARGUMENT, kTraceError,
                          "GetPlayoutDeviceName() invalid argument");
    return -1;
  }

  // Note that strGuidUTF8 is allowed to be NULL

  // Init len variable to length of supplied vectors
  const uint16_t strLen = 128;

  // Check if length has been changed in module
  static_assert(strLen == kAdmMaxDeviceNameSize, "");
  static_assert(strLen == kAdmMaxGuidSize, "");

  char name[strLen];
  char guid[strLen];

  // Get names from module
  if (_shared->audio_device()->PlayoutDeviceName(index, name, guid) != 0) {
    _shared->SetLastError(VE_CANNOT_RETRIEVE_DEVICE_NAME, kTraceError,
                          "GetPlayoutDeviceName() failed to get device name");
    return -1;
  }

  // Copy to vectors supplied by user
  strncpy(strNameUTF8, name, strLen);

  if (strGuidUTF8 != NULL) {
    strncpy(strGuidUTF8, guid, strLen);
  }

  return 0;
}

int VoEHardwareImpl::SetRecordingDevice(int index,
                                        StereoChannel recordingChannel) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetRecordingDevice(index=%d, recordingChannel=%d)", index,
               (int)recordingChannel);
  CriticalSectionScoped cs(_shared->crit_sec());

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

  bool isRecording(false);

  // Store state about activated recording to be able to restore it after the
  // recording device has been modified.
  if (_shared->audio_device()->Recording()) {
    WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
                 "SetRecordingDevice() device is modified while recording"
                 " is active...");
    isRecording = true;
    if (_shared->audio_device()->StopRecording() == -1) {
      _shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
                            "SetRecordingDevice() unable to stop recording");
      return -1;
    }
  }

  // We let the module do the index sanity

  // Set recording channel
  AudioDeviceModule::ChannelType recCh = AudioDeviceModule::kChannelBoth;
  switch (recordingChannel) {
    case kStereoLeft:
      recCh = AudioDeviceModule::kChannelLeft;
      break;
    case kStereoRight:
      recCh = AudioDeviceModule::kChannelRight;
      break;
    case kStereoBoth:
      // default setting kChannelBoth (<=> mono)
      break;
  }

  if (_shared->audio_device()->SetRecordingChannel(recCh) != 0) {
    _shared->SetLastError(
        VE_AUDIO_DEVICE_MODULE_ERROR, kTraceWarning,
        "SetRecordingChannel() unable to set the recording channel");
  }

  // Map indices to unsigned since underlying functions need that
  uint16_t indexU = static_cast<uint16_t>(index);

  int32_t res(0);

  if (index == -1) {
    res = _shared->audio_device()->SetRecordingDevice(
        AudioDeviceModule::kDefaultCommunicationDevice);
  } else if (index == -2) {
    res = _shared->audio_device()->SetRecordingDevice(
        AudioDeviceModule::kDefaultDevice);
  } else {
    res = _shared->audio_device()->SetRecordingDevice(indexU);
  }

  if (res != 0) {
    _shared->SetLastError(
        VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
        "SetRecordingDevice() unable to set the recording device");
    return -1;
  }

  // Init microphone, so user can do volume settings etc
  if (_shared->audio_device()->InitMicrophone() == -1) {
    _shared->SetLastError(VE_CANNOT_ACCESS_MIC_VOL, kTraceWarning,
                          "SetRecordingDevice() cannot access microphone");
  }

  // Set number of channels
  bool available = false;
  if (_shared->audio_device()->StereoRecordingIsAvailable(&available) != 0) {
    _shared->SetLastError(
        VE_SOUNDCARD_ERROR, kTraceWarning,
        "StereoRecordingIsAvailable() failed to query stereo recording");
  }

  if (_shared->audio_device()->SetStereoRecording(available) != 0) {
    _shared->SetLastError(
        VE_SOUNDCARD_ERROR, kTraceWarning,
        "SetRecordingDevice() failed to set mono recording mode");
  }

  // Restore recording if it was enabled already when calling this function.
  if (isRecording) {
    WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
                 "SetRecordingDevice() recording is now being restored...");
    if (_shared->audio_device()->InitRecording() != 0) {
      WEBRTC_TRACE(kTraceError, kTraceVoice,
                   VoEId(_shared->instance_id(), -1),
                   "SetRecordingDevice() failed to initialize recording");
      return -1;
    }
    if (_shared->audio_device()->StartRecording() != 0) {
      WEBRTC_TRACE(kTraceError, kTraceVoice,
                   VoEId(_shared->instance_id(), -1),
                   "SetRecordingDevice() failed to start recording");
      return -1;
    }
  }

  return 0;
}

int VoEHardwareImpl::SetPlayoutDevice(int index) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "SetPlayoutDevice(index=%d)", index);
  CriticalSectionScoped cs(_shared->crit_sec());

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

  bool isPlaying(false);

  // Store state about activated playout to be able to restore it after the
  // playout device has been modified.
  if (_shared->audio_device()->Playing()) {
    WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
                 "SetPlayoutDevice() device is modified while playout is "
                 "active...");
    isPlaying = true;
    if (_shared->audio_device()->StopPlayout() == -1) {
      _shared->SetLastError(VE_AUDIO_DEVICE_MODULE_ERROR, kTraceError,
                            "SetPlayoutDevice() unable to stop playout");
      return -1;
    }
  }

  // We let the module do the index sanity

  // Map indices to unsigned since underlying functions need that
  uint16_t indexU = static_cast<uint16_t>(index);

  int32_t res(0);

  if (index == -1) {
    res = _shared->audio_device()->SetPlayoutDevice(
        AudioDeviceModule::kDefaultCommunicationDevice);
  } else if (index == -2) {
    res = _shared->audio_device()->SetPlayoutDevice(
        AudioDeviceModule::kDefaultDevice);
  } else {
    res = _shared->audio_device()->SetPlayoutDevice(indexU);
  }

  if (res != 0) {
    _shared->SetLastError(
        VE_SOUNDCARD_ERROR, kTraceError,
        "SetPlayoutDevice() unable to set the playout device");
    return -1;
  }

  // Init speaker, so user can do volume settings etc
  if (_shared->audio_device()->InitSpeaker() == -1) {
    _shared->SetLastError(VE_CANNOT_ACCESS_SPEAKER_VOL, kTraceWarning,
                          "SetPlayoutDevice() cannot access speaker");
  }

  // Set number of channels
  bool available = false;
  _shared->audio_device()->StereoPlayoutIsAvailable(&available);
  if (_shared->audio_device()->SetStereoPlayout(available) != 0) {
    _shared->SetLastError(
        VE_SOUNDCARD_ERROR, kTraceWarning,
        "SetPlayoutDevice() failed to set stereo playout mode");
  }

  // Restore playout if it was enabled already when calling this function.
  if (isPlaying) {
    WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_shared->instance_id(), -1),
                 "SetPlayoutDevice() playout is now being restored...");
    if (_shared->audio_device()->InitPlayout() != 0) {
      WEBRTC_TRACE(kTraceError, kTraceVoice,
                   VoEId(_shared->instance_id(), -1),
                   "SetPlayoutDevice() failed to initialize playout");
      return -1;
    }
    if (_shared->audio_device()->StartPlayout() != 0) {
      WEBRTC_TRACE(kTraceError, kTraceVoice,
                   VoEId(_shared->instance_id(), -1),
                   "SetPlayoutDevice() failed to start playout");
      return -1;
    }
  }

  return 0;
}

int VoEHardwareImpl::SetRecordingSampleRate(unsigned int samples_per_sec) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "%s", __FUNCTION__);
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return false;
  }
  return _shared->audio_device()->SetRecordingSampleRate(samples_per_sec);
}

int VoEHardwareImpl::RecordingSampleRate(unsigned int* samples_per_sec) const {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return false;
  }
  return _shared->audio_device()->RecordingSampleRate(samples_per_sec);
}

int VoEHardwareImpl::SetPlayoutSampleRate(unsigned int samples_per_sec) {
  WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1),
               "%s", __FUNCTION__);
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return false;
  }
  return _shared->audio_device()->SetPlayoutSampleRate(samples_per_sec);
}

int VoEHardwareImpl::PlayoutSampleRate(unsigned int* samples_per_sec) const {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return false;
  }
  return _shared->audio_device()->PlayoutSampleRate(samples_per_sec);
}

bool VoEHardwareImpl::BuiltInAECIsAvailable() const {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return false;
  }
  return _shared->audio_device()->BuiltInAECIsAvailable();
}

int VoEHardwareImpl::EnableBuiltInAEC(bool enable) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  return _shared->audio_device()->EnableBuiltInAEC(enable);
}

bool VoEHardwareImpl::BuiltInAGCIsAvailable() const {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return false;
  }
  return _shared->audio_device()->BuiltInAGCIsAvailable();
}

int VoEHardwareImpl::EnableBuiltInAGC(bool enable) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  return _shared->audio_device()->EnableBuiltInAGC(enable);
}

bool VoEHardwareImpl::BuiltInNSIsAvailable() const {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return false;
  }
  return _shared->audio_device()->BuiltInNSIsAvailable();
}

int VoEHardwareImpl::EnableBuiltInNS(bool enable) {
  if (!_shared->statistics().Initialized()) {
    _shared->SetLastError(VE_NOT_INITED, kTraceError);
    return -1;
  }
  return _shared->audio_device()->EnableBuiltInNS(enable);
}

#endif  // WEBRTC_VOICE_ENGINE_HARDWARE_API

}  // namespace webrtc
