/*
 *  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 <cstddef>
#include <cstdint>
#include <cstdio>
#include <span>
#include <string>

#include "modules/audio_coding/codecs/cng/webrtc_cng.h"
#include "rtc_base/buffer.h"
#include "rtc_base/checks.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"

namespace webrtc {

enum {
  kSidShortIntervalUpdate = 1,
  kSidNormalIntervalUpdate = 100,
  kSidLongIntervalUpdate = 10000
};

enum : size_t {
  kCNGNumParamsLow = 0,
  kCNGNumParamsNormal = 8,
  kCNGNumParamsHigh = WEBRTC_CNG_MAX_LPC_ORDER,
  kCNGNumParamsTooHigh = WEBRTC_CNG_MAX_LPC_ORDER + 1
};

enum { kNoSid, kForceSid };

class CngTest : public ::testing::Test {
 protected:
  void SetUp() override;

  void TestCngEncode(int sample_rate_hz, int quality);

  int16_t speech_data_[640];  // Max size of CNG internal buffers.
};

class CngDeathTest : public CngTest {};

void CngTest::SetUp() {
  FILE* input_file;
  const std::string file_name =
      test::ResourcePath("audio_coding/testfile32kHz", "pcm");
  input_file = fopen(file_name.c_str(), "rb");
  ASSERT_TRUE(input_file != nullptr);
  ASSERT_EQ(640, static_cast<int32_t>(
                     fread(speech_data_, sizeof(int16_t), 640, input_file)));
  fclose(input_file);
  input_file = nullptr;
}

void CngTest::TestCngEncode(int sample_rate_hz, int quality) {
  const size_t num_samples_10ms = CheckedDivExact(sample_rate_hz, 100);
  Buffer sid_data;

  ComfortNoiseEncoder cng_encoder(sample_rate_hz, kSidNormalIntervalUpdate,
                                  quality);
  EXPECT_EQ(0U, cng_encoder.Encode(
                    std::span<const int16_t>(speech_data_, num_samples_10ms),
                    kNoSid, &sid_data));
  EXPECT_EQ(static_cast<size_t>(quality + 1),
            cng_encoder.Encode(
                std::span<const int16_t>(speech_data_, num_samples_10ms),
                kForceSid, &sid_data));
}

#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
// Create CNG encoder, init with faulty values, free CNG encoder.
TEST_F(CngDeathTest, CngInitFail) {
  // Call with too few parameters.
  EXPECT_DEATH(
      {
        ComfortNoiseEncoder(8000, kSidNormalIntervalUpdate, kCNGNumParamsLow);
      },
      "");
  // Call with too many parameters.
  EXPECT_DEATH(
      {
        ComfortNoiseEncoder(8000, kSidNormalIntervalUpdate,
                            kCNGNumParamsTooHigh);
      },
      "");
}

// Encode Cng with too long input vector.
TEST_F(CngDeathTest, CngEncodeTooLong) {
  Buffer sid_data;

  // Create encoder.
  ComfortNoiseEncoder cng_encoder(8000, kSidNormalIntervalUpdate,
                                  kCNGNumParamsNormal);
  // Run encoder with too much data.
  EXPECT_DEATH(cng_encoder.Encode(std::span<const int16_t>(speech_data_, 641),
                                  kNoSid, &sid_data),
               "");
}
#endif  // GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)

TEST_F(CngTest, CngEncode8000) {
  TestCngEncode(8000, kCNGNumParamsNormal);
}

TEST_F(CngTest, CngEncode16000) {
  TestCngEncode(16000, kCNGNumParamsNormal);
}

TEST_F(CngTest, CngEncode32000) {
  TestCngEncode(32000, kCNGNumParamsHigh);
}

TEST_F(CngTest, CngEncode48000) {
  TestCngEncode(48000, kCNGNumParamsNormal);
}

TEST_F(CngTest, CngEncode64000) {
  TestCngEncode(64000, kCNGNumParamsNormal);
}

// Update SID parameters, for both 9 and 16 parameters.
TEST_F(CngTest, CngUpdateSid) {
  Buffer sid_data;

  // Create and initialize encoder and decoder.
  ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
                                  kCNGNumParamsNormal);
  ComfortNoiseDecoder cng_decoder;

  // Run normal Encode and UpdateSid.
  EXPECT_EQ(kCNGNumParamsNormal + 1,
            cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                               kForceSid, &sid_data));
  cng_decoder.UpdateSid(sid_data);

  // Reinit with new length.
  cng_encoder.Reset(16000, kSidNormalIntervalUpdate, kCNGNumParamsHigh);
  cng_decoder.Reset();

  // Expect 0 because of unstable parameters after switching length.
  EXPECT_EQ(0U, cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                                   kForceSid, &sid_data));
  EXPECT_EQ(
      kCNGNumParamsHigh + 1,
      cng_encoder.Encode(std::span<const int16_t>(speech_data_ + 160, 160),
                         kForceSid, &sid_data));
  cng_decoder.UpdateSid(
      std::span<const uint8_t>(sid_data.data(), kCNGNumParamsNormal + 1));
}

// Update SID parameters, with wrong parameters or without calling decode.
TEST_F(CngTest, CngUpdateSidErroneous) {
  Buffer sid_data;

  // Encode.
  ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
                                  kCNGNumParamsNormal);
  ComfortNoiseDecoder cng_decoder;
  EXPECT_EQ(kCNGNumParamsNormal + 1,
            cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                               kForceSid, &sid_data));

  // First run with valid parameters, then with too many CNG parameters.
  // The function will operate correctly by only reading the maximum number of
  // parameters, skipping the extra.
  EXPECT_EQ(kCNGNumParamsNormal + 1, sid_data.size());
  cng_decoder.UpdateSid(sid_data);

  // Make sure the input buffer is large enough. Since Encode() appends data, we
  // need to set the size manually only afterwards, or the buffer will be bigger
  // than anticipated.
  sid_data.SetSize(kCNGNumParamsTooHigh + 1);
  cng_decoder.UpdateSid(sid_data);
}

// Test to generate cng data, by forcing SID. Both normal and faulty condition.
TEST_F(CngTest, CngGenerate) {
  Buffer sid_data;
  int16_t out_data[640];

  // Create and initialize encoder and decoder.
  ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
                                  kCNGNumParamsNormal);
  ComfortNoiseDecoder cng_decoder;

  // Normal Encode.
  EXPECT_EQ(kCNGNumParamsNormal + 1,
            cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                               kForceSid, &sid_data));

  // Normal UpdateSid.
  cng_decoder.UpdateSid(sid_data);

  // Two normal Generate, one with new_period.
  EXPECT_TRUE(cng_decoder.Generate(std::span<int16_t>(out_data, 640), 1));
  EXPECT_TRUE(cng_decoder.Generate(std::span<int16_t>(out_data, 640), 0));

  // Call Genereate with too much data.
  EXPECT_FALSE(cng_decoder.Generate(std::span<int16_t>(out_data, 641), 0));
}

// Test automatic SID.
TEST_F(CngTest, CngAutoSid) {
  Buffer sid_data;

  // Create and initialize encoder and decoder.
  ComfortNoiseEncoder cng_encoder(16000, kSidNormalIntervalUpdate,
                                  kCNGNumParamsNormal);
  ComfortNoiseDecoder cng_decoder;

  // Normal Encode, 100 msec, where no SID data should be generated.
  for (int i = 0; i < 10; i++) {
    EXPECT_EQ(0U,
              cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                                 kNoSid, &sid_data));
  }

  // We have reached 100 msec, and SID data should be generated.
  EXPECT_EQ(kCNGNumParamsNormal + 1,
            cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                               kNoSid, &sid_data));
}

// Test automatic SID, with very short interval.
TEST_F(CngTest, CngAutoSidShort) {
  Buffer sid_data;

  // Create and initialize encoder and decoder.
  ComfortNoiseEncoder cng_encoder(16000, kSidShortIntervalUpdate,
                                  kCNGNumParamsNormal);
  ComfortNoiseDecoder cng_decoder;

  // First call will never generate SID, unless forced to.
  EXPECT_EQ(0U, cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                                   kNoSid, &sid_data));

  // Normal Encode, 100 msec, SID data should be generated all the time.
  for (int i = 0; i < 10; i++) {
    EXPECT_EQ(kCNGNumParamsNormal + 1,
              cng_encoder.Encode(std::span<const int16_t>(speech_data_, 160),
                                 kNoSid, &sid_data));
  }
}

}  // namespace webrtc
