#include "mapped_file.h"

#include <stdexcept>

#ifdef _WIN32
mapped_file_win::mapped_file_win() : data(nullptr), size(0), file_handle(INVALID_HANDLE_VALUE), mapping_handle(nullptr) {}

mapped_file_win::~mapped_file_win() {
    cleanup();
}

mapped_file_win::mapped_file_win(mapped_file_win&& other) noexcept
    : data(other.data), size(other.size), file_handle(other.file_handle), mapping_handle(other.mapping_handle) {
    other.data = nullptr;
    other.size = 0;
    other.file_handle = INVALID_HANDLE_VALUE;
    other.mapping_handle = nullptr;
}

mapped_file_win& mapped_file_win::operator=(mapped_file_win&& other) noexcept {
    if (this == &other)
        return *this;
    cleanup();
    data = other.data;
    size = other.size;
    file_handle = other.file_handle;
    mapping_handle = other.mapping_handle;

    other.data = nullptr;
    other.size = 0;
    other.file_handle = INVALID_HANDLE_VALUE;
    other.mapping_handle = nullptr;
    return *this;
}

bool mapped_file_win::map_file(const std::wstring& filename) {
    cleanup();

    file_handle = CreateFileW(
        filename.c_str(),
        GENERIC_READ,
        FILE_SHARE_READ,
        nullptr,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        nullptr
    );

    if (file_handle == INVALID_HANDLE_VALUE) {
        throw std::runtime_error("Failed to open file");
    }

    LARGE_INTEGER file_size;
    if (!GetFileSizeEx(file_handle, &file_size)) {
        cleanup();
        throw std::runtime_error("Failed to get file size");
    }
    size = static_cast<size_t>(file_size.QuadPart);

    mapping_handle = CreateFileMappingW(
        file_handle,
        nullptr,
        PAGE_READONLY,
        0,
        0,
        nullptr
    );

    if (mapping_handle == nullptr) {
        cleanup();
        throw std::runtime_error("Failed to create file mapping");
    }

    data = MapViewOfFile(
        mapping_handle,
        FILE_MAP_READ,
        0,
        0,
        0
    );

    if (data == nullptr) {
        cleanup();
        throw std::runtime_error("Failed to map view of file");
    }

    return true;
}

void mapped_file_win::cleanup() {
    if (data) {
        UnmapViewOfFile(data);
        data = nullptr;
    }
    if (mapping_handle) {
        CloseHandle(mapping_handle);
        mapping_handle = nullptr;
    }
    if (file_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(file_handle);
        file_handle = INVALID_HANDLE_VALUE;
    }
    size = 0;
}

void mapped_file_win::unmap() {
    cleanup();
}
#else
mapped_file_unix::mapped_file_unix() : data(nullptr), size(0), fd(-1) {}

mapped_file_unix::~mapped_file_unix() {
    cleanup();
}

mapped_file_unix::mapped_file_unix(mapped_file_unix&& other) noexcept
    : data(other.data), size(other.size), fd(other.fd) {
    other.data = nullptr;
    other.size = 0;
    other.fd = -1;
}

mapped_file_unix& mapped_file_unix::operator=(mapped_file_unix&& other) noexcept {
    if (this != &other) {
        cleanup();
        data = other.data;
        size = other.size;
        fd = other.fd;

        other.data = nullptr;
        other.size = 0;
        other.fd = -1;
    }
    return *this;
}

bool mapped_file_unix::map_file(const std::wstring& filename) {
    cleanup();

    std::string utf8_filename(filename.begin(), filename.end());
    fd = open(utf8_filename.c_str(), O_RDONLY);
    if (fd == -1) {
        throw std::runtime_error("Failed to open file");
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        cleanup();
        throw std::runtime_error("Failed to get file size");
    }
    size = static_cast<size_t>(sb.st_size);

    data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (data == MAP_FAILED) {
        cleanup();
        throw std::runtime_error("Failed to map file");
    }

    return true;
}

void mapped_file_unix::cleanup() {
    if (data) {
        munmap(data, size);
        data = nullptr;
    }
    if (fd != -1) {
        close(fd);
        fd = -1;
    }
    size = 0;
}

void mapped_file_unix::unmap() {
    cleanup();
}
#endif
