// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#include "tests/shared/browser/main_message_loop_external_pump.h"

#include <CommCtrl.h>

#include <memory>

#include "include/cef_app.h"
#include "tests/shared/browser/util_win.h"

namespace client {

namespace {

// Message sent to get an additional time slice for pumping (processing) another
// task (a series of such messages creates a continuous task pump).
static const int kMsgHaveWork = WM_USER + 1;

class MainMessageLoopExternalPumpWin : public MainMessageLoopExternalPump {
 public:
  MainMessageLoopExternalPumpWin();
  ~MainMessageLoopExternalPumpWin() override;

  // MainMessageLoopStd methods:
  void Quit() override;
  int Run() override;

  // MainMessageLoopExternalPump methods:
  void OnScheduleMessagePumpWork(int64_t delay_ms) override;

 protected:
  // MainMessageLoopExternalPump methods:
  void SetTimer(int64_t delay_ms) override;
  void KillTimer() override;
  bool IsTimerPending() override { return timer_pending_; }

 private:
  static LRESULT CALLBACK WndProc(HWND hwnd,
                                  UINT msg,
                                  WPARAM wparam,
                                  LPARAM lparam);

  // True if a timer event is currently pending.
  bool timer_pending_ = false;

  // HWND owned by the thread that CefDoMessageLoopWork should be invoked on.
  HWND main_thread_target_ = nullptr;
};

MainMessageLoopExternalPumpWin::MainMessageLoopExternalPumpWin() {
  HINSTANCE hInstance = GetCodeModuleHandle();
  const wchar_t* const kClassName = L"CEFMainTargetHWND";

  WNDCLASSEX wcex = {};
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.lpfnWndProc = WndProc;
  wcex.hInstance = hInstance;
  wcex.lpszClassName = kClassName;
  RegisterClassEx(&wcex);

  // Create the message handling window.
  main_thread_target_ =
      CreateWindowW(kClassName, nullptr, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0,
                    HWND_MESSAGE, nullptr, hInstance, nullptr);
  DCHECK(main_thread_target_);
  SetUserDataPtr(main_thread_target_, this);
}

MainMessageLoopExternalPumpWin::~MainMessageLoopExternalPumpWin() {
  KillTimer();
  if (main_thread_target_) {
    DestroyWindow(main_thread_target_);
  }
}

void MainMessageLoopExternalPumpWin::Quit() {
  PostMessage(nullptr, WM_QUIT, 0, 0);
}

int MainMessageLoopExternalPumpWin::Run() {
  // Run the message loop.
  MSG msg;
  while (GetMessage(&msg, nullptr, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  KillTimer();

  // We need to run the message pump until it is idle. However we don't have
  // that information here so we run the message loop "for a while".
  for (int i = 0; i < 10; ++i) {
    // Do some work.
    CefDoMessageLoopWork();

    // Sleep to allow the CEF proc to do work.
    Sleep(50);
  }

  return 0;
}

void MainMessageLoopExternalPumpWin::OnScheduleMessagePumpWork(
    int64_t delay_ms) {
  // This method may be called on any thread.
  PostMessage(main_thread_target_, kMsgHaveWork, 0,
              static_cast<LPARAM>(delay_ms));
}

void MainMessageLoopExternalPumpWin::SetTimer(int64_t delay_ms) {
  DCHECK(!timer_pending_);
  DCHECK_GT(delay_ms, 0);
  timer_pending_ = true;
  ::SetTimer(main_thread_target_, 1, static_cast<UINT>(delay_ms), nullptr);
}

void MainMessageLoopExternalPumpWin::KillTimer() {
  if (timer_pending_) {
    ::KillTimer(main_thread_target_, 1);
    timer_pending_ = false;
  }
}

// static
LRESULT CALLBACK MainMessageLoopExternalPumpWin::WndProc(HWND hwnd,
                                                         UINT msg,
                                                         WPARAM wparam,
                                                         LPARAM lparam) {
  if (msg == WM_TIMER || msg == kMsgHaveWork) {
    MainMessageLoopExternalPumpWin* message_loop =
        GetUserDataPtr<MainMessageLoopExternalPumpWin*>(hwnd);
    if (msg == kMsgHaveWork) {
      // OnScheduleMessagePumpWork() request.
      const int64_t delay_ms = static_cast<int64_t>(lparam);
      message_loop->OnScheduleWork(delay_ms);
    } else {
      // Timer timed out.
      message_loop->OnTimerTimeout();
    }
  }
  return DefWindowProc(hwnd, msg, wparam, lparam);
}

}  // namespace

// static
std::unique_ptr<MainMessageLoopExternalPump>
MainMessageLoopExternalPump::Create() {
  return std::make_unique<MainMessageLoopExternalPumpWin>();
}

}  // namespace client
