/*
 *  Copyright 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 "pc/test/fake_audio_capture_module.h"

#include <cstdint>
#include <cstring>
#include <memory>
#include <utility>

#include "api/audio/audio_device_defines.h"
#include "api/make_ref_counted.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/units/time_delta.h"
#include "rtc_base/checks.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"

using ::webrtc::TimeDelta;

// Audio sample value that is high enough that it doesn't occur naturally when
// frames are being faked. E.g. NetEq will not generate this large sample value
// unless it has received an audio frame containing a sample of this value.
// Even simpler buffers would likely just contain audio sample values of 0.
static const int kHighSampleValue = 10000;

// Constants here are derived by running VoE using a real ADM.
// The constants correspond to 10ms of mono audio at 44kHz.
static const int kTimePerFrameMs = 10;
static const uint8_t kNumberOfChannels = 1;
static const int kSamplesPerSecond = 44000;
static const int kTotalDelayMs = 0;
static const int kClockDriftMs = 0;
static const uint32_t kMaxVolume = 14392;

FakeAudioCaptureModule::FakeAudioCaptureModule(
    std::unique_ptr<webrtc::Thread> process_thread)
    : audio_callback_(nullptr),
      recording_(false),
      playing_(false),
      play_is_initialized_(false),
      rec_is_initialized_(false),
      current_mic_level_(kMaxVolume),
      started_(false),
      next_frame_time_(0),
      process_thread_(std::move(process_thread)),
      frames_received_(0) {}

FakeAudioCaptureModule::~FakeAudioCaptureModule() {
  RTC_DCHECK(!initialized_);
}

webrtc::scoped_refptr<FakeAudioCaptureModule> FakeAudioCaptureModule::Create(
    std::unique_ptr<webrtc::Thread> process_thread) {
  auto capture_module = webrtc::make_ref_counted<FakeAudioCaptureModule>(
      std::move(process_thread));
  if (!capture_module->Initialize()) {
    return nullptr;
  }
  return capture_module;
}

webrtc::scoped_refptr<FakeAudioCaptureModule> FakeAudioCaptureModule::Create() {
  auto thread = webrtc::Thread::Create();
  thread->Start();
  return Create(std::move(thread));
}

int FakeAudioCaptureModule::frames_received() const {
  webrtc::MutexLock lock(&mutex_);
  return frames_received_;
}

int32_t FakeAudioCaptureModule::ActiveAudioLayer(
    AudioLayer* /*audio_layer*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::RegisterAudioCallback(
    webrtc::AudioTransport* audio_callback) {
  webrtc::MutexLock lock(&mutex_);
  audio_callback_ = audio_callback;
  return 0;
}

int32_t FakeAudioCaptureModule::Init() {
  // Initialize is called by the factory method.
  initialized_ = true;
  return 0;
}

int32_t FakeAudioCaptureModule::Terminate() {
  StopPlayout();
  StopRecording();
  if (process_thread_) {
    process_thread_->Stop();
  }
  initialized_ = false;
  return 0;
}

bool FakeAudioCaptureModule::Initialized() const {
  return initialized_;
}

int16_t FakeAudioCaptureModule::PlayoutDevices() {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int16_t FakeAudioCaptureModule::RecordingDevices() {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::PlayoutDeviceName(
    uint16_t /*index*/,
    char /*name*/[webrtc::kAdmMaxDeviceNameSize],
    char /*guid*/[webrtc::kAdmMaxGuidSize]) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::RecordingDeviceName(
    uint16_t /*index*/,
    char /*name*/[webrtc::kAdmMaxDeviceNameSize],
    char /*guid*/[webrtc::kAdmMaxGuidSize]) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SetPlayoutDevice(uint16_t /*index*/) {
  // No playout device, just playing from file. Return success.
  return 0;
}

int32_t FakeAudioCaptureModule::SetPlayoutDevice(WindowsDeviceType /*device*/) {
  if (play_is_initialized_) {
    return -1;
  }
  return 0;
}

int32_t FakeAudioCaptureModule::SetRecordingDevice(uint16_t /*index*/) {
  // No recording device, just dropping audio. Return success.
  return 0;
}

int32_t FakeAudioCaptureModule::SetRecordingDevice(
    WindowsDeviceType /*device*/) {
  if (rec_is_initialized_) {
    return -1;
  }
  return 0;
}

