// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <android/looper.h>
#include <stdarg.h>
#include <string.h>

#include "base/android/path_utils.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/singleton.h"
#include "base/message_loop/message_pump.h"
#include "base/message_loop/message_pump_android.h"
#include "base/path_service.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/multiprocess_test.h"

namespace {

base::FilePath* g_test_data_dir = nullptr;

struct RunState {
  RunState(base::MessagePump::Delegate* delegate, int run_depth)
      : delegate(delegate),
        run_depth(run_depth),
        should_quit(false) {
  }

  raw_ptr<base::MessagePump::Delegate> delegate;

  // Used to count how many Run() invocations are on the stack.
  int run_depth;

  // Used to flag that the current Run() invocation should return ASAP.
  bool should_quit;
};

RunState* g_state = nullptr;

// A singleton WaitableEvent wrapper so we avoid a busy loop in
// MessagePumpForUIStub. Other platforms use the native event loop which blocks
// when there are no pending messages.
class Waitable {
 public:
  static Waitable* GetInstance() {
    return base::Singleton<Waitable,
                           base::LeakySingletonTraits<Waitable>>::get();
  }

  Waitable(const Waitable&) = delete;
  Waitable& operator=(const Waitable&) = delete;

  // Signals that there are more work to do.
  void Signal() { waitable_event_.Signal(); }

  // Blocks until more work is scheduled.
  void Block() { waitable_event_.Wait(); }

  void Quit() {
    g_state->should_quit = true;
    Signal();
  }

 private:
  friend struct base::DefaultSingletonTraits<Waitable>;

  Waitable()
      : waitable_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                        base::WaitableEvent::InitialState::NOT_SIGNALED) {}

  base::WaitableEvent waitable_event_;
};

// The MessagePumpForUI implementation for test purpose.
class MessagePumpForUIStub : public base::MessagePumpForUI {
 public:
  MessagePumpForUIStub() : base::MessagePumpForUI() { Waitable::GetInstance(); }
  ~MessagePumpForUIStub() override {}

  // In tests, there isn't a native thread, as such RunLoop::Run() should be
  // used to run the loop instead of attaching and delegating to the native
  // loop. As such, this override ignores the Attach() request.
  void Attach(base::MessagePump::Delegate* delegate) override {}

  void Run(base::MessagePump::Delegate* delegate) override {
    // The following was based on message_pump_glib.cc, except we're using a
    // WaitableEvent since there are no native message loop to use.
    RunState state(delegate, g_state ? g_state->run_depth + 1 : 1);

    RunState* previous_state = g_state;
    g_state = &state;

    // When not nested we can use the looper, otherwise fall back
    // to the stub implementation.
    if (g_state->run_depth > 1) {
      RunNested(delegate);
    } else {
      SetQuit(false);
      SetDelegate(delegate);

      // Pump the loop once in case we're starting off idle as ALooper_pollOnce
      // will never return in that case.
      ScheduleWork();
      while (true) {
        // Waits for either the delayed, or non-delayed fds to be signalled,
        // calling either OnDelayedLooperCallback, or
        // OnNonDelayedLooperCallback, respectively. This uses Android's Looper
        // implementation, which is based off of epoll.
        ALooper_pollOnce(-1, nullptr, nullptr, nullptr);
        if (ShouldQuit())
          break;
      }
    }

    g_state = previous_state;
  }

  void RunNested(base::MessagePump::Delegate* delegate) {
    bool more_work_is_plausible = true;

    for (;;) {
      if (!more_work_is_plausible) {
        Waitable::GetInstance()->Block();
        if (g_state->should_quit)
          break;
      }

      Delegate::NextWorkInfo next_work_info = g_state->delegate->DoWork();
      more_work_is_plausible = next_work_info.is_immediate();
      if (g_state->should_quit)
        break;

      if (more_work_is_plausible)
        continue;

      more_work_is_plausible = g_state->delegate->DoIdleWork();
      if (g_state->should_quit)
        break;

      more_work_is_plausible |= !next_work_info.delayed_run_time.is_max();
    }
  }

  void Quit() override {
    CHECK(g_state);
    if (g_state->run_depth > 1) {
      Waitable::GetInstance()->Quit();
    } else {
      MessagePumpForUI::Quit();
    }
  }

  void ScheduleWork() override {
    if (g_state && g_state->run_depth > 1) {
      Waitable::GetInstance()->Signal();
    } else {
      MessagePumpForUI::ScheduleWork();
    }
  }

  void ScheduleDelayedWork(
      const Delegate::NextWorkInfo& next_work_info) override {
    if (g_state && g_state->run_depth > 1) {
      Waitable::GetInstance()->Signal();
    } else {
      MessagePumpForUI::ScheduleDelayedWork(next_work_info);
    }
  }
};

std::unique_ptr<base::MessagePump> CreateMessagePumpForUIStub() {
  return std::unique_ptr<base::MessagePump>(new MessagePumpForUIStub());
}

// Provides the test path for paths overridden during tests.
bool GetTestProviderPath(int key, base::FilePath* result) {
  switch (key) {
    // On Android, our tests don't have permission to write to DIR_MODULE.
    // gtest/test_runner.py pushes data to external storage.
    // TODO(agrieve): Stop overriding DIR_ANDROID_APP_DATA.
    // https://crbug.com/617734
    // Instead DIR_ASSETS should be used to discover assets file location in
    // tests.
    case base::DIR_ANDROID_APP_DATA:
    case base::DIR_ASSETS:
    case base::DIR_SRC_TEST_DATA_ROOT:
      CHECK(g_test_data_dir != nullptr);
      *result = *g_test_data_dir;
      return true;
    default:
      return false;
  }
}

void InitPathProvider(int key) {
  base::FilePath path;
  // If failed to override the key, that means the way has not been registered.
  if (GetTestProviderPath(key, &path) &&
      !base::PathService::Override(key, path)) {
    base::PathService::RegisterProvider(&GetTestProviderPath, key, key + 1);
  }
}

}  // namespace

namespace base {

void InitAndroidTestPaths(const FilePath& test_data_dir) {
  if (g_test_data_dir) {
    CHECK(test_data_dir == *g_test_data_dir);
    return;
  }
  g_test_data_dir = new FilePath(test_data_dir);
  InitPathProvider(DIR_ANDROID_APP_DATA);
  InitPathProvider(DIR_ASSETS);
  InitPathProvider(DIR_SRC_TEST_DATA_ROOT);
}

void InitAndroidTestMessageLoop() {
  // NOTE something else such as a JNI call may have already overridden the UI
  // factory.
  if (!MessagePump::IsMessagePumpForUIFactoryOveridden())
    MessagePump::OverrideMessagePumpForUIFactory(&CreateMessagePumpForUIStub);
}

}  // namespace base
