/*
 *  JoeyDev
 *  Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
 *
 *  This software is provided 'as-is', without any express or implied
 *  warranty.  In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software
 *     in a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *  2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *  3. This notice may not be removed or altered from any source distribution.
*/


#include <sys/stat.h>

#include "utils.h"
#include "cwalk.h"


#ifdef _WIN32
#define ourMkdir(p,m) mkdir(p)
#else
#define ourMkdir mkdir
#endif


typedef struct WindowListS {
	GtkWidget   *key;
	WindowDataT *value;
} WindowListT;


static WindowListT *_windowList = NULL;

char __utilFilenameBuffer[FILENAME_MAX];


char *utilCreateString(char *format, ...) {
	va_list  args;
	char    *string;

	va_start(args, format);
	string = utilCreateStringVArgs(format, args);
	va_end(args);

	return string;
}


__attribute__((__format__(__printf__, 1, 0)))
char *utilCreateStringVArgs(char *format, va_list args) {
	va_list  argsCopy;
	int32_t  size     = 0;
	char    *buffer   = NULL;

	va_copy(argsCopy, args);
	size = vsnprintf(NULL, 0, format, argsCopy) + 1;
	va_end(argsCopy);
	buffer = calloc(1, (size_t)size);
	if (buffer) {
		vsnprintf(buffer, (size_t)size, format, args);
	}

	return buffer;
}


char *utilDeobfuscateASCII(char *obfuscated) {
	char *deobfuscated = NULL;
	char *hostname;
	int   i;
	int   j;
	int   c;

	// Valid ASCII 32 - 126 (94 chars)

	hostname = strdup(g_get_host_name());

	// Decrypt.
	j = 0;
	deobfuscated = (char *)malloc((strlen(obfuscated) + 1) * sizeof(char));
	for (i=0; i<strlen(obfuscated); i++) {
		c = obfuscated[i] - hostname[j] - 32;
		while (c < 0) c += 94;
		deobfuscated[i] = c + 32;
		j++;
		if (j >= strlen(hostname)) j = 0;
	}
	deobfuscated[i] = 0;

	DEL(hostname);

	return deobfuscated;
}


void utilDequote(char *string) {
	int x;

	if (string[0] == '"' || string[strlen(string) - 1] == '"') {
		string[strlen(string) - 1] = 0;
		for (x=1; x<strlen(string) + 1; x++) {
			string[x - 1] = string[x];
		}
	}
}


void utilEnsureBufferSize(unsigned char **buffer, int *length, int wanted) {
	unsigned char *temp = NULL;

	if (*length < wanted) {
		while (*length < wanted) *length = *length + 1024;
		temp = realloc(*buffer, *length);
		if (temp == NULL) {
			//***TODO*** Something bad happened.
		} else {
			*buffer = temp;
		}
	}
}


gboolean utilFileExists(char *filename) {
	FILE *f = fopen(filename, "rb");

	if (f) {
		fclose(f);
		return TRUE;
	}

	return FALSE;
}


gboolean utilFileOpen(WindowDataT *self, char *extension, char *what) {
	GtkWidget     *dialog;
	GtkFileFilter *filter;
	char          *title   = utilCreateString("Open %s", what);
	char          *warning = utilCreateString("You have unsaved changes.  Open different %s?", what);
	char          *files   = utilCreateString("%s Files", what);
	gboolean       result  = TRUE;

	if (self->isDirty) {
		if (!utilQuestionDialog(self->window, title, warning)) {
			result = FALSE;
		}
	}

	if (result == TRUE) {
		dialog = gtk_file_chooser_dialog_new(title,
														 GTK_WINDOW(self->window),
														 GTK_FILE_CHOOSER_ACTION_OPEN,
														 "_Cancel", GTK_RESPONSE_CANCEL,
														 "_Open", GTK_RESPONSE_ACCEPT,
														 NULL
		);

		filter = gtk_file_filter_new();
		gtk_file_filter_set_name(filter, files);
		gtk_file_filter_add_pattern(filter, extension);
		gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);

		if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
			utilSetDirty((WindowDataT *)self, FALSE);
			DEL(self->filename);
			self->filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
		} else {
			result = FALSE;
		}

		gtk_widget_destroy(dialog);
	}

	DEL(files);
	DEL(warning);
	DEL(title);

	return result;
}