int32_t FakeAudioCaptureModule::PlayoutIsAvailable(bool* /*available*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::InitPlayout() {
  play_is_initialized_ = true;
  return 0;
}

bool FakeAudioCaptureModule::PlayoutIsInitialized() const {
  return play_is_initialized_;
}

int32_t FakeAudioCaptureModule::RecordingIsAvailable(bool* /*available*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::InitRecording() {
  rec_is_initialized_ = true;
  return 0;
}

bool FakeAudioCaptureModule::RecordingIsInitialized() const {
  return rec_is_initialized_;
}

int32_t FakeAudioCaptureModule::StartPlayout() {
  if (!play_is_initialized_) {
    return -1;
  }
  {
    webrtc::MutexLock lock(&mutex_);
    playing_ = true;
  }
  bool start = true;
  UpdateProcessing(start);
  return 0;
}

int32_t FakeAudioCaptureModule::StopPlayout() {
  bool start = false;
  {
    webrtc::MutexLock lock(&mutex_);
    if (!playing_)
      return 0;
    playing_ = false;
    start = ShouldStartProcessing();
  }
  UpdateProcessing(start);
  return 0;
}

bool FakeAudioCaptureModule::Playing() const {
  webrtc::MutexLock lock(&mutex_);
  return playing_;
}

int32_t FakeAudioCaptureModule::StartRecording() {
  if (!rec_is_initialized_) {
    return -1;
  }
  {
    webrtc::MutexLock lock(&mutex_);
    recording_ = true;
  }
  bool start = true;
  UpdateProcessing(start);
  return 0;
}

int32_t FakeAudioCaptureModule::StopRecording() {
  bool start = false;
  {
    webrtc::MutexLock lock(&mutex_);
    if (!recording_)
      return 0;
    recording_ = false;
    start = ShouldStartProcessing();
  }
  UpdateProcessing(start);
  return 0;
}

bool FakeAudioCaptureModule::Recording() const {
  webrtc::MutexLock lock(&mutex_);
  return recording_;
}

int32_t FakeAudioCaptureModule::InitSpeaker() {
  // No speaker, just playing from file. Return success.
  return 0;
}

bool FakeAudioCaptureModule::SpeakerIsInitialized() const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::InitMicrophone() {
  // No microphone, just playing from file. Return success.
  return 0;
}

bool FakeAudioCaptureModule::MicrophoneIsInitialized() const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SpeakerVolumeIsAvailable(bool* /*available*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SetSpeakerVolume(uint32_t /*volume*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SpeakerVolume(uint32_t* /*volume*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::MaxSpeakerVolume(
    uint32_t* /*max_volume*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::MinSpeakerVolume(
    uint32_t* /*min_volume*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::MicrophoneVolumeIsAvailable(
    bool* /*available*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SetMicrophoneVolume(uint32_t volume) {
  webrtc::MutexLock lock(&mutex_);
  current_mic_level_ = volume;
  return 0;
}

int32_t FakeAudioCaptureModule::MicrophoneVolume(uint32_t* volume) const {
  webrtc::MutexLock lock(&mutex_);
  *volume = current_mic_level_;
  return 0;
}

int32_t FakeAudioCaptureModule::MaxMicrophoneVolume(
    uint32_t* max_volume) const {
  *max_volume = kMaxVolume;
  return 0;
}

int32_t FakeAudioCaptureModule::MinMicrophoneVolume(
    uint32_t* /*min_volume*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SpeakerMuteIsAvailable(bool* /*available*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SetSpeakerMute(bool /*enable*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SpeakerMute(bool* /*enabled*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::MicrophoneMuteIsAvailable(bool* /*available*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::SetMicrophoneMute(bool /*enable*/) {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::MicrophoneMute(bool* /*enabled*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::StereoPlayoutIsAvailable(
    bool* available) const {
  // No recording device, just dropping audio. Stereo can be dropped just
  // as easily as mono.
  *available = true;
  return 0;
}

int32_t FakeAudioCaptureModule::SetStereoPlayout(bool /*enable*/) {
  // No recording device, just dropping audio. Stereo can be dropped just
  // as easily as mono.
  return 0;
}

int32_t FakeAudioCaptureModule::StereoPlayout(bool* /*enabled*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::StereoRecordingIsAvailable(
    bool* available) const {
  // Keep thing simple. No stereo recording.
  *available = false;
  return 0;
}

int32_t FakeAudioCaptureModule::SetStereoRecording(bool enable) {
  if (!enable) {
    return 0;
  }
  return -1;
}

int32_t FakeAudioCaptureModule::StereoRecording(bool* /*enabled*/) const {
  RTC_DCHECK_NOTREACHED();
  return 0;
}

int32_t FakeAudioCaptureModule::PlayoutDelay(uint16_t* delay_ms) const {
  // No delay since audio frames are dropped.
  *delay_ms = 0;
  return 0;
}

bool FakeAudioCaptureModule::Initialize() {
  // Set the send buffer samples high enough that it would not occur on the
  // remote side unless a packet containing a sample of that magnitude has been
  // sent to it. Note that the audio processing pipeline will likely distort the
  // original signal.
  SetSendBuffer(kHighSampleValue);
  return true;
}

void FakeAudioCaptureModule::SetSendBuffer(int value) {
  send_buffer_.fill(value);
}

void FakeAudioCaptureModule::ResetRecBuffer() {
  rec_buffer_.fill(0);
}

bool FakeAudioCaptureModule::CheckRecBuffer(int value) {
  for (Sample sample : rec_buffer_) {
    if (sample >= value)
      return true;
  }
  return false;
}

bool FakeAudioCaptureModule::ShouldStartProcessing() {
  return recording_ || playing_;
}

void FakeAudioCaptureModule::UpdateProcessing(bool start) {
  RTC_DCHECK(initialized_);
  if (start) {
    RTC_DCHECK(process_thread_);
    process_thread_->PostTask([this] { StartProcessP(); });
  }
}

void FakeAudioCaptureModule::StartProcessP() {
  RTC_DCHECK_RUN_ON(&process_thread_checker_);
  {
    webrtc::MutexLock lock(&mutex_);
    if (started_) {
      // Already started.
      return;
    }
  }
  ProcessFrameP();
}

void FakeAudioCaptureModule::ProcessFrameP() {
  RTC_DCHECK_RUN_ON(&process_thread_checker_);
  {
    webrtc::MutexLock lock(&mutex_);
    if (!recording_ && !playing_) {
      started_ = false;
      return;
    }
    if (!started_) {
      next_frame_time_ = webrtc::TimeMillis();
      started_ = true;
    }

    // Receive and send frames every kTimePerFrameMs.
    if (playing_) {
      ReceiveFrameP();
    }
    if (recording_) {
      SendFrameP();
    }
  }

  next_frame_time_ += kTimePerFrameMs;
  const int64_t current_time = webrtc::TimeMillis();
  const int64_t wait_time =
      (next_frame_time_ > current_time) ? next_frame_time_ - current_time : 0;
  process_thread_->PostDelayedTask([this] { ProcessFrameP(); },
                                   TimeDelta::Millis(wait_time));
}

void FakeAudioCaptureModule::ReceiveFrameP() {
  RTC_DCHECK_RUN_ON(&process_thread_checker_);
  if (!audio_callback_) {
    return;
  }
  ResetRecBuffer();
  size_t nSamplesOut = 0;
  int64_t elapsed_time_ms = 0;
  int64_t ntp_time_ms = 0;
  if (audio_callback_->NeedMorePlayData(kNumberSamples, kNumberBytesPerSample,
                                        kNumberOfChannels, kSamplesPerSecond,
                                        rec_buffer_.data(), nSamplesOut,
                                        &elapsed_time_ms, &ntp_time_ms) != 0) {
    RTC_DCHECK_NOTREACHED();
  }
  RTC_CHECK(nSamplesOut == kNumberSamples);

  // The SetBuffer() function ensures that after decoding, the audio buffer
  // should contain samples of similar magnitude (there is likely to be some
  // distortion due to the audio pipeline). If one sample is detected to
  // have the same or greater magnitude somewhere in the frame, an actual frame
  // has been received from the remote side (i.e. faked frames are not being
  // pulled).
  if (CheckRecBuffer(kHighSampleValue)) {
    ++frames_received_;
  }
}

void FakeAudioCaptureModule::SendFrameP() {
  RTC_DCHECK_RUN_ON(&process_thread_checker_);
  if (!audio_callback_) {
    return;
  }
  bool key_pressed = false;
  uint32_t current_mic_level = current_mic_level_;
  if (audio_callback_->RecordedDataIsAvailable(
          send_buffer_.data(), kNumberSamples, kNumberBytesPerSample,
          kNumberOfChannels, kSamplesPerSecond, kTotalDelayMs, kClockDriftMs,
          current_mic_level, key_pressed, current_mic_level) != 0) {
    RTC_DCHECK_NOTREACHED();
  }
  current_mic_level_ = current_mic_level;
}
