// Source: https://github.com/klauspost/mman-win32 (unlicensed)

/*
  Copyright (C) 2012 Klaus Post
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation, version 3.
  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.
  You should have received a copy of the GNU Lesser General Public
  License along with this program.  If not, see
  <http://www.gnu.org/licenses/>
*/

#include <sigutils/util/win32-mman.h>

#include <errno.h>
#include <io.h>
#include <windows.h>

#ifndef FILE_MAP_EXECUTE
#  define FILE_MAP_EXECUTE 0x0020
#endif /* FILE_MAP_EXECUTE */

static int
__map_mman_error(const DWORD err, const int deferr)
{
  if (err == 0)
    return 0;
  // TODO: implement
  return err;
}

static DWORD
__map_mmap_prot_page(const int prot)
{
  DWORD protect = 0;

  if (prot == PROT_NONE)
    return protect;

  if ((prot & PROT_EXEC) != 0) {
    protect =
        ((prot & PROT_WRITE) != 0) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
  } else {
    protect = ((prot & PROT_WRITE) != 0) ? PAGE_READWRITE : PAGE_READONLY;
  }

  return protect;
}

static DWORD
__map_mmap_prot_file(const int prot)
{
  DWORD desiredAccess = 0;

  if (prot == PROT_NONE)
    return desiredAccess;

  if ((prot & PROT_READ) != 0)
    desiredAccess |= FILE_MAP_READ;
  if ((prot & PROT_WRITE) != 0)
    desiredAccess |= FILE_MAP_WRITE;
  if ((prot & PROT_EXEC) != 0)
    desiredAccess |= FILE_MAP_EXECUTE;

  return desiredAccess;
}

void *
mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
{
  HANDLE fm, h;

  void *map = MAP_FAILED;

#ifdef _MSC_VER
#  pragma warning(push)
#  pragma warning(disable : 4293)
#endif

  const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD))
                                    ? (DWORD)off
                                    : (DWORD)(off & 0xFFFFFFFFL);
  const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD))
                                     ? (DWORD)0
                                     : (DWORD)((off >> 32) & 0xFFFFFFFFL);
  const DWORD protect = __map_mmap_prot_page(prot);
  const DWORD desiredAccess = __map_mmap_prot_file(prot);

  const off_t maxSize = off + (off_t)len;

  const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD))
                                 ? (DWORD)maxSize
                                 : (DWORD)(maxSize & 0xFFFFFFFFL);
  const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD))
                                  ? (DWORD)0
                                  : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL);

#ifdef _MSC_VER
#  pragma warning(pop)
#endif

  errno = 0;

  if (len == 0
      /* Unsupported flag combinations */
      || (flags & MAP_FIXED) != 0
      /* Usupported protection combinations */
      || prot == PROT_EXEC) {
    errno = EINVAL;
    return MAP_FAILED;
  }

  h = ((flags & MAP_ANONYMOUS) == 0) ? (HANDLE)_get_osfhandle(fildes)
                                     : INVALID_HANDLE_VALUE;

  if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) {
    errno = EBADF;
    return MAP_FAILED;
  }

  fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL);

  if (fm == NULL) {
    errno = __map_mman_error(GetLastError(), EPERM);
    return MAP_FAILED;
  }

  map =
      MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len);

  CloseHandle(fm);

  if (map == NULL) {
    errno = __map_mman_error(GetLastError(), EPERM);
    return MAP_FAILED;
  }

  return map;
}

int
munmap(void *addr, size_t len)
{
  if (UnmapViewOfFile(addr))
    return 0;

  errno = __map_mman_error(GetLastError(), EPERM);

  return -1;
}

int
mprotect(void *addr, size_t len, int prot)
{
  DWORD newProtect = __map_mmap_prot_page(prot);
  DWORD oldProtect = 0;

  if (VirtualProtect(addr, len, newProtect, &oldProtect))
    return 0;

  errno = __map_mman_error(GetLastError(), EPERM);

  return -1;
}

int
msync(void *addr, size_t len, int flags)
{
  if (FlushViewOfFile(addr, len))
    return 0;

  errno = __map_mman_error(GetLastError(), EPERM);

  return -1;
}

int
mlock(const void *addr, size_t len)
{
  if (VirtualLock((LPVOID)addr, len))
    return 0;

  errno = __map_mman_error(GetLastError(), EPERM);

  return -1;
}

int
munlock(const void *addr, size_t len)
{
  if (VirtualUnlock((LPVOID)addr, len))
    return 0;

  errno = __map_mman_error(GetLastError(), EPERM);

  return -1;
}