/* Bluefish HTML Editor
 * complet.c - autocompletion of HTML tags when editing text.
 *
 * Copyright (C) 2001 Santiago Capel Torres
 *
 * 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
 */

/* Change log
   11 Nov 2001.- Improved autocompletion window
   04 Apr 2001.- Added a timer to show the window
   24 Mar 2001.- Track of the document doctype
   22 Mar 2001.- get_doctype
   21 Mar 2001.- Added to cvs
   20 Feb 2001.- This almost works!!!
   04 Jan 2001.- Santiago Capel Torres. Creation.
*/


/* TODO LIST

   There is a lot of work to do.
   - build arrays with values for:
     - Content types
     - Link types
     - etc. (see http://www.w3.org/TR/html401/types.html and more).
   - Position correctly the popup window
   - Set the focus to the popup window and insert the text in the document with
     the tab key.
   - If the text to be inserted is equal than the following text in the document,
     replace it
   - Place hide_autocompletion on a lot of events, such as change of notebooks,
      close files, delete text, insert tags, etc.

 */


/* How this module works

   This is the very start of the code for autocompletion of tags.
   It is based in the Document Definition File (DTD) for the DOCTYPE of the file
   that we are editing. For now, I am using the file "loose.dtd", but the version
   of the file would be got from the DOCTYPE TAG.

   When any of this keys are pressed: < > = or space, we load the DTD into memory.
   Then we look for the actual tag that is being edited. If none is found, we show
   the DOCTYPE TAG. In any other case, we look for the tag being edited in the list
   of definitions of tags. It it is found, we look if we are typing a tag, an
   attribute or an attribute value and show a list with the possible options.

 */

/* Standards used:
   ISO 639: 2 character language shortcuts
   RFC1354, RFC1557, RFC1468, RFC1554, RFC1922, RFC1947, RFC1556, RFC2319, RFC1456, RFC1489 et.al.
   Character-set codes
   
*/

#include "default_include.h"
#include <string.h>				/* rindex */
#include <ctype.h>				/* isalnum */

#include "bluefish.h"

#ifdef AUTOCOMPLET

#include "ref_dialog.h"			/* phpx_get_function_desc */
#include "interface.h"			/* statusbar_message */
#include "document.h"
#include "parsedtd.h"			/* filetoDTD */
#include "complet.h"

#include "gdk/gdkkeysyms.h"


#define MAXLEN_TAG  100
#define MAXLEN_ATTR 100

static const gchar *HTMLColors[] =
	{ "black", "maroon", "green", "olive", "navy", "purple", "teal",
	"gray", "silver", "red", "lime", "yellow", "blue", "fuchsia", "aqua",
	"white"
};
static const gchar *HTMLLanguagecodes[] =
	{ "en-us", "en", "de", "fr", "es", "aa", "ab", "af", "am", "ar", "as",
	"ay", "az", "ba", "be", "bg", "bh", "bi", "bn", "bo", "br", "ca", "co",
	"cs", "cy", "da", "dz", "el",
	"eo", "et", "eu", "fi", "fj", "fo", "fy", "ga", "gl", "gn", "gu", "ha",
	"he", "hi", "hr", "hu", "hy",
	"ia", "ie", "ik", "id", "is", "it", "iu", "ja", "jv", "ka", "kk", "kl",
	"km", "kn", "ko", "ks", "ku",
	"ky", "la", "ln", "lo", "lt", "lv", "mg", "mi", "mk", "ml", "mn", "mo",
	"mr", "ms", "mt", "my", "na",
	"ne", "nl", "no", "oc", "(oromo)", "or", "pa", "pl", "ps", "pt", "qu",
	"rm", "rn", "ro", "ru", "rw",
	"sa", "sd", "sg", "sh", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr",
	"ss", "st", "su", "sv", "sw",
	"ta", "te", "tg", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts", "tt",
	"tw", "ug", "uk", "ur", "uz",
	"vi", "vo", "wo", "xh", "yi", "yo", "za", "zh", "zu"
};
static const gchar *HTMLcharsets[] =
	{ "ISO_8859-1", "ANSI_X3.4-1968", "ISO-10646-UTF-1", "ISO_646.basic",
	"ISO_646.irv", "BS_4730", "NATS-SEFI", "NATS-SEFI-ADD", "NATS-DANO",
	"NATS-DANO-ADD",
	"SEN_850200_B", "SEN_850200_C", "KS_C_5601-1987", "ISO-2022-KR",
	"EUC-KR", "ISO-2022-JP",
	"ISO-2022-JP-2", "ISO-2022-CN", "ISO-2022-CN-EXT", "JIS_C6220-1969-jp",
	"JIS_C6220-1969-ro", "IT",
	"PT", "ES", "greek7-old", "latin-greek", "DIN_66003",
	"NF_Z_62-010_(1973)", "Latin-greek-1",
	"ISO_5427", "JIS_C6226-1978", "BS_viewdata", "INIS", "INIS-8",
	"INIS-cyrillic", "ISO_5427",
	"ISO_5428", "GB_1988-80", "GB_2312-80", "NS_4551-1", "NS_4551-2",
	"NF_Z_62-010", "videotex-suppl",
	"PT2", "ES2", "MSZ_7795.3", "JIS_C6226-1983", "greek7", "ASMO_449",
	"iso-ir-90", "JIS_C6229-1984-a",
	"JIS_C6229-1984-b", "JIS_C6229-1984-b-add", "JIS_C6229-1984-hand",
	"JIS_C6229-1984-hand-add",
	"JIS_C6229-1984-kana", "ISO_2033-1983", "ANSI_X3.110-1983",
	"ISO_8859-2", "T.61-7bit", "T.61-8bit",
	"ISO_8859-3", "ISO_8859-4", "ECMA-cyrillic", "CSA_Z243.4-1985-1",
	"CSA_Z243.4-1985-2",
	"CSA_Z243.4-1985-gr", "ISO_8859-6", "ISO_8859-6-E", "ISO_8859-6-I",
	"ISO_8859-7", "T.101-G2",
	"ISO_8859-8", "ISO_8859-8-E", "ISO_8859-8-I", "CSN_369103",
	"JUS_I.B1.002", "ISO_6937-2-add",
	"IEC_P27-1", "ISO_8859-5", "JUS_I.B1.003-serb", "JUS_I.B1.003-mac",
	"ISO_8859-9", "greek-ccitt",
	"NC_NC00-10", "ISO_6937-2-25", "GOST_19768-74", "ISO_8859-supp",
	"ISO_10367-box", "ISO-8859-10",
	"latin-lap", "JIS_X0212-1990", "DS_2089", "us-dk", "dk-us",
	"JIS_X0201", "KSC5636",
	"ISO-10646-UCS-2", "ISO-10646-UCS-4", "DEC-MCS", "hp-roman8",
	"macintosh", "IBM037", "IBM038",
	"IBM273", "IBM274", "IBM275", "IBM277", "IBM278", "IBM280", "IBM281",
	"IBM284", "IBM285", "IBM290",
	"IBM297", "IBM420", "IBM423", "IBM424", "IBM437", "IBM500", "IBM775",
	"IBM850", "IBM851", "IBM852",
	"IBM855", "IBM857", "IBM860", "IBM861", "IBM862", "IBM863", "IBM864",
	"IBM865", "IBM866", "IBM868",
	"IBM869", "IBM870", "IBM871", "IBM880", "IBM891", "IBM903", "IBM904",
	"IBM905", "IBM918", "IBM1026",
	"EBCDIC-AT-DE", "EBCDIC-AT-DE-A", "EBCDIC-CA-FR", "EBCDIC-DK-NO",
	"EBCDIC-DK-NO-A", "EBCDIC-FI-SE",
	"EBCDIC-FI-SE-A", "EBCDIC-FR", "EBCDIC-IT", "EBCDIC-PT", "EBCDIC-ES",
	"EBCDIC-ES-A", "EBCDIC-ES-S",
	"EBCDIC-UK", "EBCDIC-US", "UNKNOWN-8BIT", "MNEMONIC", "MNEM", "VISCII",
	"VIQR", "KOI8-R", "KOI8-U",
	"IBM00858", "IBM00924", "IBM01140", "IBM01141", "IBM01142", "IBM01143",
	"IBM01144", "IBM01145",
	"IBM01146", "IBM01147", "IBM01148", "IBM01149", "Big5-HKSCS",
	"UNICODE-1-1", "SCSU", "UTF-7",
	"UTF-16BE", "UTF-16LE", "UTF-16", "UNICODE-1-1-UTF-7", "UTF-8",
	"iso-8859-13", "iso-8859-14",
	"ISO-8859-15", "JIS_Encoding", "Shift_JIS",
	"Extended_UNIX_Code_Packed_Format_for_Japanese",
	"Extended_UNIX_Code_Fixed_Width_for_Japanese", "ISO-10646-UCS-Basic",
	"ISO-10646-Unicode-Latin1",
	"ISO-10646-J-1", "ISO-Unicode-IBM-1261", "ISO-Unicode-IBM-1268",
	"ISO-Unicode-IBM-1276",
	"ISO-Unicode-IBM-1264", "ISO-Unicode-IBM-1265",
	"ISO-8859-1-Windows-3.0-Latin-1",
	"ISO-8859-1-Windows-3.1-Latin-1", "ISO-8859-2-Windows-Latin-2",
	"ISO-8859-9-Windows-Latin-5",
	"Adobe-Standard-Encoding", "Ventura-US", "Ventura-International",
	"PC8-Danish-Norwegian",
	"PC8-Turkish", "IBM-Symbols", "IBM-Thai", "HP-Legal", "HP-Pi-font",
	"HP-Math8",
	"Adobe-Symbol-Encoding", "HP-DeskTop", "Ventura-Math",
	"Microsoft-Publishing", "Windows-31J",
	"GB2312", "Big5", "windows-1250", "windows-1251", "windows-1252",
	"windows-1253", "windows-1254",
	"windows-1255", "windows-1256", "windows-1257", "windows-1258",
	"TIS-620", "HZ-GB-2312"
};

static const gchar *MIMEtypes[] =
	{ "text/html", "text/plain", "application/octet-stream", "image/gif",
	"image/jpeg", "image/png", "video/avi", "video/x-mapeg",
	"video/msvideo", "video/mpeg",
	"video/quicktime", "audio/midi", "audio-wav", "audio/x-wav",
	"application/pdf",
	"application/x-shockwave-flash", "application/x-rx",
	"audio/x-pn-realaudio-plugin"
};

static const gchar *LINKtypes[] =
	{ "Stylesheet", "Alternate", "Start", "Next", "Prev", "Contents",
	"Index",
	"Glossary", "Copyright", "Chapter", "Section", "Subsection",
	"Appendix", "Help",
	"Bookmark"
};

static const gchar *MEDIAdescriptors[] =
	{ "screen", "tty", "tv", "projection", "handheld", "print", "braille",
	"aural",
	"all"
};

static dont_run_focus_out;
/* The popup window to show the tags */
static GtkWidget *autocompletwindow = NULL;
GtkCList *clist = NULL;


inline int isspaces(char c)
{
	return (c == ' ' || c == '\n' || c == '\t' || c == '\r');
}

/* GUI code */
static gint static_show_autocompletion_window(gpointer param);
static guint idtimer = 0;
/*******************************************************************/
/*
 * Function: hide_autocomplet_window
 * Description: Hide de list of tags
 *    This function should be called on many events, such
 *    as key presses, window deactivating, notebook
 *    changing, etc.
 */
void hide_autocompletion_window()
{
	if (idtimer) {
		gtk_timeout_remove(idtimer);
		idtimer = 0;
	}

	if (autocompletwindow) {
		gtk_widget_hide(autocompletwindow);
		gtk_clist_clear((GtkCList *) clist);
	}
}

/*
 * Function: focus_out_cb
 * Description: Hide de list of tags when the window loses the focus
 *              except when we have made a selection, because the selection_made_cb
 *              has already hiden the window
 */
static gboolean focus_out_cb(GtkWidget * widget, GdkEventFocus * event)
{
	if (dont_run_focus_out) {
		dont_run_focus_out = 0;
		return TRUE;
	}
	hide_autocompletion_window();
	return FALSE;
}

/*
 * Function: key_press_cb
 * Description: Hide de list of tags if escape is pressed
 *
 *     TODO: Pass the keys to the parent window
 */
static gint key_press_cb(GtkWidget * widget, GdkEventKey * event)
{
	switch (event->keyval) {
	case GDK_Escape:
		hide_autocompletion_window();
		return TRUE;
	default:
		return FALSE;
	}
}

/*  Function: selection_made_cb:
 *  Arguments: (standard)
 *  Description: 
 *     Inserts the text of the selected item of the list into the textbox
*/
static void selection_made_cb(GtkWidget * clist,
							  gint row, gint column,
							  GdkEventButton * event, gpointer data)
{
	gchar *text;
	gchar lastchar;

	/* Get the text that is stored in the selected row and column
	 * which was clicked in. We will receive it as a pointer in the
	 * argument text.
	 */
	dont_run_focus_out = 1;
	gtk_clist_get_text(GTK_CLIST(clist), row, column, &text);
	doc_insert_text(main_v->current_document, text);
	if (strstr(text, "!DOCTYPE"))
		doc_set_doctype(main_v->current_document);

	/* Concatenate completions */
	lastchar = text[strlen(text) - 1];
	if (lastchar != '>' && lastchar != '<' && lastchar != '=')
		lastchar = ' ';
	static_show_autocompletion_window(GINT_TO_POINTER((gint) lastchar));
	return;
}

/*  Function: make_autocomplet_window:
 *  Arguments: 
 *      main_v
 *  Description:
 *      Creates or shows the window wich contains the list of tags
 *      Maybe there is another way of doing this more smart :-)
*/
static void make_autocomplet_window(Tmain * main_v)
{
	gint posx, posy, wx, wy;
	GtkWidget *tmpwin;
	GtkWidget *scrolled_window;

	GtkStyle *defstyle, *completstyle;
	GdkColor yellow;
	GdkFont *myfont;

	if (autocompletwindow == NULL) {
		autocompletwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  		gtk_widget_realize(autocompletwindow); 
		gdk_window_set_decorations(autocompletwindow->window, 0);

		/* Create a scrolled window to pack the CList widget into */
		scrolled_window = gtk_scrolled_window_new(NULL, NULL);
		gtk_scrolled_window_set_policy(
					       GTK_SCROLLED_WINDOW(scrolled_window),
					       GTK_POLICY_NEVER,
					       GTK_POLICY_AUTOMATIC);
		gtk_container_add(GTK_CONTAINER(autocompletwindow),
						  scrolled_window);
		gtk_widget_show(scrolled_window);

		/* Create the CList */
		clist = (GtkCList *) gtk_clist_new(1);
		gtk_container_add(GTK_CONTAINER(scrolled_window),
						  GTK_WIDGET(clist));
		gtk_clist_set_auto_sort(clist, TRUE);
		gtk_clist_set_shadow_type(clist, GTK_SHADOW_NONE);

		yellow.red = 0xffff;
		yellow.green = 0xffff;
		yellow.blue = 0x0000;
		if (!gdk_color_alloc(gdk_colormap_get_system(), &yellow))
			DEBUG_MSG("couldn't allocate colour: yellow");
		defstyle = gtk_widget_get_default_style();
		completstyle = gtk_style_copy(defstyle);
		completstyle->base[GTK_STATE_NORMAL] = yellow;
		completstyle->base[GTK_STATE_ACTIVE] = yellow;
		gdk_font_unref(completstyle->font);
		myfont = gdk_font_load
			("-adobe-times-medium-r-normal-*-*-100-*-*-p-*-iso8859-1");
		if (!myfont) {
			DEBUG_MSG("Cargo fuente standard\n");
			myfont = gdk_font_load("-*-*-*-*-normal-*-*-100-*-*-*-*-*");
		}

		if (myfont)
			completstyle->font = myfont;
		gtk_widget_set_style(GTK_WIDGET(clist), completstyle);

		gtk_widget_show(GTK_WIDGET(clist));


		/* Connect signals */
		gtk_signal_connect(GTK_OBJECT(clist), "select_row",
						   GTK_SIGNAL_FUNC(selection_made_cb), NULL);
		gtk_signal_connect(GTK_OBJECT(autocompletwindow),
						   "focus_out_event",
						   GTK_SIGNAL_FUNC(focus_out_cb), NULL);
		gtk_signal_connect(GTK_OBJECT(autocompletwindow),
						   "key_press_event",
						   GTK_SIGNAL_FUNC(key_press_cb), NULL);
	}
	/* Calculate the absolute coords. of the cursor, following the parents */
	tmpwin = main_v->current_document->textbox;
	posx = GTK_TEXT(tmpwin)->cursor_pos_x;
	posy = GTK_TEXT(tmpwin)->cursor_pos_y;
	while (tmpwin) {
		switch (GTK_OBJECT_TYPE(tmpwin)) {
			/* Ignore the following containers */
		case 49429:			/* GtkTable */
		case 46101:			/* GtkHBox */
		case 40725:			/* GtkVBox */
			break;
		default:
			gdk_window_get_position(tmpwin->window, &wx, &wy);
			posx += wx;
			posy += wy;
		}
		tmpwin = tmpwin->parent;
	}
	wx = gtk_clist_optimal_column_width(clist, 0) + 30;
	wy = 120;
	gtk_widget_set_usize(GTK_WIDGET(autocompletwindow), wx, wy);
	if (posx + wx > gdk_screen_width())
		posx -= wx;
	if (posy + wy > gdk_screen_height())
		posy -= wy + 10;
	gtk_widget_set_uposition(autocompletwindow, posx, posy);
	gtk_window_set_transient_for(GTK_WINDOW(autocompletwindow),
								 GTK_WINDOW(main_v->main_window));
	gtk_widget_set_state(autocompletwindow,
						 GTK_STATE_ACTIVE | GTK_STATE_SELECTED);
	gtk_widget_grab_focus(GTK_WIDGET(clist));
	gtk_widget_show(autocompletwindow);
}


static void list_to_list(gpointer mydata, gpointer udat)
{
	gtk_clist_append(clist, mydata);
}

static gint append_to_list(const gchar * text, gchar * prefix,
						   gchar * sufix)
{
	gchar tag[MAXLEN_TAG], *tagptr = tag;

	if (prefix) {
		strcpy(tag, prefix);
		strcat(tag, text);
	} else
		strcpy(tag, text);
	if (sufix)
		strcat(tag, sufix);
	if (clist == NULL || autocompletwindow == NULL)
		make_autocomplet_window(main_v);
	return gtk_clist_append(clist, &tagptr);
}

/* Appends a list to the clist and deletes de strings */
static void append_list_to_list(GList * list, gchar * prefix,
								gchar * sufix)
{
	GList *tmplist;
	for (tmplist = g_list_first(list);
		 tmplist; tmplist = g_list_next(tmplist)) {
		append_to_list(tmplist->data, prefix, sufix);
		g_free(tmplist->data);
	}
}


/* Autocompletion code */
/*******************************************************************/
/* Function get_previoustag
 * Description:
 *   Find the previous tag in the document
 * Arguments:
 *   text - text of the document, from the start to the actual position
 *   position - position of the insertion point in the text
 *   previoustag - string to hold the previous tag if found
 *   previousattr - string to hold the previous attr if found
 *   intag - flag to hold whether we are in a tag or out of any tag.
 *   anequalfound - flag to hold whether an equal is the last non-blank char typed
 * Return value:
 *   1 - a tag has been found
 *   0 - a tag has not been found
 */
static gint get_previoustag(gchar * text, gint position,
							gchar * previoustag, gchar * previousattr,
							gint * intag, gint * anequalfound)
{
	gchar *lessthan, *revptr, *textptr, *tmptag;
	char ch;
	gint inquotes = 0, lastwordfound = 0;

  here:
	*intag = 1;
	*previoustag = *previousattr = '\0';
	if (!text || !*text)
		return 0;
	revptr = previousattr;
	tmptag = previoustag;
	textptr = text + position - 1;
	lessthan = NULL;
	*anequalfound = 0;
	while (textptr >= text && isspaces(*textptr))
		textptr--;
	if (textptr >= text && *textptr == '=') {
		*anequalfound = 1;
		textptr--;
	} else if (textptr >= text && *textptr == '(') {
		textptr--;
	}
	while (textptr >= text) {
		if (!isspaces(*textptr)) {
			if (isalnum(*textptr) && !lastwordfound)
				*revptr++ = *textptr;
			else
				lastwordfound = 1;
		} else {
			if (*(revptr - 1) != '\0')
				lastwordfound = 1;
		}
		if (*textptr == '"')
			inquotes = !inquotes;
		else if (*textptr == '<') {
			lessthan = textptr;
			if (!inquotes)
				break;
			else {
				goto here;
			}
		} else if (*textptr == '>')
			*intag = 0;
		textptr--;
	}
	if (lessthan) {
		lessthan++;
		while (*lessthan && !isspaces(*lessthan) && *lessthan != '>')
			*previoustag++ = *lessthan++;
		if (*(previoustag - 1) == '>')
			previoustag--;
		*previoustag = '\0';
	}
	if (*tmptag == '/')
		*intag = 0;
	*revptr = '\0';
	if (inquotes) {
		return 0;
	} else if (lastwordfound) {
		/* reverse previousattr */
		gchar *tmp = previousattr;
		while (--revptr > previousattr) {
			ch = *previousattr;
			*previousattr++ = *revptr;
			*revptr = ch;
		}
		if (strcmp(tmptag, tmp) == 0)
			*tmp = '\0';
	}
	return 1;
}





/*******************************************************************/
/* Function get_previoustag_nonclosed
 * Description:
 *   Find the previous non-closed tag in the document and the list of tags that
 *   contains
 * Arguments:
 *   text - text of the document, from the start to the actual position
 *   position - position of the insertion point in the text
 *   previoustagnonclosed - string to hold the previous tag if found
 * Return value:
 *   1 - a tag has been found
 *   0 - a tag has not been found
 */
static gint get_previoustag_nonclosed(gchar * text, gint position,
									  char keytyped,
									  gchar * previoustagnonclosed)
{
	gchar *greaterthan, *textptr, *tmptag, *prevptr;
	GList *closingtags = NULL, *lsttmp;
	int found;

	*previoustagnonclosed = '\0';
	if (!text || !*text)
		return 0;
	textptr = text + position - 1;

	while (textptr >= text) {
		if (keytyped == '>') {
			keytyped = '\0';
			greaterthan = textptr;
		} else {
			while (textptr >= text && *textptr != '>')
				textptr--;
			greaterthan = textptr;
		}
		while (textptr >= text && *textptr != '<')
			textptr--;
		if (*textptr == '<') {
			tmptag = textptr + 1;
			while (isspaces(*tmptag))
				tmptag++;
			prevptr = previoustagnonclosed;
			if (*tmptag == '/') {
				tmptag++;
				while (isspaces(*tmptag))
					tmptag++;
				while (!isspaces(*tmptag) && tmptag < greaterthan)
					*prevptr++ = *tmptag++;
				*prevptr = '\0';
				closingtags =
					g_list_append(closingtags,
								  g_strdup(previoustagnonclosed));
			} else {
				while (isspaces(*tmptag))
					tmptag++;
				while (!isspaces(*tmptag) && tmptag < greaterthan)
					*prevptr++ = *tmptag++;
				*prevptr = '\0';
				found = 0;
				while ((lsttmp = g_list_last(closingtags)) && !found) {
					if (strcasecmp(lsttmp->data, previoustagnonclosed) ==
						0) found = 1;
					g_free(lsttmp->data);
					closingtags = g_list_remove_link(closingtags, lsttmp);
				};
				if (!found)
					break;
			}
		}
	}
	return 1;
}



/*******************************************************************/
/* Function: get_content_elements
 * Description:
 *   Fill a list with the elements that another element can contain
 * Arguments:
 *   element - Element whose contents we are looking for
 *   elements - GList that will contain the elements
 *
 * TODO:
 *   Perhaps this list would be ordered
 *
 */
