/* Bluefish HTML Editor
 * (C) 1998-2000 Olivier Sessink
 *
 * highlight.c - syntax highlighting code
 * Copyright (C) 2000-2001 Roland Steinbach, Gero Takke and Olivier Sessink
 * 
 * incorporates small pieces of code from David Vogler and Adrian Reber
 *
 * This is the code to do syntax-highlighting in the standard gtk_text widget.
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "default_include.h"

#include <glib.h>
#include <gdk/gdk.h>

#include <stdio.h>
#include <string.h>
#include <string.h>
#include <sys/types.h>  /* _before_ regex.h for freeBSD */
#include <regex.h>


#include "bluefish.h"
#include "document.h"
#include "stringlist.h" /* edit stringlist */
#include "highlight.h"
#include "gtk_easy.h"
#ifdef UNDO2
#include "undo2.h"
#else
#include "undo.h"
#endif /* undo2 */

#include "interface.h"

#ifdef AUTOCOMPLET
#include "parsedtd.h"
#include "complet.h"
#endif

/* Maximal store-size for regular expressions...
 * If anybody figured out how to get multiple matches
 * in one regex, it should be increased ;-)
 */
#define NMAX 1

/***********************************************************************/

typedef gshort Thp_index;

typedef struct {
	GdkColor color;
	regex_t reg_comp_pat;
} Thighlight_pattern;

/* for access to main_v->main_v->syntax_structs */
#define HIPA_ARRAY_PTR(p) ((Thighlight_pattern*)(p))

/***********************************************************************/

static gchar **create_array(gchar *color, gchar *pattern) {
        gchar **array;

	array = g_malloc(3* sizeof(gchar *));
	array[0] = g_strdup(color);
	array[1] = g_strdup(pattern);
	array[2] = NULL;
	return array;
}

void reset_syntax_highlighting_lcb(GtkWidget * widget, gpointer data)
{
	free_arraylist(main_v->props.syntax_configstrings);
	
	main_v->props.syntax_configstrings = NULL;
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#000080", "<[^!%?][^>]*>"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#ee0000", "(<\\?)([^?]|\\?[^>])*(\\?>)"));
/*	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#ee0000", "(<\?)([^?>]|[^?]>|\?[^>])*(\?>)"));*/
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#0000FF", "(class|function|var|extends|if|else|elseif|for|while|do|switch|case|break|return)"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#bd0000", "<(/)?script[^>]*>"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#00AAAA", "<(/)?(table|td|tr|th)[^>]*>"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#4080bf", "<(/)?(form|input|select|textarea|option)[^>]*>"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#eead0e", "<(/)?((a )|(img)|(/a))[^>]*>"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#969696", "(<!--)([^>]|([^-]>))*-->"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#1D9200", "(&)[a-zA-Z]+(;)"));
	main_v->props.syntax_configstrings = g_list_append(main_v->props.syntax_configstrings, create_array("#22AA00", "\"[^\"]*\""));
	make_syntax_struct_list();
}


/* checkreg
 *
 * returns a regmatch_t struct including the
 * first match from regex in htmlstring
 *
 */
static regmatch_t checkreg(char *htmlstring, regex_t * pregp)
{
	regmatch_t pmatch[NMAX];

	if (!(regexec(pregp, htmlstring, NMAX, pmatch, 0) == 0)) {
		pmatch[0].rm_so = -1;
		pmatch[0].rm_eo = -1;
	}
	return pmatch[0];
}

/* hlregex
 * the "core"...
 * it highlights the given regex in the given color fore
 *
 */
#define CRAPPY_FIX

