/*
 *  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"


#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 *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;
}


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

	if (*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);

	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);

	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;
}


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         tmp[UTIL_PATH_MAX];
	char        *p   = NULL;
	struct stat  sb;
	size_t       len;

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

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

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

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

	return TRUE;
}


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);
}


int utilWindowsCloseAll(void) {
	WindowDataT *w;
	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;

	WindowDataT *w = (WindowDataT *)windowData;
	result = hmdel(_windowList, w->window);

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

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