/* Bluefish HTML Editor
 * snippets_load.c - plugin for snippets sidebar
 *
 * Copyright (C) 2006-2026 Olivier Sessink
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
the XML format is not yet fully designed
I'm starting the programming to get a feel for libXML

we'll have two main types of items in the xml document: branches and leaves
the branches are just placeholders, for leaves and for other branches
leaves are of some type:
- python script
- search and replace action
- custom insert
- custom dialog

<?xml version="1.0"?>
<snippets>
<branch title="html">
<branch title="form">
<leaf title="html form" type="insert" tooltip="html form" accelerator="&lt;Control&gt;F1">
<param name="action"/>
<param name="method"/>
<before>&lt;form method="%1" action="%0"&gt;</before>
<after>&lt;/form&gt;</after>
</leaf>
<leaf title="input type text" type="insert" tooltip="input type is text">
<param name="name"/>
<param name="value"/>
<before>&lt;input type="text" name="%0" value="%1" /&gt;</before>
</leaf>
<leaf title="input type submit" type="insert" tooltip="input type is submit" accelerator="&lt;Control&gt;F1">
<param name="name"/>
<param name="value"/>
<before>&lt;input type="submit" name="%0" value="%1" /&gt;</before>
</leaf>
</branch>
</branch>
</snippets>

*/
/* #define DEBUG */

#include <string.h>

#include "snippets.h"
#include "snippets_load.h"
#include "snippets_gui.h"
#include "../config.h"
#include "../bf_lib.h"
#include "../paths_state_config.h"

static void walk_tree(xmlNodePtr cur, GtkTreeIter *parent);

void set_accelerator_for_accelpath(const gchar *accelerator, const gchar *accelpath) {
	guint key;
	GdkModifierType mod;
	gtk_accelerator_parse((const gchar *) accelerator, &key, &mod);
	if (key!=0 || mod !=0) {
		DEBUG_MSG("gtk_accel_map_add_entry %s for accelpath %s\n",accelerator, accelpath);
		gtk_accel_map_add_entry(accelpath,key,mod);
	}
}

void snippets_fill_tree_item_from_node(GtkTreeIter *iter, xmlNodePtr node) {
	gchar *title, *accelpath, *accelerator;
	gboolean recursive=FALSE;
	GdkPixbuf* pixmap=NULL;
	DEBUG_MSG("snippets_fill_tree_item_from_node, got node with type %s\n",node->name);
	title = (gchar *)xmlGetProp(node, (const xmlChar *)"title");
	accelerator = (gchar *)xmlGetProp(node, (const xmlChar *)"accelerator");
	snippets_v.accel_path_uniq_id++;
	accelpath = g_strdup_printf("<snippets>/%d", snippets_v.accel_path_uniq_id);
	DEBUG_MSG("snippets_fill_tree_item_from_node, node has title %s\n",title);
	if ((xmlStrEqual(node->name, (const xmlChar *)"branch"))) {
		pixmap = NULL;
		recursive = TRUE;
	} else /*if ((xmlStrEqual(cur->name, (const xmlChar *)"leaf")))*/ {
		xmlChar *type;
		type = xmlGetProp(node, (const xmlChar *)"type");
		if (xmlStrEqual(type, (const xmlChar *)"insert")) {
			pixmap = snippets_v.pixmap_insert;
		} else if (xmlStrEqual(type, (const xmlChar *)"snr")) {
			pixmap = snippets_v.pixmap_snr;
		}
		xmlFree(type);
	}
	gtk_tree_store_set(snippets_v.store, iter, PIXMAP_COLUMN, pixmap, TITLE_COLUMN, title,NODE_COLUMN, node,ACCELPATH_COLUMN, accelpath, -1);
	if (accelerator) {
		set_accelerator_for_accelpath(accelerator, accelpath);
		xmlFree(accelerator);
	}
	if (pixmap) {
		g_object_unref(pixmap);
	}
	xmlFree(title);	
	if (recursive) {
		walk_tree(node, iter);
	}
	
}

