/* -*- 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 "gtest/gtest.h"
#include "MP4Reader.h"
#include "MP4Decoder.h"
#include "mozilla/SharedThreadPool.h"
#include "MockMediaResource.h"
#include "MockMediaDecoderOwner.h"
#include "mozilla/Preferences.h"
#include "TimeUnits.h"

using namespace mozilla;
using namespace mozilla::dom;

class TestBinding
{
public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestBinding);

  RefPtr<MP4Decoder> decoder;
  RefPtr<MockMediaResource> resource;
  RefPtr<MP4Reader> reader;

  explicit TestBinding(const char* aFileName = "gizmo.mp4")
    : decoder(new MP4Decoder())
    , resource(new MockMediaResource(aFileName))
    , reader(new MP4Reader(decoder))
  {
    EXPECT_EQ(NS_OK, Preferences::SetBool(
                       "media.use-blank-decoder", true));

    EXPECT_EQ(NS_OK, resource->Open(nullptr));
    decoder->SetResource(resource);

    reader->Init(nullptr);
    // This needs to be done before invoking GetBuffered. This is normally
    // done by MediaDecoderStateMachine.
    reader->DispatchSetStartTime(0);
  }

  void Init() {
    nsCOMPtr<nsIThread> thread;
    nsCOMPtr<nsIRunnable> r = NewRunnableMethod(this, &TestBinding::ReadMetadata);
    nsresult rv = NS_NewThread(getter_AddRefs(thread), r);
    EXPECT_EQ(NS_OK, rv);
    thread->Shutdown();
  }

private:
  virtual ~TestBinding()
  {
    {
      RefPtr<TaskQueue> queue = reader->OwnerThread();
      nsCOMPtr<nsIRunnable> task = NewRunnableMethod(reader, &MP4Reader::Shutdown);
      // Hackily bypass the tail dispatcher so that we can AwaitShutdownAndIdle.
      // In production code we'd use BeginShutdown + promises.
      queue->Dispatch(task.forget(), AbstractThread::AssertDispatchSuccess,
                      AbstractThread::TailDispatch);
      queue->AwaitShutdownAndIdle();
    }
    decoder = nullptr;
    resource = nullptr;
    reader = nullptr;
    SharedThreadPool::SpinUntilEmpty();
  }

  void ReadMetadata()
  {
    MediaInfo info;
    MetadataTags* tags;
    EXPECT_EQ(NS_OK, reader->ReadMetadata(&info, &tags));
  }
};

TEST(MP4Reader, BufferedRange)
{
  RefPtr<TestBinding> b = new TestBinding();
  b->Init();

  // Video 3-4 sec, audio 2.986666-4.010666 sec
  b->resource->MockAddBufferedRange(248400, 327455);

  media::TimeIntervals ranges = b->reader->GetBuffered();
  EXPECT_EQ(1U, ranges.Length());
  EXPECT_NEAR(270000 / 90000.0, ranges.Start(0).ToSeconds(), 0.000001);
  EXPECT_NEAR(360000 / 90000.0, ranges.End(0).ToSeconds(), 0.000001);
}

TEST(MP4Reader, BufferedRangeMissingLastByte)
{
  RefPtr<TestBinding> b = new TestBinding();
  b->Init();

  // Dropping the last byte of the video
  b->resource->MockClearBufferedRanges();
  b->resource->MockAddBufferedRange(248400, 324912);
  b->resource->MockAddBufferedRange(324913, 327455);

  media::TimeIntervals ranges = b->reader->GetBuffered();
  EXPECT_EQ(1U, ranges.Length());
  EXPECT_NEAR(270000.0 / 90000.0, ranges.Start(0).ToSeconds(), 0.000001);
  EXPECT_NEAR(357000 / 90000.0, ranges.End(0).ToSeconds(), 0.000001);
}

TEST(MP4Reader, BufferedRangeSyncFrame)
{
  RefPtr<TestBinding> b = new TestBinding();
  b->Init();

  // Check that missing the first byte at 2 seconds skips right through to 3
  // seconds because of a missing sync frame
  b->resource->MockClearBufferedRanges();
  b->resource->MockAddBufferedRange(146336, 327455);

  media::TimeIntervals ranges = b->reader->GetBuffered();
  EXPECT_EQ(1U, ranges.Length());
  EXPECT_NEAR(270000.0 / 90000.0, ranges.Start(0).ToSeconds(), 0.000001);
  EXPECT_NEAR(360000 / 90000.0, ranges.End(0).ToSeconds(), 0.000001);
}

TEST(MP4Reader, CompositionOrder)
{
  RefPtr<TestBinding> b = new TestBinding("mediasource_test.mp4");
  b->Init();

  // The first 5 video samples of this file are:
  // Video timescale=2500
  // Frame Start  Size  Time  Duration  Sync
  //     1    48  5455   166        83  Yes
  //     2  5503   145   249        83
  //     3  6228   575   581        83
  //     4  7383   235   415        83
  //     5  8779   183   332        83
  //     6  9543   191   498        83
  //
  // Audio timescale=44100
  //     1  5648   580     0      1024  Yes
  //     2  6803   580  1024      1058  Yes
  //     3  7618   581  2082      1014  Yes
  //     4  8199   580  3096      1015  Yes
  //     5  8962   581  4111      1014  Yes
  //     6  9734   580  5125      1014  Yes
  //     7 10314   581  6139      1059  Yes
  //     8 11207   580  7198      1014  Yes
  //     9 12035   581  8212      1014  Yes
  //    10 12616   580  9226      1015  Yes
  //    11 13220   581  10241     1014  Yes

  b->resource->MockClearBufferedRanges();
  // First two frames in decoding + first audio frame
  b->resource->MockAddBufferedRange(48, 5503);   // Video 1
  b->resource->MockAddBufferedRange(5503, 5648); // Video 2
  b->resource->MockAddBufferedRange(6228, 6803); // Video 3

  // Audio - 5 frames; 0 - 139206 us
  b->resource->MockAddBufferedRange(5648, 6228);
  b->resource->MockAddBufferedRange(6803, 7383);
  b->resource->MockAddBufferedRange(7618, 8199);
  b->resource->MockAddBufferedRange(8199, 8779);
  b->resource->MockAddBufferedRange(8962, 9563);
  b->resource->MockAddBufferedRange(9734, 10314);
  b->resource->MockAddBufferedRange(10314, 10895);
  b->resource->MockAddBufferedRange(11207, 11787);
  b->resource->MockAddBufferedRange(12035, 12616);
  b->resource->MockAddBufferedRange(12616, 13196);
  b->resource->MockAddBufferedRange(13220, 13901);

  media::TimeIntervals ranges = b->reader->GetBuffered();
  EXPECT_EQ(2U, ranges.Length());

  EXPECT_NEAR(166.0 / 2500.0, ranges.Start(0).ToSeconds(), 0.000001);
  EXPECT_NEAR(332.0 / 2500.0, ranges.End(0).ToSeconds(), 0.000001);

  EXPECT_NEAR(581.0 / 2500.0, ranges.Start(1).ToSeconds(), 0.000001);
  EXPECT_NEAR(11255.0 / 44100.0, ranges.End(1).ToSeconds(), 0.000001);
}

TEST(MP4Reader, Normalised)
{
  RefPtr<TestBinding> b = new TestBinding("mediasource_test.mp4");
  b->Init();

  // The first 5 video samples of this file are:
  // Video timescale=2500
  // Frame Start  Size  Time  Duration  Sync
  //     1    48  5455   166        83  Yes
  //     2  5503   145   249        83
  //     3  6228   575   581        83
  //     4  7383   235   415        83
  //     5  8779   183   332        83
  //     6  9543   191   498        83
  //
  // Audio timescale=44100
  //     1  5648   580     0      1024  Yes
  //     2  6803   580  1024      1058  Yes
  //     3  7618   581  2082      1014  Yes
  //     4  8199   580  3096      1015  Yes
  //     5  8962   581  4111      1014  Yes
  //     6  9734   580  5125      1014  Yes
  //     7 10314   581  6139      1059  Yes
  //     8 11207   580  7198      1014  Yes
  //     9 12035   581  8212      1014  Yes
  //    10 12616   580  9226      1015  Yes
  //    11 13220   581  10241     1014  Yes

  b->resource->MockClearBufferedRanges();
  b->resource->MockAddBufferedRange(48, 13901);

  media::TimeIntervals ranges = b->reader->GetBuffered();
  EXPECT_EQ(1U, ranges.Length());

  EXPECT_NEAR(166.0 / 2500.0, ranges.Start(0).ToSeconds(), 0.000001);
  EXPECT_NEAR(11255.0 / 44100.0, ranges.End(0).ToSeconds(), 0.000001);
}