/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsDeque.h"
#include "nsIMemoryReporter.h"

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

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

  void operator()(AudioData* aObject) override {
    mSize += aObject->SizeOfIncludingThis(MallocSizeOf);
  }

  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";
    }
  }
}
