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

#include <assert.h>

#include "audio_buffer.h"
#include "critical_section_wrapper.h"
#include "echo_cancellation_impl.h"
#include "echo_control_mobile_impl.h"
#include "file_wrapper.h"
#include "high_pass_filter_impl.h"
#include "gain_control_impl.h"
#include "level_estimator_impl.h"
#include "module_common_types.h"
#include "noise_suppression_impl.h"
#include "processing_component.h"
#include "splitting_filter.h"
#include "voice_detection_impl.h"

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
// Files generated at build-time by the protobuf compiler.
#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
#include "external/webrtc/src/modules/audio_processing/debug.pb.h"
#else
#include "webrtc/audio_processing/debug.pb.h"
#endif
#endif  // WEBRTC_AUDIOPROC_DEBUG_DUMP

namespace webrtc {
AudioProcessing* AudioProcessing::Create(int id) {

  AudioProcessingImpl* apm = new AudioProcessingImpl(id);
  if (apm->Initialize() != kNoError) {
    delete apm;
    apm = NULL;
  }

  return apm;
}

void AudioProcessing::Destroy(AudioProcessing* apm) {
  delete static_cast<AudioProcessingImpl*>(apm);
}

AudioProcessingImpl::AudioProcessingImpl(int id)
    : id_(id),
      echo_cancellation_(NULL),
      echo_control_mobile_(NULL),
      gain_control_(NULL),
      high_pass_filter_(NULL),
      level_estimator_(NULL),
      noise_suppression_(NULL),
      voice_detection_(NULL),
      crit_(CriticalSectionWrapper::CreateCriticalSection()),
      render_audio_(NULL),
      capture_audio_(NULL),
#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
      debug_file_(FileWrapper::Create()),
      event_msg_(new audioproc::Event()),
#endif
      sample_rate_hz_(kSampleRate16kHz),
      split_sample_rate_hz_(kSampleRate16kHz),
      samples_per_channel_(sample_rate_hz_ / 100),
      stream_delay_ms_(0),
      delay_offset_ms_(0),
      was_stream_delay_set_(false),
      num_reverse_channels_(1),
      num_input_channels_(1),
      num_output_channels_(1) {

  echo_cancellation_ = new EchoCancellationImpl(this);
  component_list_.push_back(echo_cancellation_);

  echo_control_mobile_ = new EchoControlMobileImpl(this);
  component_list_.push_back(echo_control_mobile_);

  gain_control_ = new GainControlImpl(this);
  component_list_.push_back(gain_control_);

  high_pass_filter_ = new HighPassFilterImpl(this);
  component_list_.push_back(high_pass_filter_);

  level_estimator_ = new LevelEstimatorImpl(this);
  component_list_.push_back(level_estimator_);

  noise_suppression_ = new NoiseSuppressionImpl(this);
  component_list_.push_back(noise_suppression_);

  voice_detection_ = new VoiceDetectionImpl(this);
  component_list_.push_back(voice_detection_);
}

AudioProcessingImpl::~AudioProcessingImpl() {
  while (!component_list_.empty()) {
    ProcessingComponent* component = component_list_.front();
    component->Destroy();
    delete component;
    component_list_.pop_front();
  }

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
  if (debug_file_->Open()) {
    debug_file_->CloseFile();
  }
#endif

  delete crit_;
  crit_ = NULL;

  if (render_audio_) {
    delete render_audio_;
    render_audio_ = NULL;
  }

  if (capture_audio_) {
    delete capture_audio_;
    capture_audio_ = NULL;
  }
}

CriticalSectionWrapper* AudioProcessingImpl::crit() const {
  return crit_;
}

int AudioProcessingImpl::split_sample_rate_hz() const {
  return split_sample_rate_hz_;
}

int AudioProcessingImpl::Initialize() {
  CriticalSectionScoped crit_scoped(crit_);
  return InitializeLocked();
}

int AudioProcessingImpl::InitializeLocked() {
  if (render_audio_ != NULL) {
    delete render_audio_;
    render_audio_ = NULL;
  }

  if (capture_audio_ != NULL) {
    delete capture_audio_;
    capture_audio_ = NULL;
  }

  render_audio_ = new AudioBuffer(num_reverse_channels_,
                                  samples_per_channel_);
  capture_audio_ = new AudioBuffer(num_input_channels_,
                                   samples_per_channel_);

  was_stream_delay_set_ = false;

  // Initialize all components.
  std::list<ProcessingComponent*>::iterator it;
  for (it = component_list_.begin(); it != component_list_.end(); it++) {
    int err = (*it)->Initialize();
    if (err != kNoError) {
      return err;
    }
  }

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
  if (debug_file_->Open()) {
    int err = WriteInitMessage();
    if (err != kNoError) {
      return err;
    }
  }
#endif

  return kNoError;
}

int AudioProcessingImpl::set_sample_rate_hz(int rate) {
  CriticalSectionScoped crit_scoped(crit_);
  if (rate != kSampleRate8kHz &&
      rate != kSampleRate16kHz &&
      rate != kSampleRate32kHz) {
    return kBadParameterError;
  }

  sample_rate_hz_ = rate;
  samples_per_channel_ = rate / 100;

  if (sample_rate_hz_ == kSampleRate32kHz) {
    split_sample_rate_hz_ = kSampleRate16kHz;
  } else {
    split_sample_rate_hz_ = sample_rate_hz_;
  }

  return InitializeLocked();
}

int AudioProcessingImpl::sample_rate_hz() const {
  return sample_rate_hz_;
}

int AudioProcessingImpl::set_num_reverse_channels(int channels) {
  CriticalSectionScoped crit_scoped(crit_);
  // Only stereo supported currently.
  if (channels > 2 || channels < 1) {
    return kBadParameterError;
  }

  num_reverse_channels_ = channels;

  return InitializeLocked();
}

int AudioProcessingImpl::num_reverse_channels() const {
  return num_reverse_channels_;
}

int AudioProcessingImpl::set_num_channels(
    int input_channels,
    int output_channels) {
  CriticalSectionScoped crit_scoped(crit_);
  if (output_channels > input_channels) {
    return kBadParameterError;
  }

  // Only stereo supported currently.
  if (input_channels > 2 || input_channels < 1) {
    return kBadParameterError;
  }

  if (output_channels > 2 || output_channels < 1) {
    return kBadParameterError;
  }

  num_input_channels_ = input_channels;
  num_output_channels_ = output_channels;

  return InitializeLocked();
}

int AudioProcessingImpl::num_input_channels() const {
  return num_input_channels_;
}

int AudioProcessingImpl::num_output_channels() const {
  return num_output_channels_;
}

int AudioProcessingImpl::ProcessStream(AudioFrame* frame) {
  CriticalSectionScoped crit_scoped(crit_);
  int err = kNoError;

  if (frame == NULL) {
    return kNullPointerError;
  }

  if (frame->_frequencyInHz != sample_rate_hz_) {
    return kBadSampleRateError;
  }

  if (frame->_audioChannel != num_input_channels_) {
    return kBadNumberChannelsError;
  }

  if (frame->_payloadDataLengthInSamples != samples_per_channel_) {
    return kBadDataLengthError;
  }

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
  if (debug_file_->Open()) {
    event_msg_->set_type(audioproc::Event::STREAM);
    audioproc::Stream* msg = event_msg_->mutable_stream();
    const size_t data_size = sizeof(int16_t) *
                             frame->_payloadDataLengthInSamples *
                             frame->_audioChannel;
    msg->set_input_data(frame->_payloadData, data_size);
    msg->set_delay(stream_delay_ms_);
    msg->set_drift(echo_cancellation_->stream_drift_samples());
    msg->set_level(gain_control_->stream_analog_level());
  }
#endif

  capture_audio_->DeinterleaveFrom(frame);

  // TODO(ajm): experiment with mixing and AEC placement.
  if (num_output_channels_ < num_input_channels_) {
    capture_audio_->Mix(num_output_channels_);
    frame->_audioChannel = num_output_channels_;
  }

  bool data_changed = stream_data_changed();
  if (analysis_needed(data_changed)) {
    for (int i = 0; i < num_output_channels_; i++) {
      // Split into a low and high band.
      SplittingFilterAnalysis(capture_audio_->data(i),
                              capture_audio_->low_pass_split_data(i),
                              capture_audio_->high_pass_split_data(i),
                              capture_audio_->analysis_filter_state1(i),
                              capture_audio_->analysis_filter_state2(i));
    }
  }

  err = high_pass_filter_->ProcessCaptureAudio(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  err = gain_control_->AnalyzeCaptureAudio(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  err = echo_cancellation_->ProcessCaptureAudio(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  if (echo_control_mobile_->is_enabled() &&
      noise_suppression_->is_enabled()) {
    capture_audio_->CopyLowPassToReference();
  }

  err = noise_suppression_->ProcessCaptureAudio(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  err = echo_control_mobile_->ProcessCaptureAudio(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  err = voice_detection_->ProcessCaptureAudio(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  err = gain_control_->ProcessCaptureAudio(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  if (synthesis_needed(data_changed)) {
    for (int i = 0; i < num_output_channels_; i++) {
      // Recombine low and high bands.
      SplittingFilterSynthesis(capture_audio_->low_pass_split_data(i),
                               capture_audio_->high_pass_split_data(i),
                               capture_audio_->data(i),
                               capture_audio_->synthesis_filter_state1(i),
                               capture_audio_->synthesis_filter_state2(i));
    }
  }

  // The level estimator operates on the recombined data.
  err = level_estimator_->ProcessStream(capture_audio_);
  if (err != kNoError) {
    return err;
  }

  capture_audio_->InterleaveTo(frame, data_changed);

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
  if (debug_file_->Open()) {
    audioproc::Stream* msg = event_msg_->mutable_stream();
    const size_t data_size = sizeof(int16_t) *
                             frame->_payloadDataLengthInSamples *
                             frame->_audioChannel;
    msg->set_output_data(frame->_payloadData, data_size);
    err = WriteMessageToDebugFile();
    if (err != kNoError) {
      return err;
    }
  }
#endif

  was_stream_delay_set_ = false;
  return kNoError;
}

int AudioProcessingImpl::AnalyzeReverseStream(AudioFrame* frame) {
  CriticalSectionScoped crit_scoped(crit_);
  int err = kNoError;

  if (frame == NULL) {
    return kNullPointerError;
  }

  if (frame->_frequencyInHz != sample_rate_hz_) {
    return kBadSampleRateError;
  }

  if (frame->_audioChannel != num_reverse_channels_) {
    return kBadNumberChannelsError;
  }

  if (frame->_payloadDataLengthInSamples != samples_per_channel_) {
    return kBadDataLengthError;
  }

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
  if (debug_file_->Open()) {
    event_msg_->set_type(audioproc::Event::REVERSE_STREAM);
    audioproc::ReverseStream* msg = event_msg_->mutable_reverse_stream();
    const size_t data_size = sizeof(int16_t) *
                             frame->_payloadDataLengthInSamples *
                             frame->_audioChannel;
    msg->set_data(frame->_payloadData, data_size);
    err = WriteMessageToDebugFile();
    if (err != kNoError) {
      return err;
    }
  }
#endif

  render_audio_->DeinterleaveFrom(frame);

  // TODO(ajm): turn the splitting filter into a component?
  if (sample_rate_hz_ == kSampleRate32kHz) {
    for (int i = 0; i < num_reverse_channels_; i++) {
      // Split into low and high band.
      SplittingFilterAnalysis(render_audio_->data(i),
                              render_audio_->low_pass_split_data(i),
                              render_audio_->high_pass_split_data(i),
                              render_audio_->analysis_filter_state1(i),
                              render_audio_->analysis_filter_state2(i));
    }
  }

  // TODO(ajm): warnings possible from components?
  err = echo_cancellation_->ProcessRenderAudio(render_audio_);
  if (err != kNoError) {
    return err;
  }

  err = echo_control_mobile_->ProcessRenderAudio(render_audio_);
  if (err != kNoError) {
    return err;
  }

  err = gain_control_->ProcessRenderAudio(render_audio_);
  if (err != kNoError) {
    return err;
  }

  return err;  // TODO(ajm): this is for returning warnings; necessary?
}

int AudioProcessingImpl::set_stream_delay_ms(int delay) {
  was_stream_delay_set_ = true;
  delay += delay_offset_ms_;

  if (delay < 0) {
    return kBadParameterError;
  }

  // TODO(ajm): the max is rather arbitrarily chosen; investigate.
  if (delay > 500) {
    stream_delay_ms_ = 500;
    return kBadStreamParameterWarning;
  }

  stream_delay_ms_ = delay;
  return kNoError;
}

int AudioProcessingImpl::stream_delay_ms() const {
  return stream_delay_ms_;
}

bool AudioProcessingImpl::was_stream_delay_set() const {
  return was_stream_delay_set_;
}

void AudioProcessingImpl::set_delay_offset_ms(int offset) {
  CriticalSectionScoped crit_scoped(crit_);
  delay_offset_ms_ = offset;
}

int AudioProcessingImpl::delay_offset_ms() const {
  return delay_offset_ms_;
}

int AudioProcessingImpl::StartDebugRecording(
    const char filename[AudioProcessing::kMaxFilenameSize]) {
  CriticalSectionScoped crit_scoped(crit_);
  assert(kMaxFilenameSize == FileWrapper::kMaxFileNameSize);

  if (filename == NULL) {
    return kNullPointerError;
  }

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
  // Stop any ongoing recording.
  if (debug_file_->Open()) {
    if (debug_file_->CloseFile() == -1) {
      return kFileError;
    }
  }

  if (debug_file_->OpenFile(filename, false) == -1) {
    debug_file_->CloseFile();
    return kFileError;
  }

  int err = WriteInitMessage();
  if (err != kNoError) {
    return err;
  }
  return kNoError;
#else
  return kUnsupportedFunctionError;
#endif  // WEBRTC_AUDIOPROC_DEBUG_DUMP
}

int AudioProcessingImpl::StopDebugRecording() {
  CriticalSectionScoped crit_scoped(crit_);

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
  // We just return if recording hasn't started.
  if (debug_file_->Open()) {
    if (debug_file_->CloseFile() == -1) {
      return kFileError;
    }
  }
  return kNoError;
#else
  return kUnsupportedFunctionError;
#endif  // WEBRTC_AUDIOPROC_DEBUG_DUMP
}

EchoCancellation* AudioProcessingImpl::echo_cancellation() const {
  return echo_cancellation_;
}

EchoControlMobile* AudioProcessingImpl::echo_control_mobile() const {
  return echo_control_mobile_;
}

GainControl* AudioProcessingImpl::gain_control() const {
  return gain_control_;
}

HighPassFilter* AudioProcessingImpl::high_pass_filter() const {
  return high_pass_filter_;
}

LevelEstimator* AudioProcessingImpl::level_estimator() const {
  return level_estimator_;
}

NoiseSuppression* AudioProcessingImpl::noise_suppression() const {
  return noise_suppression_;
}

VoiceDetection* AudioProcessingImpl::voice_detection() const {
  return voice_detection_;
}

WebRtc_Word32 AudioProcessingImpl::ChangeUniqueId(const WebRtc_Word32 id) {
  CriticalSectionScoped crit_scoped(crit_);
  id_ = id;

  return kNoError;
}

bool AudioProcessingImpl::stream_data_changed() const {
  int enabled_count = 0;
  std::list<ProcessingComponent*>::const_iterator it;
  for (it = component_list_.begin(); it != component_list_.end(); it++) {
    if ((*it)->is_component_enabled()) {
      enabled_count++;
    }
  }

  // Data is unchanged if no components are enabled, or if only level_estimator_
  // or voice_detection_ is enabled.
  if (enabled_count == 0) {
    return false;
  } else if (enabled_count == 1) {
    if (level_estimator_->is_enabled() || voice_detection_->is_enabled()) {
      return false;
    }
  } else if (enabled_count == 2) {
    if (level_estimator_->is_enabled() && voice_detection_->is_enabled()) {
      return false;
    }
  }
  return true;
}

bool AudioProcessingImpl::synthesis_needed(bool stream_data_changed) const {
  return (stream_data_changed && sample_rate_hz_ == kSampleRate32kHz);
}

bool AudioProcessingImpl::analysis_needed(bool stream_data_changed) const {
  if (!stream_data_changed && !voice_detection_->is_enabled()) {
    // Only level_estimator_ is enabled.
    return false;
  } else if (sample_rate_hz_ == kSampleRate32kHz) {
    // Something besides level_estimator_ is enabled, and we have super-wb.
    return true;
  }
  return false;
}

#ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
int AudioProcessingImpl::WriteMessageToDebugFile() {
  int32_t size = event_msg_->ByteSize();
  if (size <= 0) {
    return kUnspecifiedError;
  }
#if defined(WEBRTC_BIG_ENDIAN)
  // TODO(ajm): Use little-endian "on the wire". For the moment, we can be
  //            pretty safe in assuming little-endian.
#endif

  if (!event_msg_->SerializeToString(&event_str_)) {
    return kUnspecifiedError;
  }

  // Write message preceded by its size.
  if (!debug_file_->Write(&size, sizeof(int32_t))) {
    return kFileError;
  }
  if (!debug_file_->Write(event_str_.data(), event_str_.length())) {
    return kFileError;
  }

  event_msg_->Clear();

  return 0;
}

int AudioProcessingImpl::WriteInitMessage() {
  event_msg_->set_type(audioproc::Event::INIT);
  audioproc::Init* msg = event_msg_->mutable_init();
  msg->set_sample_rate(sample_rate_hz_);
  msg->set_device_sample_rate(echo_cancellation_->device_sample_rate_hz());
  msg->set_num_input_channels(num_input_channels_);
  msg->set_num_output_channels(num_output_channels_);
  msg->set_num_reverse_channels(num_reverse_channels_);

  int err = WriteMessageToDebugFile();
  if (err != kNoError) {
    return err;
  }

  return kNoError;
}
#endif  // WEBRTC_AUDIOPROC_DEBUG_DUMP
}  // namespace webrtc
