/* 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 "MediaUtils.h"
#include "gtest/gtest.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/gtest/MozHelpers.h"

#ifdef XP_MACOSX
#  include "gfxPlatformMac.h"
#endif

using namespace mozilla;
using namespace mozilla::gtest;
using namespace mozilla::media;

// Spawning the death test child process aborts on Android.
#if !defined(ANDROID) && defined(GTEST_HAS_DEATH_TEST)

// Kept here for reference as it can be handy during development.
#  define DISABLE_CRASH_REPORTING  \
    gtest::DisableCrashReporter(); \
    ZERO_GDB_SLEEP();

void DoCreateTicketBeforeAppShutdownOnMain() {
  auto reporter = ScopedTestResultReporter::Create(ExitMode::ExitOnDtor);

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown);

  Monitor mon("TestMonitor");
  bool pastAppShutdown = false;
  bool backgroundTaskFinished = false;

  auto ticket = ShutdownBlockingTicket::Create(
      u"Test"_ns, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);

  MOZ_ALWAYS_SUCCEEDS(
      NS_DispatchBackgroundTask(NS_NewRunnableFunction(__func__, [&] {
        TimeStamp now = TimeStamp::Now();
        TimeStamp end = now + TimeDuration::FromSeconds(0.2);
        MonitorAutoLock lock(mon);
        while (!pastAppShutdown && (end - now) > TimeDuration()) {
          lock.Wait(end - now);
          now = TimeStamp::Now();
        }
        EXPECT_FALSE(pastAppShutdown);
        ticket = nullptr;
        while (!pastAppShutdown) {
          lock.Wait();
        }
        EXPECT_TRUE(pastAppShutdown);
        backgroundTaskFinished = true;
        lock.Notify();
      })));

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown);

  {
    MonitorAutoLock lock(mon);
    pastAppShutdown = true;
    lock.Notify();
    while (!backgroundTaskFinished) {
      lock.Wait();
    }
  }

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry);

  NS_ShutdownXPCOM(nullptr);

#  ifdef XP_MACOSX
  gfxPlatformMac::WaitForFontRegistration();
#  endif
}

void DoCreateTicketAfterAppShutdownOnMain() {
  auto reporter = ScopedTestResultReporter::Create(ExitMode::ExitOnDtor);

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown);

  auto ticket = ShutdownBlockingTicket::Create(
      u"Test"_ns, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);
  EXPECT_FALSE(ticket);

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry);

  NS_ShutdownXPCOM(nullptr);

#  ifdef XP_MACOSX
  gfxPlatformMac::WaitForFontRegistration();
#  endif
}

void DoCreateTicketBeforeAppShutdownOffMain() {
  auto reporter = ScopedTestResultReporter::Create(ExitMode::ExitOnDtor);

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown);

  Monitor mon("TestMonitor");
  bool pastAppShutdown = false;
  bool ticketCreated = false;
  bool backgroundTaskFinished = false;

  MOZ_ALWAYS_SUCCEEDS(
      NS_DispatchBackgroundTask(NS_NewRunnableFunction(__func__, [&] {
        MonitorAutoLock lock(mon);
        auto ticket = ShutdownBlockingTicket::Create(
            u"Test"_ns, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);
        EXPECT_TRUE(ticket);
        ticketCreated = true;
        lock.Notify();

        TimeStamp now = TimeStamp::Now();
        TimeStamp end = now + TimeDuration::FromSeconds(0.2);
        while (!pastAppShutdown && (end - now) > TimeDuration()) {
          lock.Wait(end - now);
          now = TimeStamp::Now();
        }
        EXPECT_FALSE(pastAppShutdown);
        ticket = nullptr;
        while (!pastAppShutdown) {
          lock.Wait();
        }
        EXPECT_TRUE(pastAppShutdown);
        backgroundTaskFinished = true;
        lock.Notify();
      })));

  {
    MonitorAutoLock lock(mon);
    while (!ticketCreated) {
      lock.Wait();
    }
  }

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown);

  MonitorAutoLock lock(mon);
  pastAppShutdown = true;
  lock.Notify();
  while (!backgroundTaskFinished) {
    lock.Wait();
  }

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry);

  NS_ShutdownXPCOM(nullptr);

#  ifdef XP_MACOSX
  gfxPlatformMac::WaitForFontRegistration();
#  endif
}

void DoCreateTicketAfterAppShutdownOffMain() {
  auto reporter = ScopedTestResultReporter::Create(ExitMode::ExitOnDtor);

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown);

  nsCOMPtr<nsISerialEventTarget> bg;
  MOZ_ALWAYS_SUCCEEDS(
      NS_CreateBackgroundTaskQueue(__func__, getter_AddRefs(bg)));
  UniquePtr<ShutdownBlockingTicket> ticket;
  MOZ_ALWAYS_SUCCEEDS(
      MakeRefPtr<SyncRunnable>(NS_NewRunnableFunction(__func__, [&] {
        ticket = ShutdownBlockingTicket::Create(
            u"Test"_ns, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);
      }))->DispatchToThread(bg));

  EXPECT_FALSE(ticket);

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry);

  NS_ShutdownXPCOM(nullptr);

#  ifdef XP_MACOSX
  gfxPlatformMac::WaitForFontRegistration();
#  endif
}

void DoTwoTicketsWithSameNameBothBlockShutdown() {
  auto reporter = ScopedTestResultReporter::Create(ExitMode::ExitOnDtor);

  const auto name = u"Test"_ns;
  auto ticket1 = ShutdownBlockingTicket::Create(
      name, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);
  EXPECT_TRUE(ticket1);
  auto ticket2 = ShutdownBlockingTicket::Create(
      name, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__);
  EXPECT_TRUE(ticket2);

  ticket1 = nullptr;

  // A copyable holder for the std::function in NS_NewTimerWithCallback.
  auto ticket2Holder =
      MakeRefPtr<Refcountable<UniquePtr<ShutdownBlockingTicket>>>(
          ticket2.release());

  const auto waitBeforeDestroyingTicket = TimeDuration::FromMilliseconds(100);
  TimeStamp before = TimeStamp::Now();
  auto timerResult = NS_NewTimerWithCallback(
      [t = std::move(ticket2Holder)](nsITimer* aTimer) {},
      waitBeforeDestroyingTicket, nsITimer::TYPE_ONE_SHOT,
      "DoTwoTicketsWithSameNameBothBlockShutdown"_ns);
  ASSERT_TRUE(timerResult.isOk());

  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownNetTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTeardown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdown);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownQM);
  AppShutdown::AdvanceShutdownPhase(ShutdownPhase::AppShutdownTelemetry);

  NS_ShutdownXPCOM(nullptr);

#  ifdef XP_MACOSX
  gfxPlatformMac::WaitForFontRegistration();
#  endif

  TimeStamp after = TimeStamp::Now();
  EXPECT_GT((after - before).ToMilliseconds(),
            waitBeforeDestroyingTicket.ToMilliseconds());
}

TEST(ShutdownBlockingTicketDeathTest, CreateTicketBeforeAppShutdownOnMain)
{
  EXPECT_EXIT(DoCreateTicketBeforeAppShutdownOnMain(),
              testing::ExitedWithCode(0), "");
}

TEST(ShutdownBlockingTicketDeathTest, CreateTicketAfterAppShutdownOnMain)
{
  EXPECT_EXIT(DoCreateTicketAfterAppShutdownOnMain(),
              testing::ExitedWithCode(0), "");
}

TEST(ShutdownBlockingTicketDeathTest, CreateTicketBeforeAppShutdownOffMain)
{
  EXPECT_EXIT(DoCreateTicketBeforeAppShutdownOffMain(),
              testing::ExitedWithCode(0), "");
}

TEST(ShutdownBlockingTicketDeathTest, CreateTicketAfterAppShutdownOffMain)
{
  EXPECT_EXIT(DoCreateTicketAfterAppShutdownOffMain(),
              testing::ExitedWithCode(0), "");
}

TEST(ShutdownBlockingTicketDeathTest, TwoTicketsWithSameNameBothBlockShutdown)
{
  EXPECT_EXIT(DoTwoTicketsWithSameNameBothBlockShutdown(),
              testing::ExitedWithCode(0), "");
}

#  undef DISABLE_CRASH_REPORTING

#endif
