#include "efence.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static caddr_t	startAddr = (caddr_t) 0;


/* TODO: This function is not thread safe. */
static const char *
stringErrorReport(void)
{
    static char buf[1024];
    DWORD err = GetLastError();
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | 78, NULL, err, 0, buf, sizeof(buf), NULL);

    buf[sizeof(buf)-1] = 0;
    return buf;
}

/*
 * Create memory.
 */
void *
Page_Create(size_t size)
{
    caddr_t		allocation;

	/*
	 * In this version, "startAddr" is a _hint_, not a demand.
	 * When the memory I map here is contiguous with other
	 * mappings, the allocator can coalesce the memory from two
	 * or more mappings into one large contiguous chunk, and thus
	 * might be able to find a fit that would not otherwise have
	 * been possible. I could _force_ it to be contiguous by using
	 * the MMAP_FIXED flag, but I don't want to stomp on memory mappings
	 * generated by other software, etc.
	 */
    allocation = (caddr_t) VirtualAlloc(
	 (LPVOID)startAddr
	,size
	,MEM_RESERVE|MEM_COMMIT
	,PAGE_READWRITE);

#ifndef	_WIN32
	/*
	 * Set the "address hint" for the next mmap() so that it will abut
	 * the mapping we just created.
	 *
	 * Windows doesn't treat the address as a hint.
	 * 
	 * Maybe TODO: Hint and retry when the hint fails.
	 */
    startAddr = allocation + size;
#endif

    if ( allocation == 0 )
	EF_Exit("VirtualAlloc() failed: %s", stringErrorReport());

    return (void *)allocation;
}

static DWORD AllocationGranularity;

static void
mprotectFailed(void)
{
    EF_Exit("VirtualProtect() failed: %s", stringErrorReport());
}

/* Win32's VirtualProtect does not work across VirtualAlloc boundaries */
/* Therefore, avoid the problem by doing all operations in AllocationGranularity sized chunks */

void
Page_AllowAccess(void * address, size_t size)
{
    DWORD old;
    while (size) {
	size_t mySize;
	mySize = min(size, AllocationGranularity - ((DWORD_PTR)address) % AllocationGranularity); 
	if (!VirtualProtect(address, mySize, PAGE_READWRITE, &old))
	    mprotectFailed();
	size -= mySize;
	address = (void *)((DWORD_PTR)address + mySize);
    }
}

void
Page_DenyAccess(void * address, size_t size)
{
    DWORD old;
    while (size) {
	size_t mySize;
	mySize = min(size, AllocationGranularity - ((DWORD_PTR)address) % AllocationGranularity); 
	if (!VirtualProtect(address, mySize, PAGE_NOACCESS, &old))
	    mprotectFailed();
	size -= mySize;
	address = (void *)((DWORD_PTR)address + mySize);
    }
}

void
Page_Delete(void * address, size_t size)
{
//    Page_DenyAccess(address, size);
    while (size) {
	size_t mySize;
	mySize = min(size, AllocationGranularity - ((DWORD_PTR)address) % AllocationGranularity); 
	if (!VirtualFree(address, mySize, MEM_DECOMMIT))
	    EF_Exit("VirtualFree() failed: %s", stringErrorReport());
	size -= mySize;
	address = (void *)((DWORD_PTR)address + mySize);
    }
}

size_t
Page_Size(void)
{
    SYSTEM_INFO info;

    GetSystemInfo(&info);
    AllocationGranularity = info.dwAllocationGranularity;
    return info.dwPageSize;
}