static void hlregex(Thighlight_pattern * hipa, Thp_index hipa_index, gchar * fulltext, Thp_index *fulltext_attr, gint fulltext_len)
{
	regmatch_t tagmatch;
	gshort loop = 1;
	gint pos = 0;
	gint i;

	while (loop == 1) {
		tagmatch = checkreg(&fulltext[pos], &hipa->reg_comp_pat);

		if (tagmatch.rm_so == -1) {
			/* DEBUG_MSG ("EXIT LOOP (REGULAR)\n"); */
			loop = 0;
		} else if (tagmatch.rm_so == tagmatch.rm_eo) {
                     /* DEBUG_MSG ("EXIT LOOP (NULL MATCH)\n");*/
                     loop = 0;
		} else {
#ifdef DEBUG
			g_print
				("position in text is=%d, match start=%d, match end=%d, match length=%d\n",
				 pos, pos + tagmatch.rm_so, pos + tagmatch.rm_eo, tagmatch.rm_eo - tagmatch.rm_so);
#endif
			for (i = tagmatch.rm_so; i < tagmatch.rm_eo; i++) {
				if (pos + i >= fulltext_len) {
					loop = 0;
					break;
				} else {
					fulltext_attr[pos + i] = hipa_index + 1;
				}
			}
			pos = pos + tagmatch.rm_eo;
		}

	}
}

static void insert_highlighted_text(GtkWidget * textbox, Thighlight_pattern *hipalist, gchar * fulltext, Thp_index *fulltext_attr, gint fulltext_len)
{
	gint start_pos = 0, end_pos = 0;
	Thp_index hipa_index = fulltext_attr[0];
	gchar *tmpbuf;
	Thighlight_pattern *hipa;
	GdkColor *hipa_color;

	for (end_pos = 0; end_pos < fulltext_len; end_pos++) {
		if (fulltext_attr[end_pos] != hipa_index) {
			tmpbuf = g_strndup(&fulltext[start_pos], end_pos - start_pos);
			if (hipa_index > 0) {
				hipa = &hipalist[hipa_index-1];
				hipa_color = &hipa->color;
			} else {
				hipa_color = &textbox->style->black;
			}
			gtk_text_insert(GTK_TEXT(textbox), NULL, hipa_color, NULL, tmpbuf, -1);
			g_free(tmpbuf);
			start_pos = end_pos;
			hipa_index = fulltext_attr[end_pos];
		}
	}
	if (start_pos != end_pos) {
		tmpbuf = g_strndup(&fulltext[start_pos], end_pos - start_pos);
		if (hipa_index > 0) {
			hipa = &hipalist[hipa_index-1];
			hipa_color = &hipa->color;
		} else {
			hipa_color = &textbox->style->black;
		}
		gtk_text_insert(GTK_TEXT(textbox), NULL, hipa_color, NULL, tmpbuf, -1);
		g_free(tmpbuf);
	}
}


static void start_highlighting(GtkWidget * textbox)
{
	Thighlight_pattern *hipa;
	gchar *fulltext;
	Thp_index *fulltext_attr;
	gint fulltext_bufflen;
	gint textlen;
	gint i;

	DEBUG_MSG("start_highlighting, started, text_len=%d\n", gtk_text_get_length(GTK_TEXT(textbox)));
	gtk_text_freeze(GTK_TEXT(textbox));
	fulltext = gtk_editable_get_chars(GTK_EDITABLE(textbox), 0, -1);
	if (!fulltext) {
		gtk_text_thaw(GTK_TEXT(textbox));
		return;
	}
	fulltext_bufflen = strlen(fulltext);
	DEBUG_MSG("start_highlighting, fulltext_bufflen=%d\n", fulltext_bufflen);
	fulltext_attr = g_new0(Thp_index, fulltext_bufflen);
	if (!fulltext_attr) {
		g_free(fulltext);
		gtk_text_thaw(GTK_TEXT(textbox));
		return;
	}

	hipa = HIPA_ARRAY_PTR(main_v->syntax_structs);
	for (i = 0; i < main_v->syntax_structs_n; i++) {
		hlregex(hipa, i, fulltext, fulltext_attr, fulltext_bufflen);
		hipa++;
		DEBUG_MSG("start_highlighting, highlighting pattern=%d\n", i);
	}

	textlen = gtk_text_get_length(GTK_TEXT(textbox));
	DEBUG_MSG("start_highlighting, text length=%d\n", textlen);
	gtk_text_set_point(GTK_TEXT(textbox), 0);
	gtk_text_forward_delete(GTK_TEXT(textbox), textlen);

	insert_highlighted_text(textbox, HIPA_ARRAY_PTR(main_v->syntax_structs), fulltext, fulltext_attr, fulltext_bufflen);

	g_free(fulltext);
	g_free(fulltext_attr);
	gtk_text_thaw(GTK_TEXT(textbox));

}

