#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <windows.h>
#include <ntstatus.h>
#include <winternl.h>

#include "os.h"

int __mingw_sprintf(char* buffer, const char* format, ...)
{
    va_list args;
    int ret;

    va_start(args, format);
    ret = vsprintf(buffer, format, args);
    va_end(args);

    return ret;
}

int __isnanf(float x)
{
    union { float x; unsigned int i; } u = { x };
    return (u.i & 0x7fffffff) > 0x7f800000;
}

int __isnan(double x)
{
    union { double x; unsigned __int64 i; } u = { x };
    return (u.i & ~0ull >> 1) > 0x7ffull << 52;
}

double math_error(int type, const char *name, double arg1, double arg2, double retval)
{
    return retval;
}

int __fpclassify(double x)
{
	union {double f; uint64_t i;} u = {x};
	int e = u.i>>52 & 0x7ff;
	if (!e) return u.i<<1 ? FP_SUBNORMAL : FP_ZERO;
	if (e==0x7ff) return u.i<<12 ? FP_NAN : FP_INFINITE;
	return FP_NORMAL;
}

int __fpclassifyf(float x)
{
	union {float f; uint32_t i;} u = {x};
	int e = u.i>>23 & 0xff;
	if (!e) return u.i<<1 ? FP_SUBNORMAL : FP_ZERO;
	if (e==0xff) return u.i<<9 ? FP_NAN : FP_INFINITE;
	return FP_NORMAL;
}

int fegetround (void)
{
    return 0;
}

int fesetround (int __rounding_direction)
{
    return 0;
}

div_t __cdecl div(int num, int denom)
{
    div_t ret;

    ret.quot = num / denom;
    ret.rem = num % denom;
    return ret;
}

ldiv_t __cdecl ldiv(long num, long denom)
{
    ldiv_t ret;

    ret.quot = num / denom;
    ret.rem = num % denom;
    return ret;
}

void _assert (const char *_Message, const char *_File, unsigned _Line)
{
    // NYI
}

char* strerror(int e)
{
    return "error";
}

int snprintf(char* restrict s, size_t n, const char* restrict fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    int result = _vsnprintf(s, n, fmt, args);
    va_end(args);
    return result;
}

char* strdup(const char* s)
{
    char* buf = (char*)RtlAllocateHeap(GetProcessHeap(), 0, strlen(s) + 1);
    if (buf) strcpy(buf, s);
    return buf;
}

long long atoll(const char* str)
{
    ULONG tmp;
    RtlCharToInteger(str, 10, &tmp);
    return (LONGLONG)tmp;
}

long long strtoll(const char* restrict str, char** restrict str_end, int base)
{
    // FIXME: it kinda work, but not identical to the C version.
    ULONG tmp;
    if (base == 0) {
        NTSTATUS status = RtlCharToInteger(str, 10, &tmp);
        if (status != STATUS_SUCCESS) RtlCharToInteger(str, 16, &tmp);
    } else {
        RtlCharToInteger(str, base, &tmp);
    }
    return (LONGLONG)tmp;
}

int strcasecmp(const char *s1, const char *s2)
{
    while (*s1 && *s2) {
        int diff = tolower(*s1) - tolower(*s2);
        if (diff) return diff;
        s1++;
        s2++;
    }
    return tolower(*s1) - tolower(*s2);
}

BOXFILE* box_fopen(const char* filename, const char* mode)
{
    DWORD dwDesiredAccess = 0;
    DWORD dwCreationDisposition = 0;

    if (strcmp(mode, "r") == 0) {
        dwDesiredAccess = GENERIC_READ;
        dwCreationDisposition = OPEN_EXISTING;
    } else if (strcmp(mode, "w") == 0) {
        dwDesiredAccess = GENERIC_WRITE;
        dwCreationDisposition = CREATE_ALWAYS;
    } else if (strcmp(mode, "a") == 0) {
        dwDesiredAccess = FILE_APPEND_DATA;
        dwCreationDisposition = OPEN_ALWAYS;
    } else {
        return NULL;
    }

    HANDLE hFile = CreateFileA(
        filename,
        dwDesiredAccess,
        FILE_SHARE_READ,
        NULL,
        dwCreationDisposition,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
        NULL);

    if (hFile == INVALID_HANDLE_VALUE) return NULL;

    BOXFILE* file = (BOXFILE*)WinMalloc(sizeof(BOXFILE));
    if (!file) {
        CloseHandle(hFile);
        return NULL;
    }

    file->hFile = hFile;
    file->buf_pos = 0;
    file->buf_size = 0;
    file->eof = 0;

    return file;
}

char* box_fgets(char* str, int num, BOXFILE* stream)
{
    if (stream == NULL || str == NULL || num <= 0 || stream->eof) return NULL;

    int i = 0;
    while (i < num - 1) {
        if (stream->buf_pos >= stream->buf_size) {
            DWORD bytesRead;
            if (!ReadFile(stream->hFile, stream->buffer, BOXFILE_BUFSIZE, &bytesRead, NULL) || bytesRead == 0) {
                stream->eof = 1;
                break;
            }
            stream->buf_size = bytesRead;
            stream->buf_pos = 0;
        }

        char c = stream->buffer[stream->buf_pos++];
        str[i++] = c;

        if (c == '\n')
            break;
    }

    if (i == 0) return NULL;

    str[i] = '\0';
    return str;
}

int box_fclose(BOXFILE* stream)
{
    if (stream == NULL) return EOF;
    BOOL closed = CloseHandle(stream->hFile);
    WinFree(stream);
    return closed ? 0 : EOF;
}