gboolean utilFileSaveAs(WindowDataT *self, char *extension, char *what) {
	GtkWidget     *dialog;
	GtkFileFilter *filter;
	gboolean       result = FALSE;
	char          *files  = utilCreateString("%s Files", what);
	int            x;

	dialog = gtk_file_chooser_dialog_new("Save As",
													 GTK_WINDOW(self->window),
													 GTK_FILE_CHOOSER_ACTION_SAVE,
													 "_Cancel", GTK_RESPONSE_CANCEL,
													 "_Save", GTK_RESPONSE_ACCEPT,
													 NULL);

	if (self->filename != NULL) {
		memcpy(__utilFilenameBuffer, self->filename, strlen(self->filename) + 1);
		cwk_path_get_dirname(__utilFilenameBuffer, &x);
		if (x > 0) __utilFilenameBuffer[x] = 0;
		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),__utilFilenameBuffer);
	}

	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);

	filter = gtk_file_filter_new();
	gtk_file_filter_set_name(filter, files);
	gtk_file_filter_add_pattern(filter, extension);
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);

	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
		DEL(self->filename);
		self->filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
		utilSetDirty(self, TRUE);
		result = TRUE;
	}

	DEL(files);

	gtk_widget_destroy(dialog);

	return result;
}


// https://stackoverflow.com/questions/26187037/in-c-split-char-on-spaces-with-strtok-function-except-if-between-quotes
char *utilGetToken(char *input, char *delimit, char *openblock, char *closeblock) {
	static char *token       = NULL;
	char        *lead        = NULL;
	char        *block       = NULL;
	int          iBlock      = 0;
	int          iBlockIndex = 0;

	if (input != NULL) {
		token = input;
		lead  = input;
	} else {
		lead = token;
		if (*token == 0) lead = NULL;
	}

	while (*token != 0) {

		if (iBlock) {
			if (closeblock[iBlockIndex] == *token) {
				iBlock = 0;
			}
			token++;
			continue;
		}

		if ((block = strchr(openblock, *token)) != NULL) {
			iBlock      = 1;
			iBlockIndex = block - openblock;
			token++;
			continue;
		}

		if (strchr(delimit, *token) != NULL) {
			*token = 0;
			token++;
			break;
		}

		token++;
	}

	return lead;
}


WindowDataT *utilGetWindowData(GtkWidget *window) {
	return hmget(_windowList, window);
}


gboolean utilGetWidgetsFromMemory(char *resource, char *name[], GtkWidget **widgets[], gpointer userData) {
	GtkBuilder *gtkBuilder;
	int         x;

	gtkBuilder = gtk_builder_new();
	if (gtk_builder_add_from_resource(gtkBuilder, resource, NULL) == 0) {
		printf("Failed to build UI '%s'!\n", name[0]);
		exit(1);
	}

	x = 0;
	while (name[x] != NULL) {
		*widgets[x] = GTK_WIDGET(gtk_builder_get_object(gtkBuilder, name[x]));
		x++;
	}

	gtk_builder_connect_signals(gtkBuilder, userData);
	g_object_unref(G_OBJECT(gtkBuilder));

	return TRUE;
}


