/*
 *  Copyright (c) 2018 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_processing/agc2/biquad_filter.h"

#include <algorithm>
#include <array>
#include <cmath>

// TODO(bugs.webrtc.org/8948): Add when the issue is fixed.
// #include "test/fpe_observer.h"
#include "rtc_base/gunit.h"

namespace webrtc {
namespace test {
namespace {

constexpr size_t kFrameSize = 8;
constexpr size_t kNumFrames = 4;
using FloatArraySequence =
    std::array<std::array<float, kFrameSize>, kNumFrames>;

constexpr FloatArraySequence kBiQuadInputSeq = {
    {{{-87.166290f, -8.029022f, 101.619583f, -0.294296f, -5.825764f, -8.890625f,
       10.310432f, 54.845333f}},
     {{-64.647644f, -6.883945f, 11.059189f, -95.242538f, -108.870834f,
       11.024944f, 63.044102f, -52.709583f}},
     {{-32.350529f, -18.108028f, -74.022339f, -8.986874f, -1.525581f,
       103.705513f, 6.346226f, -14.319557f}},
     {{22.645832f, -64.597153f, 55.462521f, -109.393188f, 10.117825f,
       -40.019642f, -98.612228f, -8.330326f}}}};

// Generated via "B, A = scipy.signal.butter(2, 30/12000, btype='highpass')"
const BiQuadFilter::BiQuadCoefficients kBiQuadConfig = {
    {0.99446179f, -1.98892358f, 0.99446179f},
    {-1.98889291f, 0.98895425f}};

// Comparing to scipy. The expected output is generated as follows:
// zi = np.float32([0, 0])
// for i in range(4):
//   yn, zi = scipy.signal.lfilter(B, A, x[i], zi=zi)
//   print(yn)
constexpr FloatArraySequence kBiQuadOutputSeq = {
    {{{-86.68354497f, -7.02175351f, 102.10290352f, -0.37487333f, -5.87205847f,
       -8.85521608f, 10.33772563f, 54.51157181f}},
     {{-64.92531604f, -6.76395978f, 11.15534507f, -94.68073341f, -107.18177856f,
       13.24642474f, 64.84288941f, -50.97822629f}},
     {{-30.1579652f, -15.64850899f, -71.06662821f, -5.5883229f, 1.91175353f,
       106.5572003f, 8.57183046f, -12.06298473f}},
     {{24.84286614f, -62.18094158f, 57.91488056f, -106.65685933f, 13.38760103f,
       -36.60367134f, -94.44880104f, -3.59920354f}}}};

// Fail for every pair from two equally sized rtc::ArrayView<float> views such
// that their relative error is above a given threshold. If the expected value
// of a pair is 0, the tolerance is used to check the absolute error.
void ExpectNearRelative(rtc::ArrayView<const float> expected,
                        rtc::ArrayView<const float> computed,
                        const float tolerance) {
  // The relative error is undefined when the expected value is 0.
  // When that happens, check the absolute error instead. |safe_den| is used
  // below to implement such logic.
  auto safe_den = [](float x) { return (x == 0.f) ? 1.f : std::fabs(x); };
  ASSERT_EQ(expected.size(), computed.size());
  for (size_t i = 0; i < expected.size(); ++i) {
    const float abs_diff = std::fabs(expected[i] - computed[i]);
    // No failure when the values are equal.
    if (abs_diff == 0.f)
      continue;
    SCOPED_TRACE(i);
    SCOPED_TRACE(expected[i]);
    SCOPED_TRACE(computed[i]);
    EXPECT_LE(abs_diff / safe_den(expected[i]), tolerance);
  }
}

}  // namespace

TEST(BiQuadFilterTest, FilterNotInPlace) {
  BiQuadFilter filter;
  filter.Initialize(kBiQuadConfig);
  std::array<float, kFrameSize> samples;

  // TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed.
  // FloatingPointExceptionObserver fpe_observer;

  for (size_t i = 0; i < kNumFrames; ++i) {
    SCOPED_TRACE(i);
    filter.Process(kBiQuadInputSeq[i], samples);
    ExpectNearRelative(kBiQuadOutputSeq[i], samples, 2e-4f);
  }
}

TEST(BiQuadFilterTest, FilterInPlace) {
  BiQuadFilter filter;
  filter.Initialize(kBiQuadConfig);
  std::array<float, kFrameSize> samples;

  // TODO(https://bugs.webrtc.org/8948): Add when the issue is fixed.
  // FloatingPointExceptionObserver fpe_observer;

  for (size_t i = 0; i < kNumFrames; ++i) {
    SCOPED_TRACE(i);
    std::copy(kBiQuadInputSeq[i].begin(), kBiQuadInputSeq[i].end(),
              samples.begin());
    filter.Process({samples}, {samples});
    ExpectNearRelative(kBiQuadOutputSeq[i], samples, 2e-4f);
  }
}

TEST(BiQuadFilterTest, Reset) {
  BiQuadFilter filter;
  filter.Initialize(kBiQuadConfig);

  std::array<float, kFrameSize> samples1;
  for (size_t i = 0; i < kNumFrames; ++i) {
    filter.Process(kBiQuadInputSeq[i], samples1);
  }

  filter.Reset();
  std::array<float, kFrameSize> samples2;
  for (size_t i = 0; i < kNumFrames; ++i) {
    filter.Process(kBiQuadInputSeq[i], samples2);
  }

  EXPECT_EQ(samples1, samples2);
}

}  // namespace test
}  // namespace webrtc