static void walk_tree(xmlNodePtr cur, GtkTreeIter *parent) {
	DEBUG_MSG("walk_tree, started for children of %s\n",cur->name);
	cur = cur->xmlChildrenNode;
	while (cur != NULL) {
		GtkTreeIter iter;
		DEBUG_MSG("walk_tree, found child with type %s\n",cur->name);
		if (xmlStrEqual(cur->name, (const xmlChar *)"branch") || xmlStrEqual(cur->name, (const xmlChar *)"leaf")) {
			/* do not add type text (all the xml spacing is type text )! */ 
			gtk_tree_store_append(snippets_v.store, &iter, parent);
			snippets_fill_tree_item_from_node(&iter, cur);
		}
		cur = cur->next;
	}
}

static xmlNodePtr get_node_from_accel_path(const gchar *findaccelpath) {
	GtkTreeIter iter;
	gboolean next = gtk_tree_model_iter_children(GTK_TREE_MODEL(snippets_v.store),&iter,NULL);
	while (next) {
		gint ret=0;
		gchar *accelpath;
		xmlNodePtr node;
		gtk_tree_model_get(GTK_TREE_MODEL(snippets_v.store),&iter,NODE_COLUMN, &node,ACCELPATH_COLUMN, &accelpath, -1);
		/*g_print("compare with %s\n",accelpath);*/
		ret = g_strcmp0(findaccelpath, accelpath);
		g_free(accelpath);
		if (ret==0) {
			/*g_print("found it, node=%p\n",node);*/
			return node;
		}
		if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(snippets_v.store),&iter)) {
			GtkTreeIter parent=iter;
			next = gtk_tree_model_iter_children(GTK_TREE_MODEL(snippets_v.store),&iter,&parent);
			/*g_print("get the children, next=%d\n",next);*/
		} else {
			GtkTreeIter cur=iter;
			next = gtk_tree_model_iter_next(GTK_TREE_MODEL(snippets_v.store),&iter);
			if (!next) {
				while (!next) {
					/*g_print("no more children, go up\n");*/
					/* no more children on this level, go back to the parent */
					next = gtk_tree_model_iter_parent(GTK_TREE_MODEL(snippets_v.store),&iter,&cur);
					if (!next) {
						/*g_print("no more parents\n");*/
						return NULL;
					}
					cur=iter;
					/* there is a parent, go the next on that level */
					next = gtk_tree_model_iter_next(GTK_TREE_MODEL(snippets_v.store),&iter);
					/*g_print("next on parent level=%d\n", next);*/
				}
			} else {
				/*g_print("next on same level=%d\n", next);*/
			}
		}
	}
	/*g_print("end of function, return NULL;\n");*/
	return NULL;
}

static void accelmap_changed_lcb(GtkAccelMap    *object,
               char           *accel_path,
               guint           accel_key,
               GdkModifierType accel_mods,
               gpointer        user_data) {
	if (strncmp(accel_path, "<snippets>", 10)!=0)
		return;
	DEBUG_MSG("snippets.so:accelmap_changed_lcb() called, connecting_accelerators=%d\n",snippets_v.connecting_accelerators);
	if (snippets_v.connecting_accelerators)
		return;
	gchar *accelerator = gtk_accelerator_name(accel_key, accel_mods);
	DEBUG_MSG("accelmap_changed_lcb, path=%s has new accelerator %s (key=%d, mods=%d)\n",accel_path, accelerator, accel_key, accel_mods);
	xmlNodePtr node = get_node_from_accel_path(accel_path);
	if (!node) {
		g_print("accelmap_changed_lcb, did not find a node for path %s\n",accel_path);
		return;
	}
	xmlSetProp(node, (const xmlChar *) "accelerator", (const xmlChar *) accelerator);
	g_free(accelerator);
	trigger_snippets_store();
}

