#include "ffi_platypus.h"

#ifdef PERL_OS_WINDOWS

#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#ifdef HAVE_SYS_CYGWIN_H
#include <sys/cygwin.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
/*
 * TODO: c::ac is not detecting psapi.h for some reason ...
 * but it should always be there in any platform that
 * we support
 */
#include <psapi.h>

typedef struct _library_handle {
  int is_null;
  int flags;
  HMODULE os_handle;
} library_handle;

static const char *error = NULL;

/*
 * dlopen()
 */

void *
windlopen(const char *filename, int flags)
{
  char *win_path_filename;
  library_handle *handle;

  win_path_filename = NULL;

#ifdef PERL_OS_CYGWIN
  if(filename != NULL)
  {
    ssize_t size;
    size = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_RELATIVE, filename, NULL, 0);
    if(size < 0)
    {
      error = "unable to determine length of string for cygwin_conv_path";
      return NULL;
    }
    win_path_filename = malloc(size);
    if(win_path_filename == NULL)
    {
      error = "unable to allocate enough memory for cygwin_conv_path";
      return NULL;
    }
    if(cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_RELATIVE, filename, win_path_filename, size))
    {
      error = "error in conversion for cygwin_conv_path";
      free(win_path_filename);
      return NULL;
    }
    filename = win_path_filename;
  }
#endif

  handle = malloc(sizeof(library_handle));

  if(handle == NULL)
  {
    if(win_path_filename != NULL)
      free(win_path_filename);
    error = "unable to allocate memory for handle";
    return NULL;
  }

  if(filename == NULL)
  {
    handle->is_null = 1;
  }
  else
  {
    handle->is_null = 0;
    handle->os_handle = LoadLibrary(filename);
    if(handle->os_handle == NULL)
    {
      free(handle);
      error = "Error loading file";
      return NULL;
    }
  }

  handle->flags = flags;

  if(win_path_filename != NULL)
    free(win_path_filename);
  error = NULL;
  return (void*) handle;
}

/*
 * dlsym()
 */

void *
windlsym(void *void_handle, const char *symbol_name)
{
  library_handle *handle = (library_handle*) void_handle;
  static const char *not_found = "symbol not found";
  void *symbol;

  if(!handle->is_null)
  {
    symbol = GetProcAddress(handle->os_handle, symbol_name);
    if(symbol == NULL)
      error = not_found;
    else
      error = NULL;
    return symbol;
  }
  else
  {
    int n;
    DWORD needed;
    HANDLE process;
    HMODULE mods[1024];
    TCHAR mod_name[MAX_PATH];

    process = OpenProcess(
      PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
      FALSE, GetCurrentProcessId()
    );

    if(process == NULL)
    {
      error = "Process for self not found";
      return NULL;
    }

    if(EnumProcessModules(process, mods, sizeof(mods), &needed))
    {
      for(n=0; n < (needed/sizeof(HMODULE)); n++)
      {
        if(GetModuleFileNameEx(process, mods[n], mod_name, sizeof(mod_name) / sizeof(TCHAR)))
        {
          HMODULE handle = LoadLibrary(mod_name);
          if(handle == NULL)
            continue;
          symbol = GetProcAddress(handle, symbol_name);

          if(symbol != NULL)
          {
            error = NULL;
            FreeLibrary(handle);
            return symbol;
          }

          FreeLibrary(handle);
        }
      }
    }
    error = not_found;
    return NULL;
  }
}

/*
 * dlerror()
 */

const char *
windlerror(void)
{
  return error;
}

/*
 * dlclose()
 */

int
windlclose(void *void_handle)
{
  library_handle *handle = (library_handle*) void_handle;
  if(!handle->is_null)
  {
    FreeLibrary(handle->os_handle);
  }
  free(handle);
  error = NULL;
  return 0;
}

#endif
