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


#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
#pragma ide diagnostic ignored "EndlessLoop"


#include "common.h"
#include "project.h"
#include "utils.h"
#include "http.h"
#include "ssh.h"


enum ProjectColumnsE {
	COL_FILENAME = 0,
	COL_COUNT
};
typedef enum ProjectColumnsE ProjectColumnsT;

enum ProjectSectionTypeE {
	SECTION_HEADER = 0,
	SECTION_CODE,
	SECTION_BITMAP,
	SECTION_STENCIL,
	SECTION_VECTOR,
	SECTION_SOUND,
	SECTION_MUSIC,
	SECTION_RAW_DATA,
	SECTION_COOKED_DATA,
	SECTION_COUNT
};
typedef enum ProjectSectionTypeE ProjectSectionTypeT;


typedef struct ProjectDataS {
	WindowDataT      windowData;
	GtkWidget       *treeProject;
	char            *configName;
	char            *buildHost;
	int              buildHTTPPort;
	int              buildSSHPort;
	char            *buildUser;
	char            *buildPassword;
} ProjectDataT;

typedef struct SectionDataS {
	char *name;
	char *extension;
} SectionDataT;


static SectionDataT _sectionData[] = {
		{ "Header",   "*.h"   },
		{ "Code",     "*.c"   },
		{ "Bitmap",   "*.img" },
		{ "Stencil",  "*.stn" },
		{ "Vector",   "*.vic" },
		{ "Sound",    "*.snd" },
		{ "Music",    "*.mod" },
		{ "Raw Data", "*.raw" },
		{ "Cooked Data", NULL },
		{ NULL,          NULL }
};


static gboolean _httpTestComplete = FALSE;
static gboolean _httpTestSuccess  = FALSE;


#define BUILD_SETTINGS_RESPONSE_TEST  1