static gboolean snippets_load_finished_lcb(gpointer data) {
	xmlDocPtr doc = (xmlDocPtr)data;
	xmlNodePtr cur=NULL;
	DEBUG_SIG("snippets_load_finished_lcb, priority=%d\n",G_PRIORITY_LOW);
	DEBUG_MSG("snippets_load_finished_lcb, doc=%p, starting to load xml data into treestore\n", doc);
	if (doc) {
		cur = xmlDocGetRootElement(doc);
		if (!cur) {
			xmlFreeDoc(doc);
			doc = NULL;
		} else if (!xmlStrEqual(cur->name, (const xmlChar *) "snippets")) {
			xmlFreeDoc(doc);
			doc = NULL;
		} else {
			snippets_v.doc = doc;
			walk_tree(cur, NULL);
			/*snippets_rebuild_accelerators();*/
			DEBUG_MSG("snippets_load_finished_lcb, finished walking tree\n");
		}
	}
	if (!doc) {
		snippets_v.doc = xmlNewDoc((const xmlChar *)"1.0");
		cur = xmlNewDocNode(snippets_v.doc,NULL, (const xmlChar *)"snippets",NULL);
		xmlDocSetRootElement(snippets_v.doc, cur);
		DEBUG_MSG("snippets_load_finished_lcb, finished empty tree\n");
	}
	GtkAccelMap *am = gtk_accel_map_get();
	g_signal_connect(am, "changed", G_CALLBACK(accelmap_changed_lcb), NULL);	
	
	/*if (doc) {
		cur = xmlDocGetRootElement(doc);
		if (cur) {
			if (xmlStrEqual(cur->name, (const xmlChar *) "snippets")) {
				snippets_v.doc = doc;
				walk_tree(cur, NULL);
				snippets_rebuild_accelerators();
				DEBUG_MSG("snippets_load_finished_lcb, finished walking tree\n");
				return FALSE;
			}
		}
		xmlFreeDoc(doc);
		doc = NULL;
	}
	if (doc == NULL) {
		snippets_v.doc = xmlNewDoc((const xmlChar *)"1.0");
		cur = xmlNewDocNode(snippets_v.doc,NULL, (const xmlChar *)"snippets",NULL);
		xmlDocSetRootElement(snippets_v.doc, cur);
	}
	DEBUG_MSG("snippets_load_finished_lcb, finished empty tree\n");
	*/
	return FALSE;
}

static gchar *get_snipfile(gboolean forload) {
	gchar *filename;
	DEBUG_MSG("snippets.so:get_snipfile() called\n");
	filename = paths_datadir_char("snippets");
	if (forload) {
		GFile *uri;
		/* if it does not exist, return PKGDATADIR"/snippets" so we start with the default file */
		uri = return_first_existing_filename(filename, PKGDATADIR"/snippets","data/snippets","../data/snippets",NULL);	
		g_free(filename);
		if (!uri) {
			return NULL;
		}
		filename = g_file_get_path(uri);
		g_object_unref(uri);
	}
	return filename;
}

static gpointer snippets_load_async(gpointer data) {
	DEBUG_MSG("snippets.so:snippets_load_async() called\n");
	gchar *filename = get_snipfile(TRUE);
	xmlDocPtr doc;
	if (!filename) {
		snippets_load_finished_lcb(NULL);
		return NULL;
	}
	DEBUG_MSG("snippets_load, filename=%s\n",filename);
	doc = xmlParseFile(filename);
	g_free(filename);
	g_idle_add_full(G_PRIORITY_LOW, snippets_load_finished_lcb, doc, NULL);	
	return NULL;
}

static gboolean snippets_load_low_priority(gpointer data) {
	DEBUG_MSG("snippets.so:snippets_load_low_priority() called\n");
	DEBUG_SIG("snippets_load_low_priority, priority=%d\n",G_PRIORITY_LOW);
	g_thread_create(snippets_load_async, NULL, FALSE, NULL);
/*	g_print("snippets_load_low_priority\n");*/
	return FALSE;
}