static gint get_content_elements(GList * elements, gchar * element,
								 GList ** elementscontained)
{
	SGML_elementinfo *ei;
	gchar *content, tag[MAXLEN_TAG] = "/", *tagptr = tag;
	gint parent = 0, endword = 0, removing = -1;
	GList *lsttmp;

	*elementscontained = NULL;
	ei = get_element_byname(elements, element);
	if (ei) {
		*elementscontained =
			g_list_append(*elementscontained,
						  g_strdup(strcat(strcat(tag, element), ">")));
		*tag = '\0';
		content = ei->content;
		do {
			switch (*content) {
			case '(':
				parent++;
				endword = 1;
				break;
			case ')':
				endword = 1;
				break;
			case ' ':
			case '|':
				endword = 1;
				break;
			case '-':
				removing = parent;
				break;
			case '*':
			case '+':
			case '&':
			case '?':
			case ',':
				break;
			default:
				*tagptr++ = *content;
			}
			if (endword) {
				*tagptr = '\0';
				if (*tag) {
					if (strcasecmp(tag, "#PCDATA") != 0) {
						if (removing != -1) {
							for (lsttmp = *elementscontained; lsttmp;
								 lsttmp = g_list_next(lsttmp)) {
								if (strcmp((gchar *) lsttmp->data, tag) ==
									0) {
									g_free(lsttmp->data);
									*elementscontained =
										g_list_remove_link
										(*elementscontained, lsttmp);
									break;
								}
							}
						} else {
							*elementscontained =
								g_list_append(*elementscontained,
											  g_strdup(tag));
						}
					}
				}
				tagptr = tag;
				endword = 0;
			}
			if (*content == ')') {
				parent--;
				if (parent == removing)
					removing = -1;
			}
		} while (*content++);
	}
	return 1;
}





/* Function: fill_tags
 * Description:
 *   When we are not editing a tag (normaly after > or <), fill the list of
 *      possible tags
 * Arguments:
 *   element - Previous tag in the text
 *   prefix  - string to prefix to the elements of the list
 *   sufix   - string to sufix to the elements of the list
 */
static gint fill_tags(GList * elements, gchar * element,
					  gchar * nonclosedelement, gchar * prefix,
					  gchar * sufix)
{
	int i;
	GList *elementscontained = NULL, *lsttmp;
	DEBUG_MSG("fill_tags: element=%s, nonclosedelement=%s,\n", element,
			  nonclosedelement);

	if (!*element && !*nonclosedelement) {
		for (i = 0; HTML_doctypes[i].doctype != NULL; i++)
			append_to_list(HTML_doctypes[i].doctype, prefix, ">");
	} else if (strcasecmp(element, "!DOCTYPE") == 0
			   || strcasecmp(nonclosedelement, "!DOCTYPE") == 0) {
		append_to_list("HTML ", prefix, NULL);
		append_to_list("HTML>", prefix, NULL);
	} else {
		if (*element == '/' || *nonclosedelement)
			get_content_elements(elements, nonclosedelement,
								 &elementscontained);
		else
			get_content_elements(elements, element, &elementscontained);
		if (elementscontained) {
			for (lsttmp = elementscontained; lsttmp;
				 lsttmp = g_list_next(lsttmp)) {
				append_to_list((gchar *) lsttmp->data, prefix, sufix);
				g_free(lsttmp->data);
			}
			g_list_free(elementscontained);
		}
	}
	return 1;
}


/* Function: fill_attrs
 * Description:
 *    When we are editing a tag, but no an attribute value, fill in a list with
 *      possible attributes
 * Arguments:
 *   element - Tag that we are editing
 *   prefix  - string to prefix to the elements of the list
 *   sufix   - string to sufix to the elements of the list
 * Return value:
 *   0 - The element has no attributes, or
 *   the number of attributes added to the list
 */
static int fill_attrs(GList * elements, gchar * element, gchar * prefix,
					  gchar * sufix)
{
	SGML_elementinfo *ei;
	SGML_attrinfo *ai;
	GList *glattrs;

	ei = get_element_byname(elements, element);
	if (ei) {
		glattrs = ei->attrs;
		while (glattrs) {
			ai = (SGML_attrinfo *) glattrs->data;
			append_to_list(ai->name, prefix, sufix);
			glattrs = g_list_next(glattrs);
		}
		return 1;				// clist->NELEMENTS
	} else
		return 0;
}

/* Function: fill_attrvalues
 * Description:
 *    When we are editing an attribute value, fill in a list with
 *      possible values for this attribute
 *    The default value is selected
 * Arguments:
 *   element - Tag that we are editing
 *   attr    - Attribute that we are editing
 *   prefix  - string to prefix to the elements of the list
 *   sufix   - string to sufix to the elements of the list
 * Return value:
 *   0 - The element doesn't exists or haven't any attributes, or
 *   the number of values added to the list
 */
