/*
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * windows_utils - Helper functions for Windows
 * Copyright (C) 2006 - 2024 Addy Yeow <ayeowch@gmail.com>
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#ifndef WINDOWS_UTILS_H
#define WINDOWS_UTILS_H

#ifdef _WIN32
#include <fcntl.h>

/* must be included before windows.h */
#include <winsock2.h>

/* must be included after winsock2.h */
#include <windows.h>

static void filetime_to_timeval(const FILETIME *ft, struct timeval *tv)
{
    /* number of 100-ns intervals between FILETIME epoch (1601) and Unix epoch (1970) */
    const uint64_t epoch_diff = 116444736000000000ULL;

    /* convert FILETIME (100-ns intervals) to us */
    uint64_t time = ((uint64_t)ft->dwHighDateTime << 32) + ft->dwLowDateTime;

    /* adjust to Unix epoch*/
    time -= epoch_diff;
    tv->tv_sec = (long)(time / 10000000ULL);
    tv->tv_usec = (long)((time % 10000000ULL) / 10);
}

int gettimeofday(struct timeval *tv, void *tz)
{
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);

    if (tv != NULL)
    {
        filetime_to_timeval(&ft, tv);
    }

    return 0;
}

void timersub(const struct timeval *end, const struct timeval *start, struct timeval *diff)
{
    diff->tv_sec = end->tv_sec - start->tv_sec;
    diff->tv_usec = end->tv_usec - start->tv_usec;

    if (diff->tv_usec < 0)
    {
        diff->tv_sec--;
        diff->tv_usec += 1000000;
    }
}

int clock_gettime(int clock_id, struct timespec *ts)
{
    static LARGE_INTEGER freq = {0};
    LARGE_INTEGER count;
    double seconds;

    if (freq.QuadPart == 0)
    {
        QueryPerformanceFrequency(&freq);
    }
    QueryPerformanceCounter(&count);

    seconds = (double)count.QuadPart / (double)freq.QuadPart;

    ts->tv_sec = (time_t)seconds;
    ts->tv_nsec = (long)((seconds - ts->tv_sec) * 1e9);

    return 0;
}

int nanosleep(const struct timespec *req, struct timespec *rem)
{
    int64_t total_usec = req->tv_sec * 1000000 + req->tv_nsec / 1000;

    if (total_usec >= 1000)
    {
        /* round to nearest millisecond */
        SleepEx((total_usec + 500) / 1000, FALSE);
    }
    else
    {
        static LARGE_INTEGER freq = {0};
        LARGE_INTEGER start, end;

        if (freq.QuadPart == 0)
        {
            /* ticks per second, e.g. 10M */
            QueryPerformanceFrequency(&freq);
        }
        QueryPerformanceCounter(&start);

        do
        {
            QueryPerformanceCounter(&end);
        } while (((end.QuadPart - start.QuadPart) * 1000000 / freq.QuadPart) < total_usec);
    }

    /* remaining time not applicable for Windows */
    if (rem != NULL)
    {
        rem->tv_sec = 0;
        rem->tv_nsec = 0;
    }

    return 0;
}

int strptime(const char *s, const char *format, struct tm *tm)
{
    return sscanf(s, "%d/%d/%d,%d:%d:%d", &tm->tm_mday, &tm->tm_mon, &tm->tm_year, &tm->tm_hour,
                  &tm->tm_min, &tm->tm_sec) == 6
               ? (tm->tm_mon -= 1, tm->tm_year -= 1900, 0)
               : -1;
}

FILE *fmemopen(void *buf, size_t size, const char *mode)
{
    int tmp_path_len;
    char tmp_path[MAX_PATH];
    char tmp_filename[MAX_PATH];
    HANDLE fh;
    DWORD bytes_written;
    int fd;
    FILE *fp;

    tmp_path_len = GetTempPathA(MAX_PATH, tmp_path);
    if (tmp_path_len == 0 || tmp_path_len > MAX_PATH)
    {
        (void)fprintf(stderr, "fmemopen(): GetTempPathA() failed\n");
        return NULL;
    }

    if (GetTempFileNameA(tmp_path, "tmp", 0, tmp_filename) == 0)
    {
        (void)fprintf(stderr, "fmemopen(): GetTempFileNameA() failed\n");
        return NULL;
    }

    fh = CreateFileA(tmp_filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                     FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);

    if (fh == INVALID_HANDLE_VALUE)
    {
        (void)fprintf(stderr, "fmemopen(): CreateFileA() failed\n");
        return NULL;
    }

    if (buf != NULL && size > 0)
    {
        if (!WriteFile(fh, buf, (DWORD)size, &bytes_written, NULL) || bytes_written != size)
        {
            (void)fprintf(stderr, "fmemopen(): WriteFile() failed\n");
            CloseHandle(fh);
            return NULL;
        }
    }

    SetFilePointer(fh, 0, NULL, FILE_BEGIN);

    fd = _open_osfhandle((intptr_t)fh, _O_RDWR);
    if (fd == -1)
    {
        (void)fprintf(stderr, "fmemopen(): _open_osfhandle() failed\n");
        CloseHandle(fh);
        return NULL;
    }

    fp = _fdopen(fd, mode);
    if (fp == NULL)
    {
        (void)fprintf(stderr, "fmemopen(): _fdopen() failed\n");
        _close(fd);
        return NULL;
    }

    return fp;
}
#else
#endif

#endif