static void         addToTree(ProjectDataT *self, char *filename);
static void         httpTest(HTTPT *download);
static void         loadConfig(ProjectDataT *self);
static void         loadProject(ProjectDataT *self);
EVENT  void         menuProjectFileNew(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectFileOpen(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectFileSave(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectFileSaveAs(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectFileClose(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectProjectAdd(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectProjectRemove(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectProjectProperties(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectBuildSettings(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectBuildBuild(GtkWidget *object, gpointer userData);
EVENT  void         menuProjectHelpProject(GtkWidget *object, gpointer userData);
static void         saveConfig(ProjectDataT *self);
EVENT  gboolean     winProjectClose(GtkWidget *object, gpointer userData);
static void         winProjectDelete(gpointer userData);


static void addToTree(ProjectDataT *self, char *filename) {
	ProjectSectionTypeT  section;
	char                 temp[4];
	GtkTreeIter          iterParent;
	GtkTreeIter          iter;
	GtkTreeModel        *model      = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject));
	int                  fileLen;
	int                  extLen;

	// Is it long enough?
	if (strlen(filename) > 2) {
		// Find proper section.
		for (section = 0; section < SECTION_COUNT; section++) {
			if (_sectionData[section].extension != NULL) {
				// Compare last two bytes of filename with extension - it's enough to differentiate them and allows for ".c" and ".h".
				fileLen = strlen(filename) - 1;
				extLen  = strlen(_sectionData[section].extension) - 1;
				if (filename[fileLen - 1] == _sectionData[section].extension[extLen - 1] && filename[fileLen] == _sectionData[section].extension[extLen]) {
					snprintf(temp, 4, "%d", section);
					gtk_tree_model_get_iter_from_string(model, &iterParent, temp);
					gtk_tree_store_append(GTK_TREE_STORE(model), &iter, &iterParent);
					gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_FILENAME, filename, -1);
					//***TODO*** Need to store extra RAW data somewhere.
					break;
				}
			}
		}
	}
}


static void httpTest(HTTPT *download) {
	_httpTestComplete = TRUE;
	_httpTestSuccess  = download->success;
}


static void loadConfig(ProjectDataT *self) {
	FILE                *in       = NULL;
	char                *line     = NULL;
	size_t               len      = 0;
	size_t               count    = 0;

	self->configName = utilCreateString("%s%cjoeydev%cbuild.conf", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR);
	if (utilFileExists(self->configName)) {
		in = fopen(self->configName, "rt");
		if (in != NULL) {
			while (getline(&line, &len, in) != -1) {
				if (strlen(line) > 0) line[strlen(line) - 1] = 0;
				switch (count) {
					case 0:  // Version Number
					case 1:  // Separator line
						break;

					case 2: // Host
						DEL(self->buildHost);
						self->buildHost = strdup(line);
						break;

					case 3: // HTTP Port
						self->buildHTTPPort = atoi(line);
						break;

					case 4: // SSH Port
						self->buildSSHPort = atoi(line);
						break;

					case 5: // User
						DEL(self->buildUser);
						self->buildUser = strdup(line);
						break;

					case 6: // Password
						DEL(self->buildPassword);
						self->buildPassword = strdup(line);
						break;

					default:  // Oops
						break;
				}
				count++;
			}
			fclose(in);
			DEL(line);
		} else {
			//***TODO*** Something bad happened.
		}
	} else {
		// No config file.  Set defaults.
		DEL(self->buildHost);
		DEL(self->buildUser);
		DEL(self->buildPassword);

		self->buildHost     = strdup("build.joeylib.com");
		self->buildHTTPPort = 6502;
		self->buildSSHPort  = 816;
		self->buildUser     = strdup(g_get_user_name());
		self->buildPassword = strdup("SuperSecret");

		saveConfig(self);
	}
}


static void loadProject(ProjectDataT *self) {
	FILE   *in    = NULL;
	char   *line  = NULL;
	size_t  len   = 0;
	size_t  count = 0;

	in = fopen(self->windowData.filename, "rt");
	if (in != NULL) {
		while (getline(&line, &len, in) != -1) {
			if (strlen(line) > 0) line[strlen(line) - 1] = 0;
			switch (count) {
				case 0:  // Version Number
				case 1:  // Separator line
					break;

				default:  // Project Data
					addToTree(self, line);
					break;
			}
			count++;
		}
		fclose(in);
		DEL(line);
		gtk_tree_view_expand_all(GTK_TREE_VIEW(self->treeProject));
		utilSetDirty((WindowDataT *)self, FALSE);  // Do again - loading text marks us dirty.
	} else {
		//***TODO*** Something bad happened.
	}
}


EVENT void menuProjectFileNew(GtkWidget *object, gpointer userData) {
	ProjectDataT        *self = (ProjectDataT *)userData;
	GtkTreeIter          iter;
	ProjectSectionTypeT  i;
	GtkTreeStore        *store;

	(void)object;

	gtk_tree_view_set_model(GTK_TREE_VIEW(self->treeProject), NULL);

	store = gtk_tree_store_new(1, G_TYPE_STRING);

	for (i=0; i<SECTION_COUNT; i++) {
		gtk_tree_store_append(store, &iter, NULL);
		gtk_tree_store_set(store, &iter, COL_FILENAME, _sectionData[i].name, -1);
	}

	gtk_tree_view_set_model(GTK_TREE_VIEW(self->treeProject), GTK_TREE_MODEL(store));
	g_object_unref(store);
}


EVENT void menuProjectFileOpen(GtkWidget *object, gpointer userData) {
	ProjectDataT *self = (ProjectDataT *)userData;

	if (utilFileOpen((WindowDataT *)userData, "*.joe", "Project")) {
		menuProjectFileNew(object, userData);
		loadProject(self);
	}
}


EVENT void menuProjectFileSave(GtkWidget *object, gpointer userData) {
	ProjectDataT *self  = (ProjectDataT *)userData;
	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(self->treeProject));
	FILE         *out   = NULL;
	GtkTreeIter   iter;
	GtkTreeIter   child;
	char         *temp;

	// Do we need to save?
	if (self->windowData.isDirty == TRUE) {

		// Do we have a filename?  If not, kick 'em to SaveAs.
		if (self->windowData.filename == NULL) {
			menuProjectFileSaveAs(object, userData);
			return;
		}

		out = fopen(self->windowData.filename, "wt");
		if (out != NULL) {
			// Save!
			fprintf(out, "%s\n", PROJECT_VERSION);
			fprintf(out, "------------------------------------------------------------------------------\n");
			gtk_tree_model_get_iter_first(model, &iter);
			do {
				if (gtk_tree_model_iter_children(model, &child, &iter)) {
					do {
						//***TODO*** For RAW we need to store function name somewhere.
						gtk_tree_model_get(model, &child, COL_FILENAME, &temp, -1);
						fprintf(out, "%s\n", temp);
						DEL(temp);
					} while (gtk_tree_model_iter_next(model, &child));
				}
			} while (gtk_tree_model_iter_next(model, &iter));
			fclose(out);
			// We're clean now.
			utilSetDirty((WindowDataT *)self, FALSE);
		} else {
			//***TODO*** Something bad happened.
		}
	}
}


EVENT void menuProjectFileSaveAs(GtkWidget *object, gpointer userData) {
	(void)object;

	if (utilFileSaveAs((WindowDataT *)userData, "*.joe", "Project")) {
		menuProjectFileSave(object, (ProjectDataT *)userData);
	}
}


EVENT void menuProjectFileClose(GtkWidget *object, gpointer userData) {
	ProjectDataT *self = (ProjectDataT *)userData;

	(void)object;

	gtk_window_close(GTK_WINDOW(self->windowData.window));
}


EVENT void menuProjectProjectAdd(GtkWidget *object, gpointer userData) {
	ProjectDataT        *self    = (ProjectDataT *)userData;
	GtkWidget           *dialog;
	GtkFileFilter       *filter;
	ProjectSectionTypeT  section;
	char                *temp;

	(void)object;

	dialog = gtk_file_chooser_dialog_new("Add File to Project",
													 GTK_WINDOW(self->windowData.window),
													 GTK_FILE_CHOOSER_ACTION_OPEN,
													 "_Cancel", GTK_RESPONSE_CANCEL,
													 "_Open", GTK_RESPONSE_ACCEPT,
													 NULL
	);

	for (section=0; section<SECTION_COUNT; section++) {
		if (_sectionData[section].extension != NULL) {
			filter = gtk_file_filter_new();
			gtk_file_filter_set_name(filter, _sectionData[section].name);
			gtk_file_filter_add_pattern(filter,  _sectionData[section].extension);
			gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
		}
	}

	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
		temp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
		addToTree(self, temp);
		utilSetDirty((WindowDataT *)self, TRUE);
		DEL(temp);
	}

	gtk_widget_destroy(dialog);
}


EVENT void menuProjectProjectRemove(GtkWidget *object, gpointer userData) {
	ProjectDataT     *self      = (ProjectDataT *)userData;
	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self->treeProject));
	gboolean          found     = FALSE;
	GtkWidget        *dialog;
	GtkTreeModel     *model;
	GtkTreeIter       iter;

	// Is anything selected?
	if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
		// Are we on a child item?
		if (gtk_tree_store_iter_depth(GTK_TREE_STORE(model), &iter) > 0) {
			found = TRUE;
		}
	}

	if (found) {
		gtk_tree_store_remove(GTK_TREE_STORE(model), &iter);
		utilSetDirty((WindowDataT *)self, TRUE);
	} else {
		// Provide help.
		dialog = gtk_message_dialog_new(GTK_WINDOW(self->windowData.window),
													GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
													GTK_MESSAGE_INFO,
													GTK_BUTTONS_CLOSE,
													"Please select a file to remove.");
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
	}
}


EVENT void menuProjectProjectProperties(GtkWidget *object, gpointer userData) {

}


EVENT void menuProjectBuildSettings(GtkWidget *object, gpointer userData) {
	ProjectDataT *self = (ProjectDataT *)userData;
	GtkWidget    *dialog;
	GtkWidget    *dialogServerSettings;
	GtkWidget    *txtHost;
	GtkWidget    *txtHTTPPort;
	GtkWidget    *txtSSHPort;
	GtkWidget    *txtUser;
	GtkWidget    *txtPassword;
	GtkWidget    *btnTest;
	GtkWidget    *btnOkay;
	char         *widgetNames[] = {
			"dialogServerSettings",
			"txtHost",
			"txtHTTPPort",
			"txtSSHPort",
			"txtUser",
			"txtPassword",
			"btnTest",
			"btnOkay",
			NULL
	};
	GtkWidget   **widgets[] = {
			&dialogServerSettings,
			&txtHost,
			&txtHTTPPort,
			&txtSSHPort,
			&txtUser,
			&txtPassword,
			&btnTest,
			&btnOkay
	};
	char          temp[6];
	char         *name    = NULL;
	char         *url     = NULL;
	int           result  = 0;
	SSHT         *ssh     = NULL;
	char         *error   = NULL;

	(void)object;

	utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/BuildServer.glade", widgetNames, widgets, self);

	while (1) {
		gtk_entry_set_text(GTK_ENTRY(txtHost), self->buildHost);
		snprintf(temp, 6, "%d", self->buildHTTPPort);
		gtk_entry_set_text(GTK_ENTRY(txtHTTPPort), temp);
		snprintf(temp, 6, "%d", self->buildSSHPort);
		gtk_entry_set_text(GTK_ENTRY(txtSSHPort), temp);
		gtk_entry_set_text(GTK_ENTRY(txtUser), self->buildUser);
		gtk_entry_set_text(GTK_ENTRY(txtPassword), self->buildPassword);
		result = gtk_dialog_run(GTK_DIALOG(dialogServerSettings));
		if (result == BUILD_SETTINGS_RESPONSE_TEST || result == GTK_RESPONSE_OK) {
			// Save settings to disk.
			DEL(self->buildHost);
			DEL(self->buildUser);
			DEL(self->buildPassword);
			self->buildHost     = strdup(gtk_entry_get_text(GTK_ENTRY(txtHost)));
			self->buildHTTPPort = atoi(gtk_entry_get_text(GTK_ENTRY(txtHTTPPort)));
			self->buildSSHPort  = atoi(gtk_entry_get_text(GTK_ENTRY(txtSSHPort)));
			self->buildUser     = strdup(gtk_entry_get_text(GTK_ENTRY(txtUser)));
			self->buildPassword = strdup(gtk_entry_get_text(GTK_ENTRY(txtPassword)));
			saveConfig(self);

			if (result == BUILD_SETTINGS_RESPONSE_TEST) {
				gtk_widget_set_sensitive(btnTest, FALSE);
				gtk_widget_set_sensitive(btnOkay, FALSE);
				// Run server connection tests - HTTP.
				name = utilCreateString("%s%cjoeydev%cjoeydev.info", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR);
				url  = utilCreateString("http://%s:%d/joeydev.info", self->buildHost, self->buildHTTPPort);
				_httpTestComplete = FALSE;
				httpDownload(httpTest, name, url);
				// This loop seriously breaks CLion's syntax highlighting.
				while (_httpTestComplete == FALSE) {
					gtk_main_iteration();
				}
				if (_httpTestSuccess == FALSE) {
					error = utilCreateString("Unable to connect to HTTP port.");
				}
				DEL(url);
				DEL(name);
				// Run server connection tests - SSH.
				ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword);
				if (ssh) {
					sshDisconnect(&ssh);
				} else {
					DEL(error);
					error = utilCreateString("Unable to connect to SSH port.");
				}
				// Did the tests pass?
				if (error == NULL) {
					dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings),
															  GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
															  GTK_MESSAGE_INFO,
															  GTK_BUTTONS_CLOSE,
															  "Successful connection to build server!");
				} else {
					dialog = gtk_message_dialog_new(GTK_WINDOW(dialogServerSettings),
															  GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
															  GTK_MESSAGE_ERROR,
															  GTK_BUTTONS_CLOSE,
															  "%s", error);
				}
				gtk_dialog_run(GTK_DIALOG(dialog));
				gtk_widget_destroy(dialog);
				DEL(error);
				gtk_widget_set_sensitive(btnTest, TRUE);
				gtk_widget_set_sensitive(btnOkay, TRUE);
			} else {
				// Close dialog on OKAY but not TEST.
				break;
			}

		}
	}

	gtk_widget_destroy(dialogServerSettings);
}


EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData) {

}


EVENT void menuProjectHelpProject(GtkWidget *object, gpointer userData) {
	(void)object;
	(void)userData;

	gtk_show_uri_on_window(NULL, "https://skunkworks.kangaroopunch.com/skunkworks/joeydev/-/wikis/Project-Editor", GDK_CURRENT_TIME, NULL);
}


static void saveConfig(ProjectDataT *self) {
	char *temp;
	FILE *out;

	// Make sure we have a config folder.
	temp = utilCreateString("%s%cjoeydev", g_get_user_config_dir(), UTIL_PATH_CHAR);
	utilMkDirP(temp, 0777);
	DEL(temp);

	// Save config.
	out = fopen(self->configName, "wt");
	if (out != NULL) {
		fprintf(out, "%s\n", BUILD_SETTINGS_VERSION);
		fprintf(out, "------------------------------------------------------------------------------\n");
		fprintf(out, "%s\n", self->buildHost);
		fprintf(out, "%d\n", self->buildHTTPPort);
		fprintf(out, "%d\n", self->buildSSHPort);
		fprintf(out, "%s\n", self->buildUser);
		fprintf(out, "%s\n", self->buildPassword);  //***TODO*** This is hardly secure.
		fclose(out);
	} else {
		//***TODO*** Something bad happened.
	}
}


