/* $Id: e2_complete.c 534 2007-07-16 23:50:46Z tpgww $

Copyright (C) 2004-2007 tooar <tooar@gmx.net>

This file is part of emelFM2.
emelFM2 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, or (at your option)
any later version.

emelFM2 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 emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file src/command/complete/e2_complete.c
@brief string auto completion

This file contains functions to auto-complete strings, e.g. in a command
line or dir line. It has a registry which makes it possible to add new
completion checks. The type of the completion is then choosen from the
registry when completing strings.
*/

#include "e2_complete.h"
#include <string.h>

static GList *checks = NULL;

  /*****************/
 /***** utils *****/
/*****************/

/**
@brief check whether a competion of the kind associated with @a rt is applicable, for the current competion request
@param flags bitflags in accord with the type(s) of competion to be done for the current request
@param rt pointer to E2_CompleteRuntime data struct for a registered completion check
@return TRUE if @a rt relates to a completion that is to be used in this completion request
*/
/*static gboolean _e2_complete_should_check (E2_CompleteFlags flags, E2_CompleteRuntime *rt)
{
	return (flags & rt->flags);
}*/
/**
@brief find the word in @a line that should be completed
this scans @a line backward from cursor position, looking for
the most recent non-escaped ' ' character
*word is set to a copy of @a line after that ' ', up to the cursor position.
(if cursor is at the beginning of @a line, *word is set to a copy of "")
@param line utf8 string containing the line to be checked
@param pos the current cursor position (characters) in @a line
@param word pointer set to the word
@return a copy of the part of @a line before *word, copy of "" if cursor is at start of @a line or no space before cursor
*/
static gchar *_e2_complete_find_word (const gchar *line, gint pos, gchar **word)
{
	//ignore leading whitespace
	gchar *str = e2_utils_pass_whitespace ((gchar *)line);
	if (str != NULL)
		pos -= (str-line);
	//quick exit if cursor is at the beginning
	if (pos == 0)
	{
		*word = g_strdup ("");
		return g_strdup ("");
	}
	//copy the part of the string to the cursor position
	str = e2_utf8_ndup (str, pos);
	//clear any trailing whitespace too (eg in a mount command)
	gchar *tail = (str + strlen (str) - 1);
	while (*tail == ' ' || *tail == '\t')	//don't bother with utf8 get, whitespace is always ascii
	{
		*tail-- = '\0';
		pos--;
	}

	//get the character offset index in str (line) where the word starts
	gint i;
	//search backwards from the current cursor position for the beginning
	//of the word
	for (i = pos; i > 0; i--)
	{
		gunichar cur = g_utf8_get_char (g_utf8_offset_to_pointer (str, i));
		gunichar before = g_utf8_get_char (g_utf8_offset_to_pointer (str, i - 1));
		//a non-escaped space character indicates the gap before the word
		if ((cur == ' ') && (before != '\\'))
		{
			i++;
			break;
		}
	}
/* leading spaces now stripped, above
	//in case the word starts at the beginning of the line and the first character
	//is a space, increment i (this case is missed by the backward search above)
	if ((i == 0) && (g_utf8_get_char (str) == ' '))
		i++; */
	//now
//	gchar *tmp = g_strdup (g_utf8_offset_to_pointer (str, i));
	gchar *tmp = g_utf8_offset_to_pointer (str, i);
	*word = e2_utf8_unescape (tmp, ' ');
//	g_free (tmp);
	gchar *retval = e2_utf8_ndup (str, i);
	g_free (str);
	return retval;
}

/**
@brief store data for a string-completion "method"
@param priority priority assigned to the method (not actually used)
@param flags bit-flags indicating type of match(es) etc
@param func the function which does the completion for the method
@param data pointer to data to be sent to @a func
@return
*/
static void _e2_complete_register_method (guint priority, E2_CompleteFlags flags,
//	const gchar *name, gchar *group, gchar *desc, gchar *tip,
	gpointer func, gpointer data)
{
	E2_CompleteRuntime *rt = ALLOCATE (E2_CompleteRuntime);	//FIXME never deallocated
	CHECKALLOCATEDWARN (rt, return);
	rt->priority = priority;
	rt->flags = flags;
//	rt->name = g_strdup (name);   //CHECKME = need to dup when source is runtime-created
//	rt->group = g_strdup (group); //CHECKME used ?
//	rt->desc = g_strdup (desc); UNUSED
//	rt->tip = g_strdup (tip); UNUSED
	rt->func = func;
	rt->data = data;
	checks = g_list_append (checks, rt);
}

  /******************/
 /***** public *****/
