/*
 * src/file.c
 * 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, high level file operations
 */

#include "main.h"
#define __USE_MISC
#include <stdio.h>
#include <dirent.h>
#include <unistd.h>

#ifdef __WIN32__
#include <windows.h>
#include <winnls.h>
#include <fileapi.h>
#include <wchar.h>
#else
#include <unistd.h>
#endif
#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

char fn[PATH_MAX + FILENAME_MAX + 1];
int pathlen;

/**
 * RFC4648 base64 decoder
 */
int base64_decode(char *s, uint8_t *out, int len)
{
    uint8_t *ret = out;
    int b = 0, c = 0, d;
    while(*s && *s != '=' && *s != '\"' && (int)(out - ret) < len) {
        while(*s && (*s == ' ' || *s == '\r' || *s == '\n')) s++;
        if(!*s) break;
        if(*s >= 'A' && *s <= 'Z') d = *s - 'A'; else
        if(*s >= 'a' && *s <= 'z') d = *s - 'a' + 26; else
        if(*s >= '0' && *s <= '9') d = *s - '0' + 52; else
        if(*s == '+' || *s == '-' || *s == '.') d = 62; else
        if(*s == '/' || *s == '_') d = 63; else break;
        b += d; c++; s++;
        if(c == 4) {
            *out++ = (b >> 16);
            *out++ = ((b >> 8) & 0xff);
            *out++ = (b & 0xff);
            b = c = 0;
        } else {
            b <<= 6;
        }
    }
    switch(c) {
        case 1: break;
        case 2: *out++ = (b >> 10); break;
        case 3: *out++ = (b >> 16); *out++ = ((b >> 8) & 0xff); break;
    }
    *out = 0;
    return (int)(out - ret);
}

/**
 * Create directory
 */
void apck_mkdir(char *fn)
{
#ifdef __WIN32__
    wchar_t szFn[PATH_MAX + FILENAME_MAX + 1];
    if(!fn || !*fn) return;
    apck_utf8_ucs2(fn, szFn);
    _wmkdir(szFn);
#else
    if(!fn || !*fn) return;
    mkdir(fn, 0775);
#endif
}

/**
 * Check if file exists, is a file and is readable
 */
int isfile(char *fn)
{
    void *f;
    uint64_t size;
    if(fn && *fn && (f = apck_fileopen(fn, &size))) { apck_fileclose(f); return 1; }
    return 0;
}

/**
 * Read in a file into memory
 */
uint8_t* getfile(char *fn, uint64_t *size)
{
    uint8_t *buf = NULL;
    void *f;

    *size = 0;
    if(!fn || !*fn || !(f = apck_fileopen(fn, size))) return NULL;
    if((buf = (uint8_t*)malloc((*size) + 512))) {
        memset(buf, 0, (*size) + 512);
        *size = apck_fileread(f, buf, *size);
    }
    apck_fileclose(f);
    return buf;
}

/**
 * Write out buffer to a file
 */
int putfile(char *fn, uint8_t *buf, uint64_t size)
{
    void *f;

    if(!fn || !*fn || !buf || size < 1 || !(f = apck_fileopenw(fn))) return 0;
    apck_filewrite(f, buf, size);
    apck_fileclose(f);
    return 1;
}

/**
 * Iterate on directory entries (subdirs == 1 recursively) and call a callback on each file
 */
int _find(int subdirs, filecb_t filecb)
{
    int len;
#ifdef __WIN32__
    static wchar_t szFn[PATH_MAX + FILENAME_MAX + 1];
    WIN32_FIND_DATAW ffd;
    HANDLE h;
#else
    DIR *dir;
    struct dirent *ent;
#endif
    int ret = 1;

    len = strlen(fn);
#ifdef __WIN32__
    strcpy(fn + len, "/*.*");
    apck_utf8_ucs2(fn, (wchar_t*)szFn);
    if((h = FindFirstFileW(szFn, &ffd)) != INVALID_HANDLE_VALUE) {
        do {
            if(!wcscmp(ffd.cFileName, L".") || !wcscmp(ffd.cFileName, L".."))
                continue;
            fn[len] = '/'; fn[len + 1] = 0;
            WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, fn + len + 1, FILENAME_MAX, NULL, NULL);
            if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                if(!subdirs) continue;
                _find(1, filecb);
            } else {
                ret = (*filecb)(fn);
            }
        } while(ret && FindNextFileW(h, &ffd) != 0);
        FindClose(h);
    }
#else
    if((dir = opendir(fn)) != NULL) {
        while(ret && (ent = readdir(dir)) != NULL) {
            if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
                continue;
            fn[len] = '/';
            strncpy(fn + len + 1, ent->d_name, FILENAME_MAX);
            if(ent->d_type == DT_DIR) {
                if(!subdirs) continue;
                _find(1, filecb);
            } else
            if(ent->d_type == DT_REG) {
                ret = (*filecb)(fn);
            }
        }
        closedir(dir);
    }
#endif
    return ret;
}
int find(char *path, int subdirs, filecb_t filecb)
{
    uint64_t size;
    void *f;

    if(!path || !*path || !filecb) return 0;
    if(path != fn) strcpy(fn, path);
    pathlen = strlen(fn);
    if(pathlen && (fn[pathlen - 1] == '/' || fn[pathlen - 1] == '\\')) fn[--pathlen] = 0;
    if((f = apck_fileopen(fn, &size))) { apck_fileclose(f); return (*filecb)(fn); }
    pathlen++;
    return _find(subdirs, filecb);
}
