#include "System.h"
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#ifdef PLATFORM_WINDOWS
#include "windows.h"
#else
#include <sys/mman.h>
#endif

unsigned long System::page_size;
unsigned int System::page_size_log;
unsigned long System::page_base_mask;
unsigned long System::page_offset_mask;
static std::string Executable_Path;

bool System::init(char * argv[])
{
#if defined(PLATFORM_LINUX)
    long system_page_size = sysconf(_SC_PAGESIZE);
    if (system_page_size < 0)
    {
        fprintf(stderr, "Unable to determine operating system page size.\n");
        return false;
    }
#elif defined(PLATFORM_WINDOWS)
    SYSTEM_INFO system_info = {0};
    GetSystemInfo(&system_info);
    unsigned long system_page_size = system_info.dwPageSize;
#endif

    System::page_size = (unsigned long)system_page_size;
    System::page_size_log = 0u;
    System::page_offset_mask = System::page_size - 1u;
    System::page_base_mask = ~System::page_offset_mask;

    for (uint_fast8_t i = 0u; i < 32u; i++)
    {
        unsigned long v = 1u << i;
        if (v == System::page_size)
        {
            System::page_size_log = i;
            break;
        }
    }

    if ((System::page_size < 256u) ||
            (System::page_size > (4u * 1024u * 1024u)) ||
            (System::page_size_log == 0u))
    {
        fprintf(stderr, "Unsupported page size detected: 0x%lx\n", System::page_size);
        return false;
    }

#if defined(PLATFORM_LINUX)
    char link_buffer[1000];
    ssize_t len = readlink("/proc/self/exe", link_buffer, sizeof(link_buffer));
    if ((len > 0) && (len < (ssize_t)sizeof(link_buffer)))
    {
        Executable_Path = std::string(link_buffer, len);
    }
    else
    {
        return false;
    }
#elif defined(PLATFORM_WINDOWS)
    Executable_Path = argv[0];
#endif

    return true;
}

void * System::alloc_pages(int num)
{
#ifdef PLATFORM_WINDOWS
    return VirtualAlloc(
            NULL,
            num << System::page_size_log,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_READWRITE);
#else
    return mmap(
            NULL,
            num << System::page_size_log,
            PROT_READ | PROT_WRITE,
            MAP_PRIVATE | MAP_ANONYMOUS,
            -1,
            0);
#endif
}

void System::free_pages(void * addr, int num)
{
#ifdef PLATFORM_WINDOWS
    (void)VirtualFree(addr, 0, MEM_RELEASE);
#else
    (void)munmap(addr, num << System::page_size_log);
#endif
}

std::string System::executable_path()
{
    return Executable_Path;
}
