#include <io.h>
#include <process.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

// THis file exists to avoid clashes between windows.h and idris_rts.h
//

int win_fpoll(FILE *f) {
  HANDLE wh = (HANDLE)_get_osfhandle(_fileno(f));
  if (wh == INVALID_HANDLE_VALUE) {
    return -1;
  }
  DWORD ret = WaitForSingleObject(wh, 1000);
  // Imitate the return values of select()
  if (ret == WAIT_OBJECT_0)
    return 1;
  if (ret == WAIT_TIMEOUT)
    return 0;
  return -1;
}

int widen_utf8(const char *filename_utf8, LPWSTR *filename_w) {
  int num_chars = MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, 0, 0);
  int size = sizeof(WCHAR);
  *filename_w = (LPWSTR)malloc(size * num_chars);
  MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, *filename_w, num_chars);
  return num_chars;
}

FILE *win32_u8fopen(const char *path, const char *mode) {
  LPWSTR wpath, wmode;
  widen_utf8(path, &wpath);
  widen_utf8(mode, &wmode);
  FILE *f = _wfopen(wpath, wmode);
  free(wpath);
  free(wmode);
  return f;
}

FILE *win32_u8popen(const char *path, const char *mode) {
  LPWSTR wpath, wmode;
  widen_utf8(path, &wpath);
  widen_utf8(mode, &wmode);
  FILE *f = _wpopen(wpath, wmode);
  free(wpath);
  free(wmode);
  return f;
}

void win32_gettime(int64_t *sec, int64_t *nsec) {
  FILETIME ft;
#ifdef _OLD_WIN
  GetSystemTimeAsFileTime(&ft);
#else
  // For Windows NT 6.2 or higher
  GetSystemTimePreciseAsFileTime(&ft);
#endif
  ULARGE_INTEGER t;
  t.HighPart = ft.dwHighDateTime;
  t.LowPart = ft.dwLowDateTime;

  *nsec = (t.QuadPart % 10000000) * 100;
  *sec = t.QuadPart / 10000000;
  *sec -= 11644473600; // LDAP epoch to Unix epoch
}

void win32_sleep(int ms) { Sleep(ms); }

int win32_modenv(const char *name, const char *value, int overwrite) {
  if (!overwrite && getenv(name))
    return 0;
  return _putenv_s(name, value);
}

int win32_getErrno() { return GetLastError(); }

int win32_getPID() { return _getpid(); }

typedef BOOL(WINAPI *LPFN_GLPI)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);

long win32_getNProcessors() {
  // largely taken from
  // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformation

  BOOL done = FALSE;
  long nPhysicalProcessors = 0;
  long nSMTProcessors = 0;

  // length of array for storing the information structs
  DWORD returnLength = 0;
  // structs for storing the information
  PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL;
  PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL;

  // shortcut to a function (?)
  LPFN_GLPI glpi;
  glpi = (LPFN_GLPI)GetProcAddress(GetModuleHandle(TEXT("kernel32")),
                                   "GetLogicalProcessorInformation");
  // to keep track of whether we're at the end of the array
  DWORD byteOffset = 0;

  // repeatedly try to malloc and retrieve the information until we have a
  // large enough array of information structs, or we fail to malloc
  while (!done) {
    DWORD rc = glpi(buffer, &returnLength);
    if (rc == FALSE) {
      if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
        // this will happen initially since buffer = NULL and we need to
        // somehow retrieve the required size

        if (buffer) {
          free(buffer);
        }

        buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(returnLength);

        if (NULL == buffer) {
          // memory allocation error
          return -1;
        }

      } else {
        // something else went wrong
        return -1;
      }
    } else {
      done = TRUE;
    }
  }

  ptr = buffer;

  while ((byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) <=
         returnLength) {
    // if we have a processor core, count the processors
    if (ptr->Relationship == RelationProcessorCore) {
      nPhysicalProcessors++;

      // if we have an SMT-enabled CPU, we need to count the virtual
      // cores as well
      DWORD lshift = sizeof(ULONG_PTR) * 8 - 1;
      ULONG_PTR bitTest = (ULONG_PTR)1 << lshift;
      DWORD i;
      for (i = 0; i <= lshift; ++i) {
        // count the bit if it is set
        nSMTProcessors += ((ptr->ProcessorMask & bitTest) ? 1 : 0);
        // move the test to the next bit
        bitTest /= 2;
      }
    }
    // move to next info element
    byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
    ptr++;
  }

  // return the larger number of physical vs SMT cores
  // (if this bothers you, overhaul this implementation with a solution that
  // distinguishes between types of cores on _both_ *NIX and Windows!)
  return nPhysicalProcessors == nSMTProcessors ? nPhysicalProcessors
                                               : nSMTProcessors;
}

int win32_getFileNo(FILE *f) { return _fileno(f); }

int win32_getFileTime(FILE *f, int *atime_sec, int *atime_nsec, int *mtime_sec,
                      int *mtime_nsec, int *ctime_sec, int *ctime_nsec) {
  HANDLE wh = (HANDLE)_get_osfhandle(_fileno(f));
  if (wh == INVALID_HANDLE_VALUE) {
    return -1;
  }

  FILETIME atime, mtime, ctime;

  if (GetFileTime(wh, &ctime, &atime, &mtime)) {
    ULARGE_INTEGER at, mt, ct;

    at.HighPart = atime.dwHighDateTime;
    at.LowPart = atime.dwLowDateTime;
    mt.HighPart = mtime.dwHighDateTime;
    mt.LowPart = mtime.dwLowDateTime;
    ct.HighPart = ctime.dwHighDateTime;
    ct.LowPart = ctime.dwLowDateTime;

    *atime_sec = at.QuadPart / 10000000;
    *atime_sec -= 11644473600;
    *atime_nsec = (at.QuadPart % 10000000) * 100;

    *mtime_sec = mt.QuadPart / 10000000;
    *mtime_sec -= 11644473600;
    *mtime_nsec = (mt.QuadPart % 10000000) * 100;

    *ctime_sec = ct.QuadPart / 10000000;
    *ctime_sec -= 11644473600;
    *ctime_nsec = (ct.QuadPart % 10000000) * 100;

    return 0;
  } else {
    return -1;
  }
}

int win32_isTTY(int fd) { return _isatty(fd); }