void line_highlighting(GtkWidget * textbox)
{
	Thighlight_pattern *hipa;
	gchar *line, curchar;
	Thp_index *line_attr;
	guint pos,linelen,line_bufflen;
	gint startol, endol, max;
	gint i;

	gtk_text_freeze(GTK_TEXT(textbox));
	pos = gtk_editable_get_position(GTK_EDITABLE(textbox));
	max = gtk_text_get_length(GTK_TEXT(textbox));
	DEBUG_MSG("line_highlighting textbox=%p, max=%d, pos=%d\n", textbox, max, pos);
	startol = pos;
	curchar = GTK_TEXT_INDEX(GTK_TEXT(textbox), startol);
	if (curchar == '\n') {
		startol--;
		curchar = GTK_TEXT_INDEX(GTK_TEXT(textbox), startol);
	}
	while ((curchar != '\n') && (startol != 0)){
		startol--;
		curchar = GTK_TEXT_INDEX(GTK_TEXT(textbox), startol);
	}
	endol = pos;
	curchar = GTK_TEXT_INDEX(GTK_TEXT(textbox), endol);
	while ((curchar != '\n') && (endol != max)) {
		endol++;
		curchar = GTK_TEXT_INDEX(GTK_TEXT(textbox), endol);
	}

	linelen = endol - startol;
	DEBUG_MSG("line_highlighting, startol=%d, endol=%d, linelen=%d\n", startol, endol, linelen);
	
	line = gtk_editable_get_chars(GTK_EDITABLE(textbox), startol, endol);
	if (!line) {
		gtk_text_thaw (GTK_TEXT(textbox));
		return;
	}

	line_bufflen = strlen(line);
	line_attr = g_new0(Thp_index, line_bufflen);
	if (!line_attr) {
		g_free(line);
		gtk_text_thaw(GTK_TEXT(textbox));
		return;
	}

	DEBUG_MSG("line_highlighting, startol=%d, endol=%d, inserting %s\n", startol, endol, line);

	hipa = HIPA_ARRAY_PTR(main_v->syntax_structs);
	for (i = 0; i < main_v->syntax_structs_n; i++) {
		hlregex(hipa, i, line, line_attr, line_bufflen);
		hipa++;
	}

	gtk_text_set_point(GTK_TEXT(textbox), startol);
	gtk_text_forward_delete(GTK_TEXT(textbox), linelen);
	insert_highlighted_text(textbox, HIPA_ARRAY_PTR(main_v->syntax_structs), line, line_attr, line_bufflen);

	g_free(line);
	g_free(line_attr);
	gtk_text_thaw(GTK_TEXT(textbox));
}


static void stop_highlighting(GtkWidget * textbox)
{
	gchar *buffer;
	guint buflen;
	guint textlen;
	guint scrollp;
	DEBUG_MSG("stop_highlighting, started\n");
	gtk_text_freeze(GTK_TEXT(textbox));
	scrollp = gtk_editable_get_position(GTK_EDITABLE(textbox));
	buffer = gtk_editable_get_chars(GTK_EDITABLE(textbox), 0, -1);
	buflen = strlen(buffer);
	/* if multibyte character was used, buflen is not a length of text.
	   so we query a length of text to the widget. */
	textlen = gtk_text_get_length(GTK_TEXT(textbox));
	if (buflen > 0) {
		DEBUG_MSG("stop_highlighting, buflen=%d\n", buflen);
		gtk_text_set_point(GTK_TEXT(textbox), 0);
		DEBUG_MSG("stop_highlighting, point set to %d\n", gtk_text_get_point(GTK_TEXT(textbox)));
		gtk_text_forward_delete(GTK_TEXT(textbox), textlen);
#ifdef CRAPPY_FIX
#if GTK_GT_7 == 0
		gtk_text_thaw(GTK_TEXT(textbox));
		gtk_text_freeze(GTK_TEXT(textbox));
#endif
#endif
		gtk_text_insert(GTK_TEXT(textbox), NULL, &textbox->style->black, NULL, buffer, buflen);
	}
	g_free(buffer);
	gtk_text_thaw(GTK_TEXT(textbox));
	DEBUG_MSG("stop_highlighting, set scrollp to %d\n", scrollp);
	gtk_text_set_point(GTK_TEXT(textbox), scrollp);
}

