/*
 *  Copyright (c) 2011 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 "level_estimator_impl.h"

#include <assert.h>
#include <math.h>
#include <string.h>

#include "audio_processing_impl.h"
#include "audio_buffer.h"
#include "critical_section_wrapper.h"

namespace webrtc {
namespace {

const double kMaxSquaredLevel = 32768.0 * 32768.0;

class Level {
 public:
  static const int kMinLevel = 127;

  Level()
    : sum_square_(0.0),
      sample_count_(0) {}
  ~Level() {}

  void Init() {
    sum_square_ = 0.0;
    sample_count_ = 0;
  }

  void Process(int16_t* data, int length) {
    assert(data != NULL);
    assert(length > 0);
    sum_square_ += SumSquare(data, length);
    sample_count_ += length;
  }

  void ProcessMuted(int length) {
    assert(length > 0);
    sample_count_ += length;
  }

  int RMS() {
    if (sample_count_ == 0 || sum_square_ == 0.0) {
      Init();
      return kMinLevel;
    }

    // Normalize by the max level.
    double rms = sum_square_ / (sample_count_ * kMaxSquaredLevel);
    // 20log_10(x^0.5) = 10log_10(x)
    rms = 10 * log10(rms);
    if (rms > 0)
      rms = 0;
    else if (rms < -kMinLevel)
      rms = -kMinLevel;

    rms = -rms;
    Init();
    return static_cast<int>(rms + 0.5);
  }

 private:
  static double SumSquare(int16_t* data, int length) {
    double sum_square = 0.0;
    for (int i = 0; i < length; ++i) {
      double data_d = static_cast<double>(data[i]);
      sum_square += data_d * data_d;
    }
    return sum_square;
  }

  double sum_square_;
  int sample_count_;
};
}  // namespace

LevelEstimatorImpl::LevelEstimatorImpl(const AudioProcessingImpl* apm)
  : ProcessingComponent(apm),
    apm_(apm) {}

LevelEstimatorImpl::~LevelEstimatorImpl() {}

int LevelEstimatorImpl::ProcessStream(AudioBuffer* audio) {
  if (!is_component_enabled()) {
    return apm_->kNoError;
  }

  Level* level = static_cast<Level*>(handle(0));
  if (audio->is_muted()) {
    level->ProcessMuted(audio->samples_per_channel());
    return apm_->kNoError;
  }

  int16_t* mixed_data = audio->data(0);
  if (audio->num_channels() > 1) {
    audio->CopyAndMix(1);
    mixed_data = audio->mixed_data(0);
  }

  level->Process(mixed_data, audio->samples_per_channel());

  return apm_->kNoError;
}

int LevelEstimatorImpl::Enable(bool enable) {
  CriticalSectionScoped crit_scoped(*apm_->crit());
  return EnableComponent(enable);
}

bool LevelEstimatorImpl::is_enabled() const {
  return is_component_enabled();
}

int LevelEstimatorImpl::RMS() {
  if (!is_component_enabled()) {
    return apm_->kNotEnabledError;
  }

  Level* level = static_cast<Level*>(handle(0));
  return level->RMS();
}

int LevelEstimatorImpl::get_version(char* version,
                                    int version_len_bytes) const {
  // An empty string is used to indicate no version information.
  memset(version, 0, version_len_bytes);
  return apm_->kNoError;
}

void* LevelEstimatorImpl::CreateHandle() const {
  return new Level;
}

int LevelEstimatorImpl::DestroyHandle(void* handle) const {
  assert(handle != NULL);
  Level* level = static_cast<Level*>(handle);
  delete level;
  return apm_->kNoError;
}

int LevelEstimatorImpl::InitializeHandle(void* handle) const {
  assert(handle != NULL);
  Level* level = static_cast<Level*>(handle);
  level->Init();

  return apm_->kNoError;
}

int LevelEstimatorImpl::ConfigureHandle(void* /*handle*/) const {
  return apm_->kNoError;
}

int LevelEstimatorImpl::num_handles_required() const {
  return 1;
}

int LevelEstimatorImpl::GetHandleError(void* handle) const {
  // The component has no detailed errors.
  assert(handle != NULL);
  return apm_->kUnspecifiedError;
}
}  // namespace webrtc