#ifdef _WIN32

#include "later2.h"

#include <cpp4r.hpp>
#include <queue>
#define WIN32_LEAN_AND_MEAN
#include "debug.h"
#include <windows.h>

using namespace cpp4r;

// Whether we have initialized the message-only window.
static int initialized = 0;

// The handle to the message-only window
static HWND hwnd;

// The ID of the timer
static UINT_PTR TIMER_ID = 1;

// The window message we use to run SetTimer on the main thread
static const UINT WM_SETUPTIMER = WM_USER + 101;

static void setupTimer() {
  if (!SetTimer(hwnd, TIMER_ID, USER_TIMER_MINIMUM, NULL)) {
    (Rf_error)("Failed to schedule callback timer");
  }
}

static bool executeHandlers() {
  if (!at_top_level()) {
    // It's not safe to run arbitrary callbacks when other R code
    // is already running. Wait until we're back at the top level.
    return false;
  }

  // This try-catch is needed for two reasons: first, if an exception occurs in
  // any of the callbacks, destructors will still execute; and second, if an
  // exception (including R-level error) occurs in a callback and it reaches
  // the top level in an R input handler, R appears to be unable to handle it
  // properly.
  // https://github.com/r-lib/later/issues/12
  // https://github.com/r-lib/later/issues/31
  try {
    execCallbacksForTopLevel();
  } catch (cpp4r::unwind_exception &e) {
    REprintf(
        "later2: exception or interrupt occurred while executing callback.\n");
  } catch (std::exception &e) {
    std::string msg = "later2: exception occurred while executing callback: \n";
    msg += e.what();
    msg += "\n";
    REprintf("%s", msg.c_str());
  } catch (...) {
    REprintf("later2: c++ exception (unknown reason) occurred while executing "
             "callback.\n");
  }

  return idle(GLOBAL_LOOP);
}

LRESULT CALLBACK callbackWndProc(HWND hWnd, UINT message, WPARAM wParam,
                                 LPARAM lParam) {
  switch (message) {
  case WM_TIMER:
    if (executeHandlers()) {
      KillTimer(hwnd, TIMER_ID);
    }
    break;
  case WM_SETUPTIMER:
    setupTimer();
    break;
  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

void ensureAutorunnerInitialized() {
  if (!initialized) {
    static const char *class_name = "R_LATER2_WINDOW_CLASS";
    WNDCLASSEX wc = {};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = callbackWndProc;
    wc.hInstance = NULL;
    wc.lpszClassName = class_name;
    if (!RegisterClassEx(&wc)) {
      cpp4r::stop("Failed to register window class");
    }

    hwnd = CreateWindowEx(0, class_name, "dummy_name", 0, 0, 0, 0, 0,
                          HWND_MESSAGE, NULL, NULL, NULL);
    if (!hwnd) {
      cpp4r::stop("Failed to create message-only window");
    }

    initialized = 1;
  }
}

uint64_t doExecLater(std::shared_ptr<CallbackRegistry> callbackRegistry,
                     SEXP callback, double delaySecs, bool resetTimer) {
  uint64_t callback_id = callbackRegistry->add(callback, delaySecs);

  if (resetTimer)
    setupTimer();

  return callback_id;
}

uint64_t doExecLater(std::shared_ptr<CallbackRegistry> callbackRegistry,
                     void (*func)(void *), void *data, double delaySecs,
                     bool resetTimer) {
  uint64_t callback_id = callbackRegistry->add(func, data, delaySecs);

  if (resetTimer) {
    if (GetCurrentThreadId() == GetWindowThreadProcessId(hwnd, NULL)) {
      setupTimer();
    } else {
      // Not safe to setup the timer from this thread. Instead, send a
      // message to the main thread that the timer should be set up.
      PostMessage(hwnd, WM_SETUPTIMER, 0, 0);
    }
  }

  return callback_id;
}

#endif // ifdef _WIN32
