#include "core/jfile.hpp"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#ifndef _WIN32
#include <pwd.h>
#endif

#define DEFAULT_FILENAME "data.jf2"
#define DEFAULT_FOLDER "data/"
#define CONFIG_FILENAME "config.txt"

struct file_t {
    std::string path;
    uint32_t size;
    uint32_t offset;
};

std::vector<file_t> toc;

/* El std::map me fa coses rares, vaig a usar un good old std::vector amb una estructura key,value propia i au, que sempre funciona */
struct keyvalue_t {
    std::string key, value;
};

char *resource_filename = NULL;
char *resource_folder = NULL;
int file_source = SOURCE_FILE;
char scratch[255];
static std::string config_folder;
std::vector<keyvalue_t> config;

void file_setresourcefilename(const char *str) {
    if (resource_filename != NULL) free(resource_filename);
    resource_filename = (char *)malloc(strlen(str) + 1);
    strcpy(resource_filename, str);
}

void file_setresourcefolder(const char *str) {
    if (resource_folder != NULL) free(resource_folder);
    resource_folder = (char *)malloc(strlen(str) + 1);
    strcpy(resource_folder, str);
}

void file_setsource(const int src) {
    file_source = src % 2;  // mod 2 so it always is a valid value, 0 (file) or 1 (folder)
    if (src == SOURCE_FOLDER && resource_folder == NULL) file_setresourcefolder(DEFAULT_FOLDER);
}

bool file_getdictionary() {
    if (resource_filename == NULL) file_setresourcefilename(DEFAULT_FILENAME);

    std::ifstream fi(resource_filename, std::ios::binary);
    if (!fi.is_open()) return false;
    char header[4];
    fi.read(header, 4);
    uint32_t num_files, toc_offset;
    fi.read((char *)&num_files, 4);
    fi.read((char *)&toc_offset, 4);
    fi.seekg(toc_offset);

    for (uint32_t i = 0; i < num_files; ++i) {
        uint32_t file_offset, file_size;
        fi.read((char *)&file_offset, 4);
        fi.read((char *)&file_size, 4);
        uint8_t path_size;
        fi.read((char *)&path_size, 1);
        char file_name[path_size + 1];
        fi.read(file_name, path_size);
        file_name[path_size] = 0;
        std::string filename = file_name;
        toc.push_back({filename, file_size, file_offset});
    }
    fi.close();
    return true;
}

char *file_getfilenamewithfolder(const char *filename) {
    strcpy(scratch, resource_folder);
    strcat(scratch, filename);
    return scratch;
}

FILE *file_getfilepointer(const char *resourcename, int &filesize, const bool binary) {
    if (file_source == SOURCE_FILE and toc.size() == 0) {
        if (not file_getdictionary()) file_setsource(SOURCE_FOLDER);
    }

    FILE *f;

    if (file_source == SOURCE_FILE) {
        bool found = false;
        uint32_t count = 0;
        while (!found && count < toc.size()) {
            found = (std::string(resourcename) == toc[count].path);
            if (!found) count++;
        }

        if (!found) {
            perror("El recurs no s'ha trobat en l'arxiu de recursos");
            exit(1);
        }

        filesize = toc[count].size;

        f = fopen(resource_filename, binary ? "rb" : "r");
        if (not f) {
            perror("No s'ha pogut obrir l'arxiu de recursos");
            exit(1);
        }
        fseek(f, toc[count].offset, SEEK_SET);
    } else {
        f = fopen(file_getfilenamewithfolder(resourcename), binary ? "rb" : "r");
        fseek(f, 0, SEEK_END);
        filesize = ftell(f);
        fseek(f, 0, SEEK_SET);
    }
    return f;
}

char *file_getfilebuffer(const char *resourcename, int &filesize, const bool zero_terminate) {
    FILE *f = file_getfilepointer(resourcename, filesize, true);
    char *buffer = (char *)malloc(zero_terminate ? filesize : filesize + 1);
    fread(buffer, filesize, 1, f);
    if (zero_terminate) buffer[filesize] = 0;
    fclose(f);
    return buffer;
}

// Crea la carpeta del sistema donde guardar datos.
// Acepta rutas con subdirectorios (ej: "jailgames/aee") y crea toda la jerarquía.
void file_setconfigfolder(const char *foldername) {
#ifdef _WIN32
    config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
#elif __APPLE__
    struct passwd *pw = getpwuid(getuid());
    const char *homedir = pw->pw_dir;
    config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
#elif __linux__
    struct passwd *pw = getpwuid(getuid());
    const char *homedir = pw->pw_dir;
    config_folder = std::string(homedir) + "/.config/" + foldername;
#endif

    std::filesystem::create_directories(config_folder);
}

const char *file_getconfigfolder() {
    static std::string folder;
    folder = config_folder + "/";
    return folder.c_str();
}

void file_loadconfigvalues() {
    config.clear();
    std::string config_file = config_folder + "/config.txt";
    FILE *f = fopen(config_file.c_str(), "r");
    if (!f) return;

    char line[1024];
    while (fgets(line, sizeof(line), f)) {
        char *value = strchr(line, '=');
        if (value) {
            *value = '\0';
            value++;
            value[strlen(value) - 1] = '\0';
            config.push_back({line, value});
        }
    }
    fclose(f);
}

void file_saveconfigvalues() {
    std::string config_file = config_folder + "/config.txt";
    FILE *f = fopen(config_file.c_str(), "w");
    if (f) {
        for (auto pair : config) {
            fprintf(f, "%s=%s\n", pair.key.c_str(), pair.value.c_str());
        }
        fclose(f);
    }
}

const char *file_getconfigvalue(const char *key) {
    if (config.empty()) file_loadconfigvalues();
    for (auto pair : config) {
        if (pair.key == std::string(key)) {
            strcpy(scratch, pair.value.c_str());
            return scratch;
        }
    }
    return NULL;
}

void file_setconfigvalue(const char *key, const char *value) {
    if (config.empty()) file_loadconfigvalues();
    for (auto &pair : config) {
        if (pair.key == std::string(key)) {
            pair.value = value;
            file_saveconfigvalues();
            return;
        }
    }
    config.push_back({key, value});
    file_saveconfigvalues();
    return;
}