void stop_hi_cb(GtkWidget * widget, gpointer data)
{
	Ttext_positions positions;
	Tdocument *whichdoc;
	DEBUG_MSG("stop_hi_cb, started\n");
	if (data == NULL) {
		whichdoc = main_v->current_document;
	} else {
		whichdoc = data;
	}
	positions = get_positions(whichdoc->textbox);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->ins_txt_id);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->del_txt_id);
	stop_highlighting(whichdoc->textbox);
	whichdoc->ins_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "insert_text", GTK_SIGNAL_FUNC(doc_insert_text_cb), whichdoc);
	whichdoc->del_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "delete_text", GTK_SIGNAL_FUNC(doc_delete_text_cb), whichdoc);
	restore_positions(positions, whichdoc->textbox);
}

void start_hi_cb(GtkWidget * widget, gpointer data)
{
	Ttext_positions positions;
	Tdocument *whichdoc;
	DEBUG_MSG("start_hi_cb, started\n");
	if (data == NULL) {
		whichdoc = main_v->current_document;
	} else {
		whichdoc = (Tdocument *) data;
	}
	positions = get_positions(whichdoc->textbox);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->ins_txt_id);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->del_txt_id);
	start_highlighting(whichdoc->textbox);
	whichdoc->ins_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "insert_text", GTK_SIGNAL_FUNC(doc_insert_text_cb), whichdoc);
	whichdoc->del_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "delete_text", GTK_SIGNAL_FUNC(doc_delete_text_cb), whichdoc);
	restore_positions(positions, whichdoc->textbox);
}

void refresh_hi_cb(GtkWidget * widget, gpointer data)
{
	Ttext_positions positions;
	Tdocument *whichdoc;
	DEBUG_MSG("refresh_hi_cb, started, data=%p, main_v->current_document=%p\n", data, main_v->current_document);
	if (data == NULL) {
		whichdoc = main_v->current_document;
	} else {
		whichdoc = (Tdocument *) data;
	}
	positions = get_positions(whichdoc->textbox);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->ins_txt_id);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->del_txt_id);
	stop_highlighting(whichdoc->textbox);
	start_highlighting(whichdoc->textbox);
	whichdoc->ins_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "insert_text", GTK_SIGNAL_FUNC(doc_insert_text_cb), whichdoc);
	whichdoc->del_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "delete_text", GTK_SIGNAL_FUNC(doc_delete_text_cb), whichdoc);
	restore_positions(positions, whichdoc->textbox);
}

void line_hi_cb(GtkWidget * widget, gpointer data)
{
	Ttext_positions positions;
	Tdocument *whichdoc;
	DEBUG_MSG("line_hi_cb, started, data=%p, main_v->current_document=%p\n", data, main_v->current_document);
	if (data == NULL) {
		whichdoc = main_v->current_document;
	} else {
		whichdoc = (Tdocument *) data;
	}

	DEBUG_MSG("line_hi_cb, whichdoc=%p, textbox=%p\n", whichdoc, whichdoc->textbox);
	positions = get_positions(whichdoc->textbox);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->ins_txt_id);
	gtk_signal_disconnect(GTK_OBJECT(whichdoc->textbox), whichdoc->del_txt_id);
	line_highlighting(whichdoc->textbox);

	whichdoc->ins_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "insert_text", GTK_SIGNAL_FUNC(doc_insert_text_cb), whichdoc);
	whichdoc->del_txt_id = gtk_signal_connect(GTK_OBJECT(whichdoc->textbox), "delete_text", GTK_SIGNAL_FUNC(doc_delete_text_cb), whichdoc);
	restore_positions(positions, whichdoc->textbox);
}


