#include "file.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/stat.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <algorithm>
#include <dirent.h>

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

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

namespace mini
{
	namespace file
	{
		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 setresourcefilename(const char *str) {
			if (resource_filename != NULL) free(resource_filename);
			resource_filename = (char*)malloc(strlen(str)+1);
			strcpy(resource_filename, str);
		}

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

		void 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) setresourcefolder(DEFAULT_FOLDER);
		}

		bool getdictionary() {
			if (resource_filename == NULL) 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 = (char*)malloc(path_size+1);
				fi.read( file_name, path_size );
				file_name[path_size] = 0;
				std::string filename = file_name;
				free(file_name);
				toc.push_back({filename, file_size, file_offset});
			}
			fi.close();
			return true;
		}

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

		FILE *getfilepointer(const char *resourcename, int& filesize, const bool binary) {

			if (file_source==SOURCE_FILE and toc.size()==0) {
				if (not getdictionary()) 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(getfilenamewithfolder(resourcename), binary?"rb":"r");
				fseek(f, 0, SEEK_END);
				filesize = ftell(f);
				fseek(f, 0, SEEK_SET);
			}
			return f;
		}

		char *getfilebuffer(const char *resourcename, int& filesize, const bool zero_terminate) {
			FILE *f = 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;
		}

		FILE *getfilepointerex(const char *filename, int& filesize, const bool binary) {

			FILE *f;
			f = fopen(filename, binary?"rb":"r");
			fseek(f, 0, SEEK_END);
			filesize = ftell(f);
			fseek(f, 0, SEEK_SET);
			return f;
		}

		char *getfilebufferex(const char *filename, int& filesize, const bool zero_terminate) {
			FILE *f = getfilepointerex(filename, 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
		void 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) + "/." + foldername;
			config_folder = std::string(homedir) + "/.config/jailgames/" + foldername;

			{
				// Intenta crear ".config", per si no existeix
				std::string config_base_folder = std::string(homedir) + "/.config";
				int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
				if (ret == -1 && errno != EEXIST)
				{
					printf("ERROR CREATING CONFIG BASE FOLDER.");
					exit(EXIT_FAILURE);
				}
			}
			{
				// Intenta crear ".config/jailgames", per si no existeix
				std::string config_base_folder = std::string(homedir) + "/.config/jailgames";
				int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
				if (ret == -1 && errno != EEXIST)
				{
					printf("ERROR CREATING CONFIG BASE FOLDER.");
					exit(EXIT_FAILURE);
				}
			}

		#endif

			struct stat st = {0};
			if (stat(config_folder.c_str(), &st) == -1)
			{
		#ifdef _WIN32
				int ret = mkdir(config_folder.c_str());
		#else
				int ret = mkdir(config_folder.c_str(), S_IRWXU);
		#endif

				if (ret == -1)
				{
					printf("ERROR CREATING CONFIG FOLDER.");
					exit(EXIT_FAILURE);
				}
			}
		}

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

		void 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 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* getconfigvalue(const char *key) {
			if (config.empty()) loadconfigvalues();
			for (auto pair : config) {
				if (pair.key == std::string(key)) {
					strcpy(scratch, pair.value.c_str());
					return scratch;
				}
			}
			return NULL;
		}

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

		bool createFolder(const char* name) {
			char tmp[256];
			strcpy(tmp, "./");
			strcat(tmp, name);
		#ifdef _WIN32
			return mkdir(tmp)==0;
		#else
			return mkdir(tmp, 0755)==0;
		#endif
		}

		static bool has_extension(const std::string &name, const char *ext)
		{
			if (!ext) return true; // sin filtro

			std::string e = ext;
			std::string suffix = "." + e;

			if (name.size() < suffix.size())
				return false;

			return (name.compare(name.size() - suffix.size(), suffix.size(), suffix) == 0);
		}

		std::vector<std::string> listresourcesdir(const char *folder, const char *extension)
		{
			std::vector<std::string> result;
			std::string base(folder);

			// Normalizar: quitar "/" final si existe
			if (!base.empty() && base.back() == '/')
				base.pop_back();

			// -------------------------------
			// 1. MODO: ARCHIVOS SUELTOS
			// -------------------------------
			if (file_source == SOURCE_FOLDER)
			{
				std::string fullpath = std::string(resource_folder) + base;

				DIR *dir = opendir(fullpath.c_str());
				if (!dir)
					return result;

				struct dirent *entry;
				while ((entry = readdir(dir)) != nullptr)
				{
					std::string name = entry->d_name;

					// Ignorar "." y ".."
					if (name == "." || name == "..")
						continue;

					// Ignorar subdirectorios
					std::string full = fullpath + "/" + name;
					DIR *test = opendir(full.c_str());
					if (test)
					{
						closedir(test);
						continue; // es un directorio
					}

					// Filtrar por extensión
					if (!has_extension(name, extension))
						continue;

					result.push_back(name);
				}

				closedir(dir);
				return result;
			}

			// -------------------------------
			// 2. MODO: ARCHIVO CONTENEDOR
			// -------------------------------
			if (file_source == SOURCE_FILE)
			{
				std::string prefix = base + "/";

				for (auto &f : toc)
				{
					const std::string &path = f.path;

					// Debe empezar por "folder/"
					if (path.compare(0, prefix.size(), prefix) != 0)
						continue;

					// Extraer la parte después de "folder/"
					std::string rest = path.substr(prefix.size());

					// Ignorar subdirectorios
					if (rest.find('/') != std::string::npos)
						continue;

					// Filtrar por extensión
					if (!has_extension(rest, extension))
						continue;

					result.push_back(rest);
				}

				return result;
			}

			return result;
		}
	}
}