void snippets_load(void) {
	/*g_thread_create(snippets_load_async, NULL, FALSE, NULL);*/
	g_idle_add_full(G_PRIORITY_LOW, snippets_load_low_priority, NULL, NULL);
}

static gboolean snippets_store_lcb(gpointer data) {
	DEBUG_MSG("snippets.so:snippets_store_lcb() called\n");
	DEBUG_SIG("snippets_store_lcb, priority=%d\n",G_PRIORITY_DEFAULT_IDLE);
	DEBUG_MSG("snippets_store_lcb, started\n");
	if (snippets_v.doc) {
		gchar *snipfile = get_snipfile(FALSE);
		xmlSaveFormatFile(snipfile, snippets_v.doc, 1);
		g_free(snipfile);
	}
	snippets_v.store_idle_id = 0;
	return FALSE;
}

void trigger_snippets_store() {
	if (!snippets_v.store_idle_id) {
		snippets_v.store_idle_id = g_idle_add_full(G_PRIORITY_LOW, snippets_store_lcb, NULL, NULL);
	}
}

void reload_tree_from_doc(void) {
	if (snippets_v.doc) {
		xmlNodePtr cur = xmlDocGetRootElement(snippets_v.doc);
		if (cur) {
			if (xmlStrEqual(cur->name, (const xmlChar *) "snippets")) {
				/* the document is correct, first empty the treestore */
				gtk_tree_store_clear(snippets_v.store);
				
				/* now reload the tree */
				walk_tree(cur, NULL);
			}
		}
	}
}

void snippets_export_node(xmlNodePtr node, const gchar *filename) {
	xmlNodePtr newnode=NULL, cur=NULL;
	xmlDocPtr newdoc; 
	
	newdoc = xmlNewDoc((const xmlChar *)"1.0");
	cur = xmlNewDocNode(newdoc,NULL, (const xmlChar *)"snippets",NULL);
	xmlDocSetRootElement(newdoc, cur);
	
	newnode = xmlDocCopyNode(node, newdoc, TRUE);
	DEBUG_MSG("snippets_export_node, cur=%p, newdoc=%p, newnode=%p\n",cur,newdoc,newnode);
	xmlAddChild(cur, newnode);

	xmlSaveFormatFile(filename, newdoc, 1);
	xmlFreeDoc(newdoc);
}

typedef struct {
	gchar *filename;
	xmlDocPtr doc;
	xmlNodePtr parent;
} Tsnippets_import;

static gboolean snippets_import_load_finished_lcb(gpointer data) {
	Tsnippets_import *si = (Tsnippets_import *)data;
	DEBUG_SIG("snippets_import_load_finished_lcb, priority=%d\n",G_PRIORITY_LOW);
	if (si->doc) {
		xmlNodePtr cur;
		cur = xmlDocGetRootElement(si->doc);
		if (cur) {
			if (xmlStrEqual(cur->name, (const xmlChar *) "snippets")) {
				xmlNodePtr newnode;
				/* now import everything below */
				newnode = xmlDocCopyNodeList(snippets_v.doc, cur->children);
				xmlAddChildList(si->parent,newnode);
				reload_tree_from_doc();
				trigger_snippets_store();
			}
		}
		xmlFreeDoc(si->doc);
	}
	g_free(si->filename);
	g_free(si);
	return FALSE;
}

static gpointer snippets_import_async(gpointer data) {
	Tsnippets_import *si = (Tsnippets_import *)data;
	
	si->doc = xmlParseFile(si->filename);
	g_idle_add_full(G_PRIORITY_LOW, snippets_import_load_finished_lcb, si, NULL);	
	return NULL;
}

void snippets_import_node(xmlNodePtr branch, const gchar *filename) {
	Tsnippets_import *si;
	
	si = g_new(Tsnippets_import,1);
	si->filename = g_strdup(filename);
	si->parent = branch;
	g_thread_create(snippets_import_async, si, FALSE, NULL);
}
