#include <SDL3/SDL.h>
#include <windows.h>

#include "dirmonitor.h"

struct dirmonitor_internal {
  HANDLE handle;
};


static int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size) {
  HANDLE handle = monitor->handle;
  if (handle && handle != INVALID_HANDLE_VALUE) {
    DWORD bytes_transferred;
    DWORD filter =
      FILE_NOTIFY_CHANGE_FILE_NAME
      | FILE_NOTIFY_CHANGE_DIR_NAME
      | FILE_NOTIFY_CHANGE_ATTRIBUTES
      | FILE_NOTIFY_CHANGE_SIZE
      | FILE_NOTIFY_CHANGE_LAST_WRITE
      | FILE_NOTIFY_CHANGE_CREATION
      | FILE_NOTIFY_CHANGE_SECURITY;
    if (ReadDirectoryChangesW(handle, buffer, buffer_size, TRUE, filter, &bytes_transferred, NULL, NULL) == 0)
      return 0;
    return bytes_transferred;
  }
  return 0;
}


static struct dirmonitor_internal* init_dirmonitor(void) {
  return SDL_calloc(1, sizeof(struct dirmonitor_internal));
}


static void close_monitor_handle(struct dirmonitor_internal* monitor) {
  if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) {
    HANDLE handle = monitor->handle;
    monitor->handle = NULL;
    CancelIoEx(handle, NULL);
    CloseHandle(handle);
  }
}


static void deinit_dirmonitor(struct dirmonitor_internal* monitor) {
  close_monitor_handle(monitor);
}


static int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int buffer_size, int (*change_callback)(int, const char*, void*), void* data) {
  for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)buffer; (char*)info < buffer + buffer_size; info = (FILE_NOTIFY_INFORMATION*)(((char*)info) + info->NextEntryOffset)) {
    char transform_buffer[MAX_PATH*4];
    int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)info->FileName, info->FileNameLength / 2, transform_buffer, MAX_PATH*4 - 1, NULL, NULL);
    change_callback(count, transform_buffer, data);
    if (!info->NextEntryOffset)
      break;
  }
  return 0;
}


static int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) {
  close_monitor_handle(monitor);
  monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
  return !monitor->handle || monitor->handle == INVALID_HANDLE_VALUE ? -1 : 1;
}


static  void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) {
  close_monitor_handle(monitor);
}


static int get_mode_dirmonitor(void) { return 1; }

struct dirmonitor_backend dirmonitor_win32 = {
  .name = "win32",
  .init = init_dirmonitor,
  .deinit = deinit_dirmonitor,
  .get_changes = get_changes_dirmonitor,
  .translate_changes = translate_changes_dirmonitor,
  .add = add_dirmonitor,
  .remove = remove_dirmonitor,
  .get_mode = get_mode_dirmonitor,
};
