/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "AudioSegment.h"
#include <iostream>
#include "gtest/gtest.h"

using namespace mozilla;

namespace audio_segment {

/* Helper function to give us the maximum and minimum value that don't clip,
 * for a given sample format (integer or floating-point). */
template<typename T>
T GetLowValue();

template<typename T>
T GetHighValue();

template<typename T>
T GetSilentValue();

template<>
float GetLowValue<float>() {
  return -1.0;
}

template<>
int16_t GetLowValue<short>() {
  return -INT16_MAX;
}

template<>
float GetHighValue<float>() {
  return 1.0;
}

template<>
int16_t GetHighValue<short>() {
  return INT16_MAX;
}

template<>
float GetSilentValue() {
  return 0.0;
}

template<>
int16_t GetSilentValue() {
  return 0;
}


// Get an array of planar audio buffers that has the inverse of the index of the
// channel (1-indexed) as samples.
template<typename T>
const T* const* GetPlanarChannelArray(size_t aChannels, size_t aSize)
{
  T** channels = new T*[aChannels];
  for (size_t c = 0; c < aChannels; c++) {
    channels[c] = new T[aSize];
    for (size_t i = 0; i < aSize; i++) {
      channels[c][i] = FloatToAudioSample<T>(1. / (c + 1));
    }
  }
  return channels;
}

template<typename T>
void DeletePlanarChannelsArray(const T* const* aArrays, size_t aChannels)
{
  for (size_t channel = 0; channel < aChannels; channel++) {
    delete [] aArrays[channel];
  }
  delete [] aArrays;
}

template<typename T>
T** GetPlanarArray(size_t aChannels, size_t aSize)
{
  T** channels = new T*[aChannels];
  for (size_t c = 0; c < aChannels; c++) {
    channels[c] = new T[aSize];
    for (size_t i = 0; i < aSize; i++) {
      channels[c][i] = 0.0f;
    }
  }
  return channels;
}

template<typename T>
void DeletePlanarArray(T** aArrays, size_t aChannels)
{
  for (size_t channel = 0; channel < aChannels; channel++) {
    delete [] aArrays[channel];
  }
  delete [] aArrays;
}

// Get an array of audio samples that have the inverse of the index of the
// channel (1-indexed) as samples.
template<typename T>
const T* GetInterleavedChannelArray(size_t aChannels, size_t aSize)
{
  size_t sampleCount = aChannels * aSize;
  T* samples = new T[sampleCount];
  for (size_t i = 0; i < sampleCount; i++) {
    uint32_t channel = (i % aChannels) + 1;
    samples[i] = FloatToAudioSample<T>(1. / channel);
  }
  return samples;
}

template<typename T>
void DeleteInterleavedChannelArray(const T* aArray)
{
  delete [] aArray;
}

bool FuzzyEqual(float aLhs, float aRhs) {
  return std::abs(aLhs - aRhs) < 0.01;
}

template<typename SrcT, typename DstT>
void TestInterleaveAndConvert()
{
  size_t arraySize = 1024;
  size_t maxChannels = 8; // 7.1
  for (uint32_t channels = 1; channels < maxChannels; channels++) {
    const SrcT* const* src = GetPlanarChannelArray<SrcT>(channels, arraySize);
    DstT* dst = new DstT[channels * arraySize];

    InterleaveAndConvertBuffer(src, arraySize, 1.0, channels, dst);

    uint32_t channelIndex = 0;
    for (size_t i = 0; i < arraySize * channels; i++) {
      ASSERT_TRUE(FuzzyEqual(dst[i],
                  FloatToAudioSample<DstT>(1. / (channelIndex + 1))));
      channelIndex++;
      channelIndex %= channels;
    }

    DeletePlanarChannelsArray(src, channels);
    delete [] dst;
  }
}

template<typename SrcT, typename DstT>
void TestDeinterleaveAndConvert()
{
  size_t arraySize = 1024;
  size_t maxChannels = 8; // 7.1
  for (uint32_t channels = 1; channels < maxChannels; channels++) {
    const SrcT* src = GetInterleavedChannelArray<SrcT>(channels, arraySize);
    DstT** dst = GetPlanarArray<DstT>(channels, arraySize);

    DeinterleaveAndConvertBuffer(src, arraySize, channels, dst);

    for (size_t channel = 0; channel < channels; channel++) {
      for (size_t i = 0; i < arraySize; i++) {
        ASSERT_TRUE(FuzzyEqual(dst[channel][i],
                    FloatToAudioSample<DstT>(1. / (channel + 1))));
      }
    }

    DeleteInterleavedChannelArray(src);
    DeletePlanarArray(dst, channels);
  }
}

uint8_t gSilence[4096] = {0};

template<typename T>
T* SilentChannel()
{
  return reinterpret_cast<T*>(gSilence);
}

template<typename T>
void TestUpmixStereo()
{
  size_t arraySize = 1024;
  nsTArray<T*> channels;
  nsTArray<const T*> channelsptr;

  channels.SetLength(1);
  channelsptr.SetLength(1);

  channels[0] = new T[arraySize];

  for (size_t i = 0; i < arraySize; i++) {
    channels[0][i] = GetHighValue<T>();
  }
  channelsptr[0] = channels[0];

  AudioChannelsUpMix(&channelsptr, 2, SilentChannel<T>());

  for (size_t channel = 0; channel < 2; channel++) {
    for (size_t i = 0; i < arraySize; i++) {
      ASSERT_TRUE(channelsptr[channel][i] == GetHighValue<T>());
    }
  }
  delete [] channels[0];
}

template<typename T>
void TestDownmixStereo()
{
  const size_t arraySize = 1024;
  nsTArray<const T*> inputptr;
  nsTArray<T*> input;
  T** output;

  output = new T*[1];
  output[0] = new T[arraySize];

  input.SetLength(2);
  inputptr.SetLength(2);

  for (size_t channel = 0; channel < input.Length(); channel++) {
    input[channel] = new T[arraySize];
    for (size_t i = 0; i < arraySize; i++) {
      input[channel][i] = channel == 0 ? GetLowValue<T>() : GetHighValue<T>();
    }
    inputptr[channel] = input[channel];
  }

  AudioChannelsDownMix(inputptr, output, 1, arraySize);

  for (size_t i = 0; i < arraySize; i++) {
    ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
    ASSERT_TRUE(output[0][i] == GetSilentValue<T>());
  }

  delete [] output[0];
  delete [] output;
}

TEST(AudioSegment, Test)
{
  TestInterleaveAndConvert<float, float>();
  TestInterleaveAndConvert<float, int16_t>();
  TestInterleaveAndConvert<int16_t, float>();
  TestInterleaveAndConvert<int16_t, int16_t>();
  TestDeinterleaveAndConvert<float, float>();
  TestDeinterleaveAndConvert<float, int16_t>();
  TestDeinterleaveAndConvert<int16_t, float>();
  TestDeinterleaveAndConvert<int16_t, int16_t>();
  TestUpmixStereo<float>();
  TestUpmixStereo<int16_t>();
  TestDownmixStereo<float>();
  TestDownmixStereo<int16_t>();
}

} // namespace audio_segment