EVENT gboolean winProjectClose(GtkWidget *object, gpointer userData) {
	// userData is not reliable due to menuVectorFileClose and util indirectly calling us.
	ProjectDataT *self = (ProjectDataT *)utilGetWindowData(object);

	(void)userData;

	if (self->windowData.isDirty == TRUE) {
		if (utilQuestionDialog(self->windowData.window, "Exit", "You have unsaved changes.  Exit?")) {
			winProjectDelete(self);
			return FALSE;
		}
		return TRUE;
	}

	winProjectDelete(self);
	return FALSE;
}


void winProjectCreate(void) {
	ProjectDataT  *self;
	char          *widgetNames[]  = {
			"winProject",
			"treeProject",
			NULL
	};
	GtkWidget    **widgets[]      = {
			NULL,
			NULL
	};
	GtkTreeViewColumn   *col;
	GtkCellRenderer     *renderer;

	// Set up instance data.
	self = NEW(ProjectDataT);
	self->windowData.closeWindow = winProjectClose;

	// Load widgets from XML.
	widgets[0] = &self->windowData.window;
	widgets[1] = &self->treeProject;
	utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Project.glade", widgetNames, widgets, self);

	// Build tree.
	col = gtk_tree_view_column_new();
	gtk_tree_view_append_column(GTK_TREE_VIEW(self->treeProject), col);
	renderer = gtk_cell_renderer_text_new();
	gtk_tree_view_column_pack_start(col, renderer, TRUE);
	gtk_tree_view_column_add_attribute(col, renderer, "text", COL_FILENAME);

	// Reset tree.
	menuProjectFileNew(self->windowData.window, self);

	// Load server settings.
	loadConfig(self);

	// Register window & show it.
	utilWindowRegister(self);
	gtk_widget_show_all(self->windowData.window);

	//***DEBUG***
	self->windowData.filename = strdup("/home/scott/code/joeydev/cmake-build-debug/test.joe");
	loadProject(self);
}


static void winProjectDelete(gpointer userData) {
	ProjectDataT *self = (ProjectDataT *)userData;

	utilWindowUnRegister(userData);

	DEL(self->buildHost);
	DEL(self->buildUser);
	DEL(self->buildPassword);
	DEL(self->configName);

	DEL(self);
}


#pragma clang diagnostic pop