static int fill_attrvalues(GList * elements, gchar * element, gchar * attr,
						   gchar * prefix, gchar * sufix)
{
  SGML_elementinfo *ei;
  SGML_attrinfo *ai;
  GList *glattrs;
  GList *tmplist;
  gint rowadded, rowselected=0;
  int attrfound = 0, i;
  
  ei = get_element_byname(elements, element);
  if (ei) {
    glattrs = ei->attrs;
    while (glattrs && !attrfound) {
      ai = (SGML_attrinfo *) glattrs->data;
      if (strcasecmp(ai->name, attr) == 0) {
	attrfound = 1;
	if (ai->defaultvaluetype == HTML_DEFVALUE_FIXED) {
	  append_to_list(ai->defaultvalue, prefix, sufix);
	} else if( ai->defaultvaluetype == HTML_DEFVALUE_LITERAL 
		   || ai->attrtype == -1) {
	  GList *popuplist = get_attr_othervalues(ai);
	  if (popuplist) {
	    append_list_to_list(popuplist, prefix, sufix);
	    for (tmplist = g_list_first(popuplist);
		 tmplist; 
		 tmplist = g_list_next(tmplist)) {
	      rowadded = append_to_list(tmplist->data, prefix, sufix);
	      if( ai->defaultvaluetype == HTML_DEFVALUE_LITERAL )
		if( strcmp(tmplist->data, ai->defaultvalue) == 0 )
		  rowselected = rowadded;
/*    	      g_free(tmplist->data);  */
	    }
	    g_list_free(popuplist);
	  }
	} else {
	  switch (ai->attrtype) {
	  case HTML_AT_TEXT:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_URI:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_COLOR:
	    for (i = 0;
		 i <
		   sizeof(HTMLColors) / sizeof(HTMLColors[0]);
		 i++)
	      append_to_list(HTMLColors[i], prefix, sufix);
	    break;
	  case HTML_AT_LENGTH:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_PIXELS:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_MULTILENGTH:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_CONTENTYPE:
	    for (i = 0;
		 i <
		   sizeof(MIMEtypes) / sizeof(MIMEtypes[0]); i++)
	      append_to_list(MIMEtypes[i], prefix, sufix);
	    break;
	  case HTML_AT_CONTENTYPES:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_LANGUAGECODE:
	    for (i = 0;
		 i <
		   sizeof(HTMLLanguagecodes) /
		   sizeof(HTMLLanguagecodes[0]); i++)
	      append_to_list(HTMLLanguagecodes[i], prefix,
			     sufix);
	    break;
	  case HTML_AT_CHARSET:
	    for (i = 0;
		 i <
		   sizeof(HTMLcharsets) /
		   sizeof(HTMLcharsets[0]); i++)
	      append_to_list(HTMLcharsets[i], prefix, sufix);
	    break;
	  case HTML_AT_CHARSETS:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_CHARACTER:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_DATETIME:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_LYNKTYPES:
	    for (i = 0;
		 i <
		   sizeof(LINKtypes) / sizeof(LINKtypes[0]); i++)
	      append_to_list(LINKtypes[i], prefix, sufix);
	    break;
	  case HTML_AT_MEDIADESC:
	    for (i = 0;
		 i <
		   sizeof(MEDIAdescriptors) /
		   sizeof(MEDIAdescriptors[0]); i++)
	      append_to_list(MEDIAdescriptors[i], prefix,
			     sufix);
	    break;
	  case HTML_AT_SCRIPT:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_STYLESHEET:
	    /* TODO Fill the values of this attribute */
	    break;
	  case HTML_AT_FRAMETARGET:
	    /* Here we fill the list with targets from the current project */
	    g_list_foreach(main_v->current_project.targetlist,
			   list_to_list, "");
	    break;
	  }
	}
      }
      glattrs = g_list_next(glattrs);
    }
    gtk_clist_select_row(clist, rowselected, 0); 
    if (!attrfound)
      return fill_attrs(elements, element, prefix, sufix);
    else
      return 1;
  }
  return 0;
}

/* Function: show_autocompletion_window
 * Description:
 *      If autocompletion is on, react to the press of certain keys to show a
 *      window with posible tags, attributes and values.
 * Arguments:
 *      keytyped - character just typed on the document.
*/

void show_autocompletion_window(gchar keytyped)
{
	DEBUG_MSG("\nstart:show_autocompletion_window(keytyped=%c)\n",
			  keytyped);
	if (idtimer)
		gtk_timeout_remove(idtimer);
	if (main_v->props.autocomplet_delay > 0)
		idtimer = gtk_timeout_add(main_v->props.autocomplet_delay,
								  (GtkFunction)
								  static_show_autocompletion_window,
								  GINT_TO_POINTER((gint) (keytyped)));
	else
		static_show_autocompletion_window(GINT_TO_POINTER
										  ((gint) (keytyped)));
	DEBUG_MSG("end:show_autocompletion_window(keytyped=%c)\n", keytyped);
}

