/* vim: set ts=4 sw=4 tw=99 et:
 *
 * Copyright (C) 2012 David Anderson
 *
 * This file is part of JITCraft.
 *
 * JITCraft is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * Foobar 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * JITCraft. If not, see http://www.gnu.org/licenses/.
 */
#include <sys/mman.h>
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include "Platform.h"

using namespace ke;

static inline int
ToProtBits(OS::Protection prot)
{
    switch (prot) {
      case OS::Prot_ReadWrite:
        return PROT_READ | PROT_WRITE;
      case OS::Prot_ReadWriteExecute:
        return PROT_READ | PROT_WRITE | PROT_EXEC;
      default:
        assert(prot == OS::Prot_ReadExecute);
        return PROT_READ | PROT_EXEC;
    }
}

Address
OS::MapMemory(size_t amount, MapMode mode, Protection prot_)
{
    int prot = ToProtBits(prot_);
    void *addr = mmap(NULL, amount, prot, MAP_ANON | MAP_PRIVATE, -1, 0);
    if (addr == MAP_FAILED)
        return NULL;

    return Address(addr);
}

bool
OS::CommitMemory(Address address, size_t amount, Protection prot_)
{
    return true;
}

void
OS::UnmapMemory(Address address, size_t size)
{
    munmap(address, size);
}

OS::AlignableMapping
OS::MapAlignedMemory(size_t amount, size_t alignment, MapMode mode)
{
    assert(amount >= alignment);
    assert(IsPowerOfTwo(alignment));

    if (alignment == kPageAlignment) {
        Address address = MapMemory(amount, mode, Prot_ReadWrite);
        if (!address)
            return AlignableMapping();
        return AlignableMapping(address, alignment, amount);
    }

    size_t requested = Min(amount * 2, amount + 2 * alignment);
    Address address = MapMemory(requested, mode, Prot_ReadWrite);
    if (!address)
        return AlignableMapping();

    size_t begin = 0;
    Address aligned = AlignedBase(address, alignment);
    if (!IsAligned(address, alignment)) {
        // Shave off the first part of the unaligned region.
        aligned += alignment;
        begin = size_t(aligned - address);
        UnmapAlignedMemory(address, begin);
    }

    if (requested - begin > amount) {
        // Shave off the last part of the unaligned region.
        UnmapAlignedMemory(aligned + amount, requested - amount - begin);
    }

    return AlignableMapping(aligned, alignment, amount);
}

void
OS::UnmapAlignedMemory(Address address, size_t size)
{
    OS::UnmapMemory(address, size);
}

bool
OS::GetAbsolutePath(const char *path, AutoFree<char> &chars)
{
    chars = (char *)calloc(PATH_MAX, sizeof(char));
    if (!*chars)
        return false;

    char *ptr = realpath(path, *chars);
    if (!ptr)
        return false;

    return ptr == *chars;
}

bool
OS::SplitPath(const char *path, AutoFree<char> &base, AutoFree<char> &name)
{
    // dirname() and basename() pretty much suck.
    size_t len = strlen(path);

    // Skip trailing slashes.
    while (len > 1 && (path[len - 1] == '/'))
        len--;

    for (size_t i = len - 1; i < len; i--) {
        if (path[i] == '/') {
            name = (char *)calloc(len - i, sizeof(char));
            if (!*name)
                return false;

            strcpy(*name, &path[i + 1]);

            base = (char *)calloc(i + 1, sizeof(char));
            if (!*base)
                return false;

            memcpy(*base, path, i);
            (*base)[i] = '\0';
            return true;
        }
    }
    
    // The path should have been absolute, which contains at least one separator.
    return false;
}

const char *
OS::PathSeparator()
{
    return "/";
}

OS::PathType
OS::InspectPath(const char *path)
{
    struct stat s;
    int result = stat(path, &s);
    if (result != 0)
        return OS::Path_Invalid;

    if (s.st_mode & S_IFDIR)
        return OS::Path_Folder;
    if (s.st_mode & S_IFREG)
        return OS::Path_File;

    return OS::Path_Invalid;
}

