/*
 * lib/apck_file.h
 * https://gitlab.com/bztsrc/assetpack
 *
 * Copyright (C) 2024 bzt, MIT license
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL ANY
 * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @brief Operating system independent, low level file operations
 */

#ifndef APCK_FILE_H
#define APCK_FILE_H

#ifdef __WIN32__
#include <windows.h>
#include <winnls.h>
#include <fileapi.h>
#include <wchar.h>
void apck_utf8_ucs2(char *fn, wchar_t *szFn);
#else
#include <unistd.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#ifndef FILENAME_MAX
#define FILENAME_MAX 255
#endif

void *apck_fileopen(char *fn, uint64_t *size);
void *apck_fileopenw(char *fn);
uint64_t apck_filewrite(void *f, void *buf, uint64_t size);
uint64_t apck_fileread(void *f, void *buf, uint64_t size);
void apck_fileseek(void *f, uint64_t offs);
void apck_fileclose(void *f);

#ifdef APCK_IMPLEMENTATION

#ifdef __WIN32__
void apck_utf8_ucs2(char *fn, wchar_t *szFn)
{
    int i;
    memset(szFn, 0, 2*(PATH_MAX + FILENAME_MAX + 1));
    MultiByteToWideChar(CP_UTF8, 0, fn, -1, szFn, PATH_MAX + FILENAME_MAX);
    for(i = 0; szFn[i]; i++) if(szFn[i] == L'/') szFn[i] = L'\\';
}
#endif

/**
 * Open a file
 */
void *apck_fileopen(char *fn, uint64_t *size)
{
#ifdef __WIN32__
    struct _stat64 st;
    static wchar_t szFn[PATH_MAX + FILENAME_MAX + 1];
    HANDLE f;

    if(size) *size = 0;
    if(!fn || !*fn) return NULL;
    apck_utf8_ucs2(fn, (wchar_t*)szFn);
    if(_wstat64(szFn, &st) || !S_ISREG(st.st_mode)) return NULL;
    f = CreateFileW(szFn, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if(f == INVALID_HANDLE_VALUE) f = NULL;
#else
    struct stat st;
    FILE *f;

    if(size) *size = 0;
    if(!fn || !*fn || (size && (stat(fn, &st) || !S_ISREG(st.st_mode)))) return NULL;
    f = fopen(fn, "rb");
#endif
    if(size) *size = (uint64_t)st.st_size;
    return (void*)f;
}

/**
 * Open a file for writing
 */
void *apck_fileopenw(char *fn)
{
#ifdef __WIN32__
    wchar_t szFn[PATH_MAX + FILENAME_MAX + 1];
    HANDLE f;

    if(!fn || !*fn) return NULL;
    apck_utf8_ucs2(fn, szFn);
    f = CreateFileW(szFn, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(f == INVALID_HANDLE_VALUE) f = NULL;
#else
    FILE *f;

    if(!fn || !*fn) return NULL;
    f = fopen(fn, "wb");
#endif
    return (void*)f;
}

/**
 * Write to file
 */
uint64_t apck_filewrite(void *f, void *buf, uint64_t size)
{
#ifdef __WIN32__
    DWORD t;
    if(!f || !buf || !size) return 0;
    if(!WriteFile((HANDLE)f, buf, (DWORD)size, &t, NULL)) t = 0;
    return (uint64_t)t;
#else
    if(!f || !buf || !size) return 0;
    return (uint64_t)fwrite(buf, 1, size, (FILE*)f);
#endif
}

/**
 * Read from file
 */
uint64_t apck_fileread(void *f, void *buf, uint64_t size)
{
#ifdef __WIN32__
    DWORD t;
    if(!f || !buf || !size) return 0;
    if(!ReadFile((HANDLE)f, buf, (DWORD)size, &t, NULL)) t = 0;
    return (uint64_t)t;
#else
    if(!f || !buf || !size) return 0;
    return (uint64_t)fread(buf, 1, (size_t)size, (FILE*)f);
#endif
}

/**
 * Seek in file from the beginning
 */
void apck_fileseek(void *f, uint64_t offs)
{
#ifdef __WIN32__
    LARGE_INTEGER pos;
    pos.QuadPart = offs;
    SetFilePointerEx((HANDLE)f, pos, NULL, FILE_BEGIN);
#else
    fseek((FILE*)f, offs, SEEK_SET);
#endif
}

/**
 * Close file
 */
void apck_fileclose(void *f)
{
    if(f) {
#ifdef __WIN32__
        CloseHandle((HANDLE)f);
#else
        fclose((FILE*)f);
#endif
    }
}

#endif /* APCK_IMPLEMENTATION */

#endif /* APIC_FILE_H */
