#include "fsutils.h"
#include "runtime.h"
#include "threading.h"

#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

struct rt_scandir_handle_s {
    HANDLE handle;
    rt_dirent next;
};

static rt_scandir_handle _dirs[256];
static unsigned int _next = 0;
static rt_mutex *_guard   = NULL;

RT_DLLEXPORT rt_scandir_handle *rtScanDirectory(const char *path) {
    char wildcard_path[MAX_PATH];
    strncpy(wildcard_path, path, MAX_PATH);
    strncat(wildcard_path, "\\*", MAX_PATH - strlen(path));
    WCHAR wpath[MAX_PATH];
    if (rtUTF8ToWStr(wildcard_path, wpath, MAX_PACKAGE_NAME) != RT_SUCCESS)
        return NULL;
    WIN32_FIND_DATAW data;
    HANDLE h = FindFirstFileW(wpath, &data);
    if (h == INVALID_HANDLE_VALUE)
        return NULL;

    if (!_guard) {
        _guard = rtCreateMutex();
        if (!_guard) {
            FindClose(h);
            return NULL;
        }
    }

    rtLockMutex(_guard);
    rt_scandir_handle *dir = &_dirs[_next];
    _next                  = (_next + 1) % RT_ARRAY_COUNT(_dirs);
    rtUnlockMutex(_guard);

    if (dir->handle != NULL) {
        rtLog("core", "Failed to acquire a scandir handle.");
        FindClose(h);
        return NULL;
    }

    dir->handle       = h;
    dir->next.is_last = false;
    rtWStrToUTF8(data.cFileName, dir->next.name, 260);
    if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        dir->next.type = RT_DIRENT_TYPE_DIRECTORY;
    else
        dir->next.type = RT_DIRENT_TYPE_FILE;

    return dir;
}

RT_DLLEXPORT rt_dirent rtNextDirectoryEntry(rt_scandir_handle *dir) {
    rt_dirent current;
    memcpy(&current, &dir->next, sizeof(rt_dirent));

    WIN32_FIND_DATAW data;
    if (!FindNextFileW(dir->handle, &data)) {
        current.is_last = true;
        return current;
    }
    rtWStrToUTF8(data.cFileName, dir->next.name, 260);
    if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        dir->next.type = RT_DIRENT_TYPE_DIRECTORY;
    else
        dir->next.type = RT_DIRENT_TYPE_FILE;

    return current;
}

RT_DLLEXPORT void rtCloseDirectory(rt_scandir_handle *dir) {
    if (!dir)
        return;
    FindClose(dir->handle);
    dir->handle = NULL;
}

RT_DLLEXPORT bool rtCreateDirectory(const char *path) {
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wpath, MAX_PATH);
    return CreateDirectoryW(wpath, NULL);
}

RT_DLLEXPORT size_t rtGetFileSize(const char *path) {
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wpath, MAX_PATH);
    WIN32_FILE_ATTRIBUTE_DATA attribs;
    if (!GetFileAttributesExW(wpath, GetFileExInfoStandard, &attribs))
        return 0;
    return (size_t)attribs.nFileSizeHigh << 32 | (size_t)attribs.nFileSizeLow;
}

uint64_t rtGetCurrentTimestamp(void) {
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);
    uint64_t ts = ft.dwLowDateTime;
    ts |= (uint64_t)ft.dwHighDateTime << 32;
    return ts;
}

uint64_t rtGetFileModificationTimestamp(const char *path) {
    WCHAR wpath[MAX_PATH];
    MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wpath, MAX_PATH);
    WIN32_FILE_ATTRIBUTE_DATA attribs;
    if (!GetFileAttributesExW(wpath, GetFileExInfoStandard, &attribs))
        return 0;
    uint64_t ts = attribs.ftLastWriteTime.dwLowDateTime;
    ts |= (uint64_t)attribs.ftLastWriteTime.dwHighDateTime << 32;
    return ts;
}

#elif defined(__linux__)

#include <string.h>
#include <sys/dir.h>
#include <sys/stat.h>
#include <time.h>

struct rt_scandir_handle_s {
    DIR *handle;
    rt_dirent next;
};

static rt_scandir_handle _dirs[256];
static unsigned int _next = 0;
static rt_mutex *_guard   = NULL;

RT_DLLEXPORT rt_scandir_handle *rtScanDirectory(const char *path) {
    DIR *h = opendir(path);
    if (!h)
        return NULL;

    if (!_guard) {
        _guard = rtCreateMutex();
        if (!_guard) {
            closedir(h);
            return NULL;
        }
    }

    rtLockMutex(_guard);
    rt_scandir_handle *dir = &_dirs[_next];
    _next                  = (_next + 1) % RT_ARRAY_COUNT(_dirs);
    rtUnlockMutex(_guard);

    if (dir->handle != NULL) {
        rtLog("core", "Failed to acquire a scandir handle.");
        closedir(h);
        return NULL;
    }

    struct dirent *ent = readdir(h);
    if (!ent) {
        closedir(h);
        return NULL;
    }
    dir->handle       = h;
    dir->next.is_last = false;
    memcpy(dir->next.name, ent->d_name, 255);
    if (ent->d_type == DT_DIR)
        dir->next.type = RT_DIRENT_TYPE_DIRECTORY;
    else
        dir->next.type = RT_DIRENT_TYPE_FILE;

    return dir;
}

RT_DLLEXPORT rt_dirent rtNextDirectoryEntry(rt_scandir_handle *dir) {
    rt_dirent current;
    memcpy(&current, &dir->next, sizeof(rt_dirent));

    struct dirent *ent = readdir(dir->handle);
    if (!ent) {
        current.is_last = true;
        return current;
    }
    memcpy(dir->next.name, ent->d_name, 255);
    if (ent->d_type == DT_DIR)
        dir->next.type = RT_DIRENT_TYPE_DIRECTORY;
    else
        dir->next.type = RT_DIRENT_TYPE_FILE;
    return current;
}

RT_DLLEXPORT void rtCloseDirectory(rt_scandir_handle *dir) {
    if (!dir)
        return;
    closedir(dir->handle);
    dir->handle = NULL;
}

RT_DLLEXPORT bool rtCreateDirectory(const char *path) {
    return mkdir(path, S_IRWXU | S_IRGRP) == 0;
}

RT_DLLEXPORT size_t rtGetFileSize(const char *path) {
    struct stat st;
    if (stat(path, &st) != 0)
        return 0;
    return (size_t)st.st_size;
}

uint64_t rtGetCurrentTimestamp(void) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    return (uint64_t)ts.tv_sec;
}

uint64_t rtGetFileModificationTimestamp(const char *path) {
    struct stat st;
    if (stat(path, &st) != 0)
        return 0;
    return (uint64_t)st.st_mtim.tv_sec;
}

#endif
