/* -*- Mode: C++; tab-width: 8; 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 "gtest/gtest.h"
#include "AudioCompactor.h"
#include "MediaDecoderReader.h"

using mozilla::AudioCompactor;
using mozilla::AudioData;
using mozilla::AudioDataValue;
using mozilla::MediaDecoderReader;
using mozilla::MediaQueue;

class MemoryFunctor : public nsDequeFunctor {
public:
  MemoryFunctor() : mSize(0) {}
  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);

  void* operator()(void* aObject) override {
    const AudioData* audioData = static_cast<const AudioData*>(aObject);
    mSize += audioData->SizeOfIncludingThis(MallocSizeOf);
    return nullptr;
  }

  size_t mSize;
};

class TestCopy
{
public:
  TestCopy(uint32_t aFrames, uint32_t aChannels,
           uint32_t &aCallCount, uint32_t &aFrameCount)
    : mFrames(aFrames)
    , mChannels(aChannels)
    , mCallCount(aCallCount)
    , mFrameCount(aFrameCount)
  { }

  uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples)
  {
    mCallCount += 1;
    uint32_t frames = std::min(mFrames - mFrameCount, aSamples / mChannels);
    mFrameCount += frames;
    return frames;
  }

private:
  const uint32_t mFrames;
  const uint32_t mChannels;
  uint32_t &mCallCount;
  uint32_t &mFrameCount;
};

static void TestAudioCompactor(size_t aBytes)
{
  MediaQueue<AudioData> queue;
  AudioCompactor compactor(queue);

  uint64_t offset = 0;
  uint64_t time = 0;
  uint32_t sampleRate = 44000;
  uint32_t channels = 2;
  uint32_t frames = aBytes / (channels * sizeof(AudioDataValue));
  size_t maxSlop = aBytes / AudioCompactor::MAX_SLOP_DIVISOR;

  uint32_t callCount = 0;
  uint32_t frameCount = 0;

  compactor.Push(offset, time, sampleRate, frames, channels,
                 TestCopy(frames, channels, callCount, frameCount));

  EXPECT_GT(callCount, 0U) << "copy functor never called";
  EXPECT_EQ(frames, frameCount) << "incorrect number of frames copied";

  MemoryFunctor memoryFunc;
  queue.LockedForEach(memoryFunc);
  size_t allocSize = memoryFunc.mSize - (callCount * sizeof(AudioData));
  size_t slop = allocSize - aBytes;
  EXPECT_LE(slop, maxSlop) << "allowed too much allocation slop";
}

TEST(Media, AudioCompactor_4000)
{
  TestAudioCompactor(4000);
}

TEST(Media, AudioCompactor_4096)
{
  TestAudioCompactor(4096);
}

TEST(Media, AudioCompactor_5000)
{
  TestAudioCompactor(5000);
}

TEST(Media, AudioCompactor_5256)
{
  TestAudioCompactor(5256);
}

TEST(Media, AudioCompactor_NativeCopy)
{
  const uint32_t channels = 2;
  const size_t srcBytes = 32;
  const uint32_t srcSamples = srcBytes / sizeof(AudioDataValue);
  const uint32_t srcFrames = srcSamples / channels;
  uint8_t src[srcBytes];

  for (uint32_t i = 0; i < srcBytes; ++i) {
    src[i] = i;
  }

  AudioCompactor::NativeCopy copy(src, srcBytes, channels);

  const uint32_t dstSamples = srcSamples * 2;
  AudioDataValue dst[dstSamples];

  const AudioDataValue notCopied = 0xffff;
  for (uint32_t i = 0; i < dstSamples; ++i) {
    dst[i] = notCopied;
  }

  const uint32_t copyCount = 8;
  uint32_t copiedFrames = 0;
  uint32_t nextSample = 0;
  for (uint32_t i = 0; i < copyCount; ++i) {
    uint32_t copySamples = dstSamples / copyCount;
    copiedFrames += copy(dst + nextSample, copySamples);
    nextSample += copySamples;
  }

  EXPECT_EQ(srcFrames, copiedFrames) << "copy exact number of source frames";

  // Verify that the only the correct bytes were copied.
  for (uint32_t i = 0; i < dstSamples; ++i) {
    if (i < srcSamples) {
      EXPECT_NE(notCopied, dst[i]) << "should have copied over these bytes";
    } else {
      EXPECT_EQ(notCopied, dst[i]) << "should not have copied over these bytes";
    }
  }
}