void toggle_highlight_syntax_cb(GtkWidget * w, gpointer data)
{
	DEBUG_MSG("toggle_highlight_syntax, started\n");

	main_v->current_document->highlightstate = 1 - main_v->current_document->highlightstate;
	if (main_v->current_document->highlightstate == 0) {
		stop_hi_cb(NULL, main_v->current_document);
	} else {
		start_hi_cb(NULL, main_v->current_document);
	}
	setup_toggle_item(gtk_item_factory_from_widget(main_v->menubar),
					  N_("/View/Highlight syntax"), main_v->current_document->highlightstate);
}

void make_syntax_struct_list(void)
{
	GList *tmplist;
	gint tmplist_len;
	gchar **splittedstring;
	gint ret;
	Thighlight_pattern *hipa;
	gint hipa_count;
	gint i;

	DEBUG_MSG("make_syntax_struct_list, started\n");

	hipa = HIPA_ARRAY_PTR(main_v->syntax_structs);
	if (hipa) {
		for (i = 0; i < main_v->syntax_structs_n; i++) {
			regfree(&hipa->reg_comp_pat);
			gdk_colormap_free_colors(main_v->colormap, &hipa->color, 1);
			hipa++;
		}
	}
	g_free(main_v->syntax_structs);
	main_v->syntax_structs = NULL;
	main_v->syntax_structs_n = 0;

	tmplist = g_list_first(main_v->props.syntax_configstrings);
	if (!tmplist)
		return;

	tmplist_len = g_list_length(tmplist);
	main_v->syntax_structs = g_new(Thighlight_pattern, tmplist_len);
	if (!main_v->syntax_structs)
		return;

	hipa = HIPA_ARRAY_PTR(main_v->syntax_structs);
	hipa_count = 0;
	while (tmplist) {
		splittedstring = (gchar **) tmplist->data;
		if (!splittedstring[0] || !splittedstring[1]) {
			DEBUG_MSG("make_syntax_struct_list, problem with config file\n");
			tmplist = g_list_next(tmplist);
			continue;
		}
		DEBUG_MSG("make_syntax_struct_list, splittedstring[0]=%s\n", splittedstring[0]);
		DEBUG_MSG("make_syntax_struct_list, splittedstring[1]=%s\n", splittedstring[1]);
		ret = gdk_color_parse(splittedstring[0], &hipa->color);
		if (ret == 1) {
			gint ret2 = regcomp(&hipa->reg_comp_pat, splittedstring[1], REG_EXTENDED | REG_ICASE);
			if (ret2 == 0) {
				gdk_colormap_alloc_color(main_v->colormap, &hipa->color, FALSE, TRUE);
				DEBUG_MSG("make_syntax_struct_list, will be added as hipa %d\n", hipa_count);
				hipa++;
				hipa_count++;
			} else {
				gchar *tmp = g_strconcat(_("invalid highlighting pattern: "), splittedstring[1], NULL);
				statusbar_message(tmp, CRITICAL_MESSAGE);
				g_free(tmp);
			}
		} else {
			gchar *tmp = g_strconcat(_("could not parse highlighting color: "), splittedstring[0], NULL);
			statusbar_message(tmp, CRITICAL_MESSAGE);
			g_free(tmp);
		}
		tmplist = g_list_next(tmplist);
	}

	if (hipa_count == 0) {
		g_free(main_v->syntax_structs);
		main_v->syntax_structs = NULL;
		main_v->syntax_structs_n = 0;
	} else {
		main_v->syntax_structs_n = hipa_count;
	}

	DEBUG_MSG("apply_highlighting_config, ended, hipa_count = %d\n", hipa_count);
}




