/*
 *  Copyright (c) 2013 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 "modules/audio_device/fine_audio_buffer.h"

#include <cstdint>
#include <cstring>

#include "api/array_view.h"
#include "modules/audio_device/audio_device_buffer.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"

namespace webrtc {

FineAudioBuffer::FineAudioBuffer(AudioDeviceBuffer* audio_device_buffer)
    : audio_device_buffer_(audio_device_buffer),
      playout_samples_per_channel_10ms_(rtc::dchecked_cast<size_t>(
          audio_device_buffer->PlayoutSampleRate() * 10 / 1000)),
      record_samples_per_channel_10ms_(rtc::dchecked_cast<size_t>(
          audio_device_buffer->RecordingSampleRate() * 10 / 1000)),
      playout_channels_(audio_device_buffer->PlayoutChannels()),
      record_channels_(audio_device_buffer->RecordingChannels()) {
  RTC_DCHECK(audio_device_buffer_);
  RTC_DLOG(LS_INFO) << __FUNCTION__;
  if (IsReadyForPlayout()) {
    RTC_DLOG(LS_INFO) << "playout_samples_per_channel_10ms: "
                      << playout_samples_per_channel_10ms_;
    RTC_DLOG(LS_INFO) << "playout_channels: " << playout_channels_;
  }
  if (IsReadyForRecord()) {
    RTC_DLOG(LS_INFO) << "record_samples_per_channel_10ms: "
                      << record_samples_per_channel_10ms_;
    RTC_DLOG(LS_INFO) << "record_channels: " << record_channels_;
  }
}

FineAudioBuffer::~FineAudioBuffer() {
  RTC_DLOG(LS_INFO) << __FUNCTION__;
}

void FineAudioBuffer::ResetPlayout() {
  playout_buffer_.Clear();
}

void FineAudioBuffer::ResetRecord() {
  record_buffer_.Clear();
}

bool FineAudioBuffer::IsReadyForPlayout() const {
  return playout_samples_per_channel_10ms_ > 0 && playout_channels_ > 0;
}

bool FineAudioBuffer::IsReadyForRecord() const {
  return record_samples_per_channel_10ms_ > 0 && record_channels_ > 0;
}

void FineAudioBuffer::GetPlayoutData(rtc::ArrayView<int16_t> audio_buffer,
                                     int playout_delay_ms) {
  RTC_DCHECK(IsReadyForPlayout());
  // Ask WebRTC for new data in chunks of 10ms until we have enough to
  // fulfill the request. It is possible that the buffer already contains
  // enough samples from the last round.
  while (playout_buffer_.size() < audio_buffer.size()) {
    // Get 10ms decoded audio from WebRTC. The ADB knows about number of
    // channels; hence we can ask for number of samples per channel here.
    if (audio_device_buffer_->RequestPlayoutData(
            playout_samples_per_channel_10ms_) ==
        static_cast<int32_t>(playout_samples_per_channel_10ms_)) {
      // Append 10ms to the end of the local buffer taking number of channels
      // into account.
      const size_t num_elements_10ms =
          playout_channels_ * playout_samples_per_channel_10ms_;
      const size_t written_elements = playout_buffer_.AppendData(
          num_elements_10ms, [&](rtc::ArrayView<int16_t> buf) {
            const size_t samples_per_channel_10ms =
                audio_device_buffer_->GetPlayoutData(buf.data());
            return playout_channels_ * samples_per_channel_10ms;
          });
      RTC_DCHECK_EQ(num_elements_10ms, written_elements);
    } else {
      // Provide silence if AudioDeviceBuffer::RequestPlayoutData() fails.
      // Can e.g. happen when an AudioTransport has not been registered.
      const size_t num_bytes = audio_buffer.size() * sizeof(int16_t);
      std::memset(audio_buffer.data(), 0, num_bytes);
      return;
    }
  }

  // Provide the requested number of bytes to the consumer.
  const size_t num_bytes = audio_buffer.size() * sizeof(int16_t);
  memcpy(audio_buffer.data(), playout_buffer_.data(), num_bytes);
  // Move remaining samples to start of buffer to prepare for next round.
  memmove(playout_buffer_.data(), playout_buffer_.data() + audio_buffer.size(),
          (playout_buffer_.size() - audio_buffer.size()) * sizeof(int16_t));
  playout_buffer_.SetSize(playout_buffer_.size() - audio_buffer.size());
  // Cache playout latency for usage in DeliverRecordedData();
  playout_delay_ms_ = playout_delay_ms;
}

void FineAudioBuffer::DeliverRecordedData(
    rtc::ArrayView<const int16_t> audio_buffer,
    int record_delay_ms,
    absl::optional<int64_t> capture_time_ns) {
  RTC_DCHECK(IsReadyForRecord());
  // Always append new data and grow the buffer when needed.
  record_buffer_.AppendData(audio_buffer.data(), audio_buffer.size());
  // Consume samples from buffer in chunks of 10ms until there is not
  // enough data left. The number of remaining samples in the cache is given by
  // the new size of the internal `record_buffer_`.
  const size_t num_elements_10ms =
      record_channels_ * record_samples_per_channel_10ms_;
  while (record_buffer_.size() >= num_elements_10ms) {
    audio_device_buffer_->SetRecordedBuffer(record_buffer_.data(),
                                            record_samples_per_channel_10ms_,
                                            capture_time_ns);
    audio_device_buffer_->SetVQEData(playout_delay_ms_, record_delay_ms);
    audio_device_buffer_->DeliverRecordedData();
    memmove(record_buffer_.data(), record_buffer_.data() + num_elements_10ms,
            (record_buffer_.size() - num_elements_10ms) * sizeof(int16_t));
    record_buffer_.SetSize(record_buffer_.size() - num_elements_10ms);
  }
}

}  // namespace webrtc