/******************/
/**
@brief create sorted list of strings which complete @a complete
@param complete pointer to utf8 string containing the text to be completed
@param pos pointer to 0-based index of cursor position (characters) in @a line
@param found pointer to list of matching strings
@param flags bit-flags indicating type of match(es) etc
@param pane enumerator of pane to be used for getting dir string 1,2,0=current

@return the number of successful matches
*/
gint e2_complete_str (gchar **complete, gint *pos, GList **found,
	E2_CompleteFlags flags, guint pane)
{
	gchar *word;
	gchar *part1 = _e2_complete_find_word (*complete, *pos, &word);
	//part1 will be empty when no word-break is before the cursor

	//this is a hack to get around killing a (u)mount command string with no arg
	gint pos_hack;
	if (strstr (word, "mount") != NULL && *part1 == '\0') //_I(
	{
		g_free (part1);
		part1 = g_strconcat (word, " ", NULL);
		pos_hack = strlen (part1);
	}
	else
		pos_hack = 0;

	//FIXME search on the basis of completion priority ??
	gint (*fun) (gchar *, gchar *, gint, GList **, E2_CompleteFlags *, gpointer, gint);
	gboolean all = flags & E2_COMPLETE_FLAG_ALL;
	gint count = 0;
	GList *member;
	for (member = checks; member != NULL; member = member->next)
	{
		E2_CompleteRuntime *crt = (E2_CompleteRuntime *)member->data;
		if (all || (flags & crt->flags))
		{
			fun = crt->func;
			gint cur = fun (*complete, word, *pos, found, &flags, crt->data, pane);
			count += cur;
			if (flags & E2_COMPLETE_FLAG_STOP)
				break;
		}
	}

	if (*found != NULL)
	{
		gchar *repl = NULL;
		gchar *part2 = g_utf8_offset_to_pointer (*complete, *pos);
		gint common = 0;
		if ((*found)->next != NULL)
		{
			repl = g_strdup ((*found)->data);
			gint len1 = g_utf8_strlen ((*found)->data, -1);
			gint common = len1;
			for (member = (*found)->next; member != NULL; member = member->next)
			{
				gint len2 = g_utf8_strlen (member->data, -1);
				gint min = MIN (len1, len2);
				if (common > min)
					common = min;
				gint i;
				for (i = 0; i < min; i++)
				{
					if ((((gchar *) (*found)->data)[i]) != (((gchar *) member->data)[i]))
						break;
				}
				if (common > i)
					common = i;
			}
			if (common == 0)
			{
				g_free (repl);
				repl = NULL;
			}
			else
				repl[common] = '\0';
		} else
		{
			repl = g_strdup ((*found)->data);
		}
		if (repl != NULL)
		{
			gchar *tmp = e2_utf8_escape (repl, ' ');
			gchar *comp_new = g_strconcat (part1, tmp, part2, NULL);
			*pos += g_utf8_strlen (tmp, -1) - g_utf8_strlen (word, -1) + pos_hack; //for [u]mount command
			g_free (tmp);
			g_free (*complete);
			*complete = comp_new;
			if (common != 0)
				repl[common] = ' ';
			g_free (repl);
		}
	}

	if (count > 1)
		*found = g_list_sort (*found, (GCompareFunc) g_utf8_collate);

	g_free (part1);
	g_free (word);
	return count;
}
/**
@brief initialize the auto completion system

These registrations are not config options, and do not appear in the
config dialog, or anywhere else in the UI. They are not really actions, either

@return
*/
void e2_complete_init (void)
{
	RUN_ONCE_CHECK ();

	checks = NULL;

	//register default methods
	/*NOTE that the order of registration is relevant to option
	 E2_COMPLETE_FLAG_STOP which can be set at any time
	 These are registered in priority order, tho' priority is not explicitly
	 examined, now */
#ifdef E2_FS_MOUNTABLE
	_e2_complete_register_method (5, E2_COMPLETE_FLAG_MOUNT,
//	"filesystems", "default", NULL, _("complete filesystems in (u)mount commands"), UNUSED
		e2_complete_mount, NULL);
#endif
	_e2_complete_register_method (10, E2_COMPLETE_FLAG_PATH,
//	"executables from path", "default", NULL, _("search active directory or PATH for matching items"),  UNUSED
		e2_complete_path, NULL);
	_e2_complete_register_method (100, E2_COMPLETE_FLAG_FILES | E2_COMPLETE_FLAG_DIRS,
// "files & directories", "default", NULL, _("complete file and directory names"), UNUSED
		e2_complete_files, NULL);
}
/**
@brief cleanup auto completion data

@return
*/
void e2_complete_clear (void)
{
	GList *member;
	for (member = checks; member != NULL; member = member->next)
		DEALLOCATE (E2_CompleteRuntime, (E2_CompleteRuntime *)member->data);
}
