/*
 *  Copyright (c) 2015 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 <memory>

#include "webrtc/common_audio/sparse_fir_filter.h"

#include "webrtc/base/arraysize.h"
#include "webrtc/common_audio/fir_filter.h"
#include "webrtc/test/gtest.h"

namespace webrtc {
namespace {

static const float kCoeffs[] = {0.2f, 0.3f, 0.5f, 0.7f, 0.11f};
static const float kInput[] =
    {1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f};

template <size_t N>
void VerifyOutput(const float (&expected_output)[N], const float (&output)[N]) {
  EXPECT_EQ(0, memcmp(expected_output, output, sizeof(output)));
}

}  // namespace

TEST(SparseFIRFilterTest, FilterAsIdentity) {
  const float kCoeff = 1.f;
  const size_t kNumCoeff = 1;
  const size_t kSparsity = 3;
  const size_t kOffset = 0;
  float output[arraysize(kInput)];
  SparseFIRFilter filter(&kCoeff, kNumCoeff, kSparsity, kOffset);
  filter.Filter(kInput, arraysize(kInput), output);
  VerifyOutput(kInput, output);
}

TEST(SparseFIRFilterTest, SameOutputForScalarCoefficientAndDifferentSparsity) {
  const float kCoeff = 2.f;
  const size_t kNumCoeff = 1;
  const size_t kLowSparsity = 1;
  const size_t kHighSparsity = 7;
  const size_t kOffset = 0;
  float low_sparsity_output[arraysize(kInput)];
  float high_sparsity_output[arraysize(kInput)];
  SparseFIRFilter low_sparsity_filter(&kCoeff,
                                      kNumCoeff,
                                      kLowSparsity,
                                      kOffset);
  SparseFIRFilter high_sparsity_filter(&kCoeff,
                                       kNumCoeff,
                                       kHighSparsity,
                                       kOffset);
  low_sparsity_filter.Filter(kInput, arraysize(kInput), low_sparsity_output);
  high_sparsity_filter.Filter(kInput, arraysize(kInput), high_sparsity_output);
  VerifyOutput(low_sparsity_output, high_sparsity_output);
}

TEST(SparseFIRFilterTest, FilterUsedAsScalarMultiplication) {
  const float kCoeff = 5.f;
  const size_t kNumCoeff = 1;
  const size_t kSparsity = 5;
  const size_t kOffset = 0;
  float output[arraysize(kInput)];
  SparseFIRFilter filter(&kCoeff, kNumCoeff, kSparsity, kOffset);
  filter.Filter(kInput, arraysize(kInput), output);
  EXPECT_FLOAT_EQ(5.f, output[0]);
  EXPECT_FLOAT_EQ(20.f, output[3]);
  EXPECT_FLOAT_EQ(25.f, output[4]);
  EXPECT_FLOAT_EQ(50.f, output[arraysize(kInput) - 1]);
}

TEST(SparseFIRFilterTest, FilterUsedAsInputShifting) {
  const float kCoeff = 1.f;
  const size_t kNumCoeff = 1;
  const size_t kSparsity = 1;
  const size_t kOffset = 4;
  float output[arraysize(kInput)];
  SparseFIRFilter filter(&kCoeff, kNumCoeff, kSparsity, kOffset);
  filter.Filter(kInput, arraysize(kInput), output);
  EXPECT_FLOAT_EQ(0.f, output[0]);
  EXPECT_FLOAT_EQ(0.f, output[3]);
  EXPECT_FLOAT_EQ(1.f, output[4]);
  EXPECT_FLOAT_EQ(2.f, output[5]);
  EXPECT_FLOAT_EQ(6.f, output[arraysize(kInput) - 1]);
}

TEST(SparseFIRFilterTest, FilterUsedAsArbitraryWeighting) {
  const size_t kSparsity = 2;
  const size_t kOffset = 1;
  float output[arraysize(kInput)];
  SparseFIRFilter filter(kCoeffs, arraysize(kCoeffs), kSparsity, kOffset);
  filter.Filter(kInput, arraysize(kInput), output);
  EXPECT_FLOAT_EQ(0.f, output[0]);
  EXPECT_FLOAT_EQ(0.9f, output[3]);
  EXPECT_FLOAT_EQ(1.4f, output[4]);
  EXPECT_FLOAT_EQ(2.4f, output[5]);
  EXPECT_FLOAT_EQ(8.61f, output[arraysize(kInput) - 1]);
}

TEST(SparseFIRFilterTest, FilterInLengthLesserOrEqualToCoefficientsLength) {
  const size_t kSparsity = 1;
  const size_t kOffset = 0;
  float output[arraysize(kInput)];
  SparseFIRFilter filter(kCoeffs, arraysize(kCoeffs), kSparsity, kOffset);
  filter.Filter(kInput, 2, output);
  EXPECT_FLOAT_EQ(0.2f, output[0]);
  EXPECT_FLOAT_EQ(0.7f, output[1]);
}

TEST(SparseFIRFilterTest, MultipleFilterCalls) {
  const size_t kSparsity = 1;
  const size_t kOffset = 0;
  float output[arraysize(kInput)];
  SparseFIRFilter filter(kCoeffs, arraysize(kCoeffs), kSparsity, kOffset);
  filter.Filter(kInput, 2, output);
  EXPECT_FLOAT_EQ(0.2f, output[0]);
  EXPECT_FLOAT_EQ(0.7f, output[1]);
  filter.Filter(kInput, 2, output);
  EXPECT_FLOAT_EQ(1.3f, output[0]);
  EXPECT_FLOAT_EQ(2.4f, output[1]);
  filter.Filter(kInput, 2, output);
  EXPECT_FLOAT_EQ(2.81f, output[0]);
  EXPECT_FLOAT_EQ(2.62f, output[1]);
  filter.Filter(kInput, 2, output);
  EXPECT_FLOAT_EQ(2.81f, output[0]);
  EXPECT_FLOAT_EQ(2.62f, output[1]);
  filter.Filter(&kInput[3], 3, output);
  EXPECT_FLOAT_EQ(3.41f, output[0]);
  EXPECT_FLOAT_EQ(4.12f, output[1]);
  EXPECT_FLOAT_EQ(6.21f, output[2]);
  filter.Filter(&kInput[3], 3, output);
  EXPECT_FLOAT_EQ(8.12f, output[0]);
  EXPECT_FLOAT_EQ(9.14f, output[1]);
  EXPECT_FLOAT_EQ(9.45f, output[2]);
}

TEST(SparseFIRFilterTest, VerifySampleBasedVsBlockBasedFiltering) {
  const size_t kSparsity = 3;
  const size_t kOffset = 1;
  float output_block_based[arraysize(kInput)];
  SparseFIRFilter filter_block(kCoeffs,
                               arraysize(kCoeffs),
                               kSparsity,
                               kOffset);
  filter_block.Filter(kInput, arraysize(kInput), output_block_based);
  float output_sample_based[arraysize(kInput)];
  SparseFIRFilter filter_sample(kCoeffs,
                                arraysize(kCoeffs),
                                kSparsity,
                                kOffset);
  for (size_t i = 0; i < arraysize(kInput); ++i)
    filter_sample.Filter(&kInput[i], 1, &output_sample_based[i]);
  VerifyOutput(output_block_based, output_sample_based);
}

TEST(SparseFIRFilterTest, SimpleHighPassFilter) {
  const size_t kSparsity = 2;
  const size_t kOffset = 2;
  const float kHPCoeffs[] = {1.f, -1.f};
  const float kConstantInput[] =
      {1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f};
  float output[arraysize(kConstantInput)];
  SparseFIRFilter filter(kHPCoeffs, arraysize(kHPCoeffs), kSparsity, kOffset);
  filter.Filter(kConstantInput, arraysize(kConstantInput), output);
  EXPECT_FLOAT_EQ(0.f, output[0]);
  EXPECT_FLOAT_EQ(0.f, output[1]);
  EXPECT_FLOAT_EQ(1.f, output[2]);
  EXPECT_FLOAT_EQ(1.f, output[3]);
  for (size_t i = kSparsity + kOffset; i < arraysize(kConstantInput); ++i)
    EXPECT_FLOAT_EQ(0.f, output[i]);
}

TEST(SparseFIRFilterTest, SimpleLowPassFilter) {
  const size_t kSparsity = 2;
  const size_t kOffset = 2;
  const float kLPCoeffs[] = {1.f, 1.f};
  const float kHighFrequencyInput[] =
      {1.f, 1.f, -1.f, -1.f, 1.f, 1.f, -1.f, -1.f, 1.f, 1.f};
  float output[arraysize(kHighFrequencyInput)];
  SparseFIRFilter filter(kLPCoeffs, arraysize(kLPCoeffs), kSparsity, kOffset);
  filter.Filter(kHighFrequencyInput, arraysize(kHighFrequencyInput), output);
  EXPECT_FLOAT_EQ(0.f, output[0]);
  EXPECT_FLOAT_EQ(0.f, output[1]);
  EXPECT_FLOAT_EQ(1.f, output[2]);
  EXPECT_FLOAT_EQ(1.f, output[3]);
  for (size_t i = kSparsity + kOffset; i < arraysize(kHighFrequencyInput); ++i)
    EXPECT_FLOAT_EQ(0.f, output[i]);
}

TEST(SparseFIRFilterTest, SameOutputWhenSwappedCoefficientsAndInput) {
  const size_t kSparsity = 1;
  const size_t kOffset = 0;
  float output[arraysize(kCoeffs)];
  float output_swapped[arraysize(kCoeffs)];
  SparseFIRFilter filter(kCoeffs, arraysize(kCoeffs), kSparsity, kOffset);
  // Use arraysize(kCoeffs) for in_length to get same-length outputs.
  filter.Filter(kInput, arraysize(kCoeffs), output);
  SparseFIRFilter filter_swapped(kInput,
                                 arraysize(kCoeffs),
                                 kSparsity,
                                 kOffset);
  filter_swapped.Filter(kCoeffs, arraysize(kCoeffs), output_swapped);
  VerifyOutput(output, output_swapped);
}

TEST(SparseFIRFilterTest, SameOutputAsFIRFilterWhenSparsityOneAndOffsetZero) {
  const size_t kSparsity = 1;
  const size_t kOffset = 0;
  float output[arraysize(kInput)];
  float sparse_output[arraysize(kInput)];
  std::unique_ptr<FIRFilter> filter(
      FIRFilter::Create(kCoeffs, arraysize(kCoeffs), arraysize(kInput)));
  SparseFIRFilter sparse_filter(kCoeffs,
                                arraysize(kCoeffs),
                                kSparsity,
                                kOffset);
  filter->Filter(kInput, arraysize(kInput), output);
  sparse_filter.Filter(kInput, arraysize(kInput), sparse_output);
  for (size_t i = 0; i < arraysize(kInput); ++i) {
    EXPECT_FLOAT_EQ(output[i], sparse_output[i]);
  }
}

}  // namespace webrtc
