/*
 *  Copyright 2024 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 "api/audio/audio_view.h"

#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
#include <vector>

#include "test/gtest.h"

namespace webrtc {

namespace {

constexpr const float kFloatStepIncrease = 0.5f;
constexpr const int16_t kIntStepIncrease = 1;

template <typename T>
void Increment(float& t) {
  t += kFloatStepIncrease;
}

template <typename T>
void Increment(int16_t& t) {
  t += kIntStepIncrease;
}

// Fills a given buffer with monotonically increasing values.
template <typename T>
void FillBuffer(std::span<T> buffer) {
  T value = {};
  for (T& t : buffer) {
    Increment<T>(value);
    t = value;
  }
}

}  // namespace

TEST(AudioViewTest, MonoView) {
  const size_t kArraySize = 100u;
  int16_t arr[kArraySize];
  FillBuffer(std::span<int16_t>(arr));

  MonoView<int16_t> mono(arr);
  MonoView<const int16_t> const_mono(arr);
  EXPECT_EQ(mono.size(), kArraySize);
  EXPECT_EQ(const_mono.size(), kArraySize);
  EXPECT_EQ(&mono[0], &const_mono[0]);
  EXPECT_EQ(mono[0], arr[0]);

  EXPECT_EQ(1u, NumChannels(mono));
  EXPECT_EQ(1u, NumChannels(const_mono));
  EXPECT_EQ(100u, SamplesPerChannel(mono));
  EXPECT_TRUE(IsMono(mono));
  EXPECT_TRUE(IsMono(const_mono));
}

TEST(AudioViewTest, InterleavedView) {
  const size_t kArraySize = 100u;
  int16_t arr[kArraySize];
  FillBuffer(std::span<int16_t>(arr));

  InterleavedView<int16_t> interleaved(arr, kArraySize, 1);
  EXPECT_EQ(NumChannels(interleaved), 1u);
  EXPECT_TRUE(IsMono(interleaved));
  EXPECT_EQ(SamplesPerChannel(interleaved), kArraySize);
  EXPECT_EQ(interleaved.AsMono().size(), std::size(arr));
  EXPECT_EQ(interleaved.AsMono().data(), std::data(arr));
  EXPECT_EQ(interleaved.AsMono().size(), interleaved.data().size());
  EXPECT_EQ(interleaved.AsMono().data(), interleaved.data().data());

  // Basic iterator test.
  int i = 0;
  for (auto s : interleaved) {
    EXPECT_EQ(s, arr[i++]);
  }

  interleaved = InterleavedView<int16_t>(arr, kArraySize / 2, 2);
  InterleavedView<const int16_t> const_interleaved(arr, 50, 2);
  EXPECT_EQ(NumChannels(interleaved), 2u);
  EXPECT_EQ(NumChannels(const_interleaved), 2u);
  EXPECT_EQ(&const_interleaved[0], &interleaved[0]);
  EXPECT_TRUE(!IsMono(interleaved));
  EXPECT_TRUE(!IsMono(const_interleaved));
  EXPECT_EQ(SamplesPerChannel(interleaved), 50u);
  EXPECT_EQ(SamplesPerChannel(const_interleaved), 50u);

  interleaved = InterleavedView<int16_t>(arr, 4);
  EXPECT_EQ(NumChannels(interleaved), 4u);
  InterleavedView<const int16_t> const_interleaved2(interleaved);
  EXPECT_EQ(NumChannels(const_interleaved2), 4u);
  EXPECT_EQ(SamplesPerChannel(interleaved), 25u);

  const_interleaved2 = interleaved;
  EXPECT_EQ(NumChannels(const_interleaved2), 4u);
  EXPECT_EQ(&const_interleaved2[0], &interleaved[0]);
}

TEST(AudioViewTest, DeinterleavedView) {
  const size_t kArraySize = 100u;
  int16_t arr[kArraySize] = {};
  DeinterleavedView<int16_t> di(arr, 10, 10);
  DeinterleavedView<const int16_t> const_di(arr, 10, 10);
  EXPECT_EQ(NumChannels(di), 10u);
  EXPECT_EQ(SamplesPerChannel(di), 10u);
  EXPECT_TRUE(!IsMono(di));
  EXPECT_EQ(const_di[5][1], di[5][1]);  // Spot check.
  // For deinterleaved views, although they may hold multiple channels,
  // the AsMono() method is still available and returns the first channel
  // in the view.
  auto mono_ch = di.AsMono();
  EXPECT_EQ(NumChannels(mono_ch), 1u);
  EXPECT_EQ(SamplesPerChannel(mono_ch), 10u);
  // first channel should be same as mono.
  EXPECT_EQ(di[0].data(), mono_ch.data());
  EXPECT_EQ(di[0].size(), mono_ch.size());

  di = DeinterleavedView<int16_t>(arr, 50, 2);
  // Test assignment.
  const_di = di;
  EXPECT_EQ(&di.AsMono()[0], &const_di.AsMono()[0]);

  // Access the second channel in the deinterleaved view.
  // The start of the second channel should be directly after the first channel.
  // The memory width of each channel is held by the `stride()` member which
  // by default is the same value as samples per channel.
  mono_ch = di[1];
  EXPECT_EQ(SamplesPerChannel(mono_ch), 50u);
  EXPECT_EQ(&mono_ch[0], &arr[di.samples_per_channel()]);
}

TEST(AudioViewTest, CopySamples) {
  const size_t kArraySize = 100u;
  int16_t source_arr[kArraySize] = {};
  int16_t dest_arr[kArraySize] = {};
  FillBuffer(std::span<int16_t>(source_arr));

  InterleavedView<const int16_t> source(source_arr, 2);
  InterleavedView<int16_t> destination(dest_arr, 2);

  static_assert(IsInterleavedView(source) == IsInterleavedView(destination),
                "");

  // Values in `dest_arr` should all be 0, none of the values in `source_arr`
  // should be 0.
  for (size_t i = 0; i < kArraySize; ++i) {
    ASSERT_EQ(dest_arr[i], 0);
    ASSERT_NE(source_arr[i], 0);
  }

  CopySamples(destination, source);
  for (size_t i = 0; i < kArraySize; ++i) {
    ASSERT_EQ(dest_arr[i], source_arr[i]) << "i == " << i;
  }
}

TEST(AudioViewTest, DeinterleavedViewPointerArray) {
  // Create vectors of varying sizes to guarantee that they don't end up
  // aligned in memory.
  std::vector<float> v1(100), v2(200), v3(300), v4(400);
  std::vector<float*> channels = {&v1[0], &v2[0], &v3[0], &v4[0]};

  DeinterleavedView<float> di(channels, v1.size());
  EXPECT_EQ(NumChannels(di), channels.size());
  EXPECT_EQ(SamplesPerChannel(di), v1.size());
  EXPECT_EQ(di[0].data(), v1.data());
  EXPECT_EQ(di[1].data(), v2.data());
  EXPECT_EQ(di[2].data(), v3.data());
  EXPECT_EQ(di[3].data(), v4.data());

  // Test that the same thing works with T* const *.
  float* channel_array[] = {&v1[0], &v2[0], &v3[0], &v4[0]};
  di = DeinterleavedView<float>(channel_array, v1.size(),
                                std::size(channel_array));
  EXPECT_EQ(NumChannels(di), channels.size());
  EXPECT_EQ(SamplesPerChannel(di), v1.size());
  EXPECT_EQ(di[0].data(), v1.data());
  EXPECT_EQ(di[1].data(), v2.data());
  EXPECT_EQ(di[2].data(), v3.data());
  EXPECT_EQ(di[3].data(), v4.data());
}

}  // namespace webrtc