static gint static_show_autocompletion_window(gpointer param)
{
	char lastchartyped = '\0';
	char keytyped = GPOINTER_TO_INT(param);
	char *function_desc;

	GtkEditable *editable;
	gchar *text = NULL;
	gchar previoustag[MAXLEN_TAG], previoustagnonclosed[MAXLEN_TAG] = "",
		previousattr[MAXLEN_ATTR];
	gint len, intag, position, equal, dtdindex = -1;

	if (main_v->props.autocomplet_html) {
		hide_autocompletion_window();
		/* we react only to this keys */
		if (keytyped == '<' || keytyped == ' ' || keytyped == '='
			|| keytyped == '>' || keytyped == '\t' || keytyped == '\r'
			|| keytyped == '\n' || keytyped == '(') {
			editable = GTK_EDITABLE(main_v->current_document->textbox);
			len =
				gtk_text_get_length(GTK_TEXT
									(main_v->current_document->textbox));
			if (len > 0) {
				position = gtk_editable_get_position(editable);
				if (position > len)
					position = len;
				doc_load_dtd(main_v->current_document);
				dtdindex = main_v->current_document->dtd_loaded;
				text = gtk_editable_get_chars(editable, 0, position);
				if (!text)
					return FALSE;
				lastchartyped = text[position - 1];
				get_previoustag(text, position, previoustag, previousattr,
								&intag, &equal);
				get_previoustag_nonclosed(text, position, keytyped,
										  previoustagnonclosed);
				DEBUG_MSG
					("text(%d)=%s\ntag(%d)=%s\nattr(%d)=%s\nprevnonclosed=%s\n",
					 position, text, intag, previoustag, equal,
					 previousattr, previoustagnonclosed);
				g_free(text);
			} else {
				*previoustag = '\0';
				*previousattr = '\0';
				intag = 0;
			}
			if (strcasecmp(previoustag, "?PHP") == 0
				|| strcasecmp(previoustag, "?PHP3") == 0) {
				switch (keytyped) {
				case '(':
					{
						if (*previousattr) {
							function_desc =
								php3_get_function_desc(previousattr);
							if (function_desc)
								statusbar_message(function_desc, 10000);
						}
						break;
					}
				}
			} else if (strcasecmp(previoustag, "?PHP4") == 0) {
				switch (keytyped) {
				case '(':
					{
						if (*previousattr) {
							function_desc =
								php4_get_function_desc(previousattr);
							if (function_desc)
								statusbar_message(function_desc, 10000);
						}
						break;
					}
				}
			} else
				switch (keytyped) {
				case '<':
					fill_tags(HTML_doctypes[dtdindex].elements,
							  previoustag, previoustagnonclosed, NULL,
							  NULL);
					break;
				case '>':
					fill_tags(HTML_doctypes[dtdindex].elements,
							  previoustag, previoustagnonclosed, "<",
							  NULL);
					break;
				case ' ':
					if (intag && *previousattr) {
						fill_attrvalues(HTML_doctypes[dtdindex].elements,
										previoustag, previousattr, NULL,
										NULL);
					} else if (intag && !*previousattr) {
						if (lastchartyped == ' ')
							fill_attrs(HTML_doctypes[dtdindex].elements,
									   previoustag, NULL, "=");
						else
							fill_attrs(HTML_doctypes[dtdindex].elements,
									   previoustag, " ", "=");
					} else {
						fill_tags(HTML_doctypes[dtdindex].elements,
								  previoustag, previoustagnonclosed, "<",
								  NULL);
					}
					break;
				case '\n':
					if (intag && *previousattr) {
						fill_attrvalues(HTML_doctypes[dtdindex].elements,
										previoustag, previousattr, NULL,
										NULL);
					} else if (intag && !*previousattr) {
						fill_attrs(HTML_doctypes[dtdindex].elements,
								   previoustag, NULL, "=");
					} else {
						fill_tags(HTML_doctypes[dtdindex].elements,
								  previoustag, previoustagnonclosed, "<",
								  NULL);
					}
					break;
				case '\t':
					if (intag && *previousattr) {
						fill_attrvalues(HTML_doctypes[dtdindex].elements,
										previoustag, previousattr, NULL,
										NULL);
					} else if (intag && !*previousattr) {
						fill_attrs(HTML_doctypes[dtdindex].elements,
								   previoustag, NULL, "=");
					} else {
						fill_tags(HTML_doctypes[dtdindex].elements,
								  previoustag, previoustagnonclosed, "<",
								  NULL);
					}
					break;
				case '\r':
					if (intag && *previousattr) {
						fill_attrvalues(HTML_doctypes[dtdindex].elements,
										previoustag, previousattr, NULL,
										NULL);
					} else if (intag && !*previousattr) {
						fill_attrs(HTML_doctypes[dtdindex].elements,
								   previoustag, NULL, "=");
					} else {
						fill_tags(HTML_doctypes[dtdindex].elements,
								  previoustag, previoustagnonclosed, "<",
								  NULL);
					}
					break;
				case '=':
					if (intag && *previousattr) {
						fill_attrvalues(HTML_doctypes[dtdindex].elements,
										previoustag, previousattr, "\"",
										"\"");
					}
					break;
				}
			if (clist && clist->rows != 0)
				make_autocomplet_window(main_v);
		}
	}
	return FALSE;
}

#endif