gboolean utilMkDirP(const char *dir, const mode_t mode) {
	char        *p   = NULL;
	struct stat  sb;
	size_t       len;

	// Make copy of dir.
	len = strnlen(dir, FILENAME_MAX);
	if (len == 0 || len == FILENAME_MAX) {
		return -1;
	}
	memcpy(__utilFilenameBuffer, dir, len);
	__utilFilenameBuffer[len] = '\0';

	// Remove trailing slash.
	if (__utilFilenameBuffer[len - 1] == UTIL_PATH_CHAR) {
		__utilFilenameBuffer[len - 1] = '\0';
	}

	// Does it already exist?
	if (stat(__utilFilenameBuffer, &sb) == 0) {
		if (S_ISDIR(sb.st_mode)) {
			return TRUE;
		}
	}

	// Recursive mkdir.
	for (p = __utilFilenameBuffer + 1; *p; p++) {
		if (*p == UTIL_PATH_CHAR) {
			*p = 0;
			if (stat(__utilFilenameBuffer, &sb) != 0) {
				// Does not exist - create it.
				if (ourMkdir(__utilFilenameBuffer, mode) < 0) {
					return FALSE;
				}
			} else {
				if (!S_ISDIR(sb.st_mode)) {
					// Not a directory
					return FALSE;
				}
			}
			*p = UTIL_PATH_CHAR;
		}
	}
	// Check path
	if (stat(__utilFilenameBuffer, &sb) != 0) {
		// Does not exist - create it.
		if (ourMkdir(__utilFilenameBuffer, mode) < 0) {
			return FALSE;
		}
	} else {
		if (!S_ISDIR(sb.st_mode)) {
			// Not a directory
			return FALSE;
		}
	}

	return TRUE;
}


char *utilObfuscateASCII(char *clearText) {
	char *obfuscated = NULL;
	char *hostname;
	int   i;
	int   j;
	int   c;

	// Valid ASCII 32 - 126 (94 chars)

	hostname = strdup(g_get_host_name());

	// Obfuscate.
	j = 0;
	obfuscated = (char *)malloc((strlen(clearText) + 1) * sizeof(char));
	for (i=0; i<strlen(clearText); i++) {
		c = clearText[i] + hostname[j] - 32;
		while (c > 94) c -= 94;
		obfuscated[i] = c + 32;
		j++;
		if (j >= strlen(hostname)) j = 0;
	}
	obfuscated[i] = 0;

	DEL(hostname);

	return obfuscated;
}


gboolean utilQuestionDialog(GtkWidget *parent, char *title, char *question) {
	GtkWidget *dialog;
	int        response;

	dialog = gtk_message_dialog_new(
			GTK_WINDOW(parent),
			GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
			GTK_MESSAGE_QUESTION,
			GTK_BUTTONS_OK_CANCEL,
			"%s", question);
	gtk_window_set_title(GTK_WINDOW(dialog), title);
	response = gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);

	return (response == GTK_RESPONSE_OK) ? TRUE : FALSE;
}


void utilSetDirty(WindowDataT *self, gboolean dirty) {
	char *title;

	self->isDirty = dirty;
	if (dirty) {
		if (self->filename) {
			title = utilCreateString("%s - * %s", self->title, self->filename);
		} else {
			title = utilCreateString("%s - * (no name)", self->title);
		}
	} else {
		if (self->filename) {
			title = utilCreateString("%s - %s", self->title, self->filename);
		} else {
			title = utilCreateString("%s", self->title);
		}
	}
	gtk_window_set_title(GTK_WINDOW(self->window), title);
	DEL(title);
}


void utilWindowRegister(gpointer windowData) {
	WindowDataT *w = (WindowDataT *)windowData;

	// Grab title.
	w->title = strdup(gtk_window_get_title(GTK_WINDOW(w->window)));

	hmput(_windowList, w->window, windowData);
	debug("Window Registered:  %d\n", hmlen(_windowList));
}


int utilWindowsCloseAll(void) {
	WindowDataT *w;

	debug("Windows Left Open:  %ld\n", hmlen(_windowList));
	while (hmlen(_windowList) > 0) {
		w = (WindowDataT *)_windowList[0].value;
		if (w->closeWindow(w->window, w) == TRUE) {
			// User canceled closing.
			break;
		}
		// Mark it clean and close it ourselves.
		w->isDirty = FALSE;
		gtk_window_close(GTK_WINDOW(w->window));
		// Unregister it.
		utilWindowUnRegister(w);
	}
	return utilWindowsOpen();
}


int utilWindowsOpen(void) {
	return hmlen(_windowList);
}


gboolean utilWindowUnRegister(gpointer windowData) {
	int          result = 0;
	WindowDataT *w      = (WindowDataT *)windowData;

	result = hmdel(_windowList, w->window);
	debug("Window Unregistered:  %ld\n", hmlen(_windowList));

	DEL(w->filename);
	DEL(w->title);

	return (result == 1) ? TRUE : FALSE;
}
