/******************************************************************************
*   TinTin++                                                                  *
*   Copyright (C) 2004 (See CREDITS file)                                     *
*                                                                             *
*   This program is protected under the GNU GPL (See COPYING)                 *
*                                                                             *
*   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 *
*******************************************************************************/

/******************************************************************************
*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
*                                                                             *
*                         coded by Peter Unold 1992                           *
*                     recoded by Igor van den Hoven 2004                      *
******************************************************************************/

#include "tintin.h"


struct listroot *init_list(struct session *ses, int type, int size)
{
	struct listroot *listhead;

	if ((listhead = (struct listroot *) calloc(1, sizeof(struct listroot))) == NULL)
	{
		fprintf(stderr, "couldn't alloc listhead\n");
		exit(1);
	}

	listhead->ses  = ses;
	listhead->list = (struct listnode **) calloc(size, sizeof(struct listnode *));
	listhead->size = size;
	listhead->type = type;

	listhead->flags = list_table[type].flags;

	return listhead;
}


void kill_list(struct listroot *root)
{
	while (root->used)
	{
		delete_index_list(root, root->used - 1);
	}
}

void free_list(struct listroot *root)
{
	kill_list(root);

	free(root->list);

	free(root);
}

struct listroot *copy_list(struct session *ses, struct listroot *sourcelist, int type)
{
	int i;
	struct listnode *node;

	push_call("copy_list(%p,%p,%p)",ses,sourcelist,type);

	ses->list[type] = init_list(ses, type, sourcelist->size);

	if (HAS_BIT(sourcelist->flags, LIST_FLAG_INHERIT))
	{
		for (i = 0 ; i < sourcelist->used ; i++)
		{
			node = (struct listnode *) calloc(1, sizeof(struct listnode));

			node->left  = strdup(sourcelist->list[i]->left);
			node->right = strdup(sourcelist->list[i]->right);
			node->pr    = strdup(sourcelist->list[i]->pr);
			node->group = strdup(sourcelist->list[i]->group);

			switch (type)
			{
				case LIST_ALIAS:
					node->regex = tintin_regexp_compile(node, node->left, PCRE_ANCHORED);
					break;

				case LIST_ACTION:
				case LIST_GAG:
				case LIST_HIGHLIGHT:
				case LIST_PROMPT:
				case LIST_SUBSTITUTE:
					node->regex = tintin_regexp_compile(node, node->left, 0);
					break;

				case LIST_VARIABLE:
					copy_nest_node(ses->list[type], node, sourcelist->list[i]);
					break;
			}
			ses->list[type]->list[i] = node;
		}
		ses->list[type]->used = sourcelist->used;
	}
	ses->list[type]->flags = sourcelist->flags;

	pop_call();
	return ses->list[type];
}

/*
	create a node and stuff it into the list in the desired order
*/

struct listnode *insert_node_list(struct listroot *root, char *ltext, char *rtext, char *prtext)
{
	int index;
	struct listnode *node;

	node = (struct listnode *) calloc(1, sizeof(struct listnode));

	node->left  = strdup(ltext);
	node->right = strdup(rtext);
	node->pr    = strdup(prtext);

	node->group = HAS_BIT(root->flags, LIST_FLAG_CLASS) ? strdup(root->ses->group) : strdup("");

	switch (root->type)
	{
		case LIST_ALIAS:
			node->regex = tintin_regexp_compile(node, node->left, PCRE_ANCHORED);
			break;

		case LIST_ACTION:
		case LIST_GAG:
		case LIST_HIGHLIGHT:
		case LIST_PROMPT:
		case LIST_SUBSTITUTE:
			node->regex = tintin_regexp_compile(node, node->left, 0);
			break;
	}

	index = locate_index_list(root, ltext, prtext);

	return insert_index_list(root, node, index);
}


struct listnode *update_node_list(struct listroot *root, char *ltext, char *rtext, char *prtext)
{
	int index;
	struct listnode *node;

	index = search_index_list(root, ltext, NULL);

	if (index != -1)
	{
		node = root->list[index];

		if (strcmp(node->right, rtext) != 0)
		{
			free(node->right);
			node->right = strdup(rtext);
		}

		node->data = 0;

		switch (list_table[root->type].mode)
		{
			case PRIORITY:
				if (atof(node->pr) != atof(prtext))
				{
					delete_index_list(root, index);
					return insert_node_list(root, ltext, rtext, prtext);
				}
				break;

			case APPEND:
				delete_index_list(root, index);
				return insert_node_list(root, ltext, rtext, prtext);
				break;

			case ALPHA:
				if (strcmp(node->pr, prtext) != 0)
				{
					free(node->pr);
					node->pr = strdup(prtext);
				}
				break;

			default:
				tintin_printf2(root->ses, "#BUG: update_node_list: unknown mode: %d", list_table[root->type].mode);
				break;
		}
		return node;
	}
	else
	{
		return insert_node_list(root, ltext, rtext, prtext);
	}
}

struct listnode *insert_index_list(struct listroot *root, struct listnode *node, int index)
{
	root->used++;

	if (root->used == root->size)
	{
		root->size *= 2;

		root->list = (struct listnode **) realloc(root->list, (root->size) * sizeof(struct listnode *));
	}

	memmove(&root->list[index + 1], &root->list[index], (root->used - index) * sizeof(struct listnode *));

	root->list[index] = node;

	return node;
}

void delete_node_list(struct session *ses, int type, struct listnode *node)
{
	int index = search_index_list(ses->list[type], node->left, node->pr);

	delete_index_list(ses->list[type], index);
}

void delete_index_list(struct listroot *root, int index)
{
	struct listnode *node = root->list[index];

	if (node->root)
	{
		free_list(node->root);
	}

	if (index <= root->update)
	{
		root->update--;
	}

	free(node->left);
	free(node->right);
	free(node->pr);
	free(node->group);

	if (node->regex)
	{
		free(node->regex);
	}
	free(node);

	memmove(&root->list[index], &root->list[index + 1], (root->used - index) * sizeof(struct listnode *));

	root->used--;

	return;
}

struct listnode *search_node_list(struct listroot *root, char *text)
{
	int index;

	switch (list_table[root->type].mode)
	{
		case ALPHA:
			index = bsearch_alpha_list(root, text, 0);
			break;

		default:
			index = nsearch_list(root, text);
			break;
	}

	if (index != -1)
	{
		return root->list[index];
	}
	else
	{
		return NULL;
	}
}

int search_index_list(struct listroot *root, char *text, char *priority)
{
	if (list_table[root->type].mode == ALPHA)
	{
		return bsearch_alpha_list(root, text, 0);
	}

	if (list_table[root->type].mode == PRIORITY && priority)
	{
		return bsearch_priority_list(root, text, priority, 0);
	}

	return nsearch_list(root, text);
}
 
/*
	Return insertion index.
*/

int locate_index_list(struct listroot *root, char *text, char *priority)
{
	switch (list_table[root->type].mode)
	{
		case ALPHA:
			return bsearch_alpha_list(root, text, 1);

		case PRIORITY:
			return bsearch_priority_list(root, text, priority, 1);

		default:
			return root->used;
	}
}

/*
	Yup, all this for a bloody binary search.
*/

int bsearch_alpha_list(struct listroot *root, char *text, int seek)
{
	long long bot, top, val;
	double toi, toj, srt;

	bot = 0;
	top = root->used - 1;
	val = top;
	toi = get_number(root->ses, text);

	if (!seek && toi && (*text == '+' || *text == '-') && HAS_BIT(list_table[root->type].flags, LIST_FLAG_NEST))
	{
		if (toi > 0 && toi <= root->used)
		{
			return toi - 1;
		}
		if (toi < 0 && toi + root->used >= 0)
		{
			return root->used + toi;
		}
		else
		{
			return -1;
		}
	}

	while (bot <= top)
	{
		toj = get_number(root->ses, root->list[val]->left);

		if (toi)
		{
			srt = toi - toj;
		}
		else if (toj)
		{
			srt = -1;
		}
		else
		{
			srt = strcmp(text, root->list[val]->left);
		}

		if (srt == 0)
		{
			return val;
		}

		if (srt < 0)
		{
			top = val - 1;
		}
		else
		{
			bot = val + 1;
		}

		val = bot + (top - bot) / 2;
	}

	if (seek)
	{
		return UMAX(0, val);
	}
	else
	{
		return -1;
	}
}

int bsearch_priority_list(struct listroot *root, char *text, char *priority, int seek)
{
	int bot, top, val;
	double srt;

	bot = 0;
	top = root->used - 1;
	val = top;

	while (bot <= top)
	{
		srt = atof(priority) - atof(root->list[val]->pr);

		if (!srt)
		{
			srt = strcmp(text, root->list[val]->left);
		}

		if (srt == 0)
		{
			return val;
		}

		if (srt < 0)
		{
			top = val - 1;
		}
		else
		{
			bot = val + 1;
		}

		val = bot + (top - bot) / 2;
	}

	if (seek)
	{
		return UMAX(0, val);
	}
	else
	{
		return -1;
	}
}

int nsearch_list(struct listroot *root, char *text)
{
	int i;

	for (i = 0 ; i < root->used ; i++)
	{
		if (!strcmp(text, root->list[i]->left))
		{
			return i;
		}
	}
	return -1;
}

/*
	show contens of a node on screen
*/

void show_node(struct listroot *root, struct listnode *node, int level)
{
	char arg[STRING_SIZE], buf[STRING_SIZE], out[STRING_SIZE];

	show_nest_node(node, arg, TRUE);

	switch (list_table[root->type].args)
	{
		case 3:
			sprintf(buf, "%*s#%s <118>{<088>%s<118>}<168>=<118>{<088>%s<118>} <168>@ <118>{<088>%s<118>}", level * 2, "", list_table[root->type].name, node->left, arg, node->pr);
			break;
		case 2:
			sprintf(buf, "%*s#%s <118>{<088>%s<118>}<168>=<118>{<088>%s<118>}", level * 2, "", list_table[root->type].name, node->left, arg);
			break;
		case 1:
			sprintf(buf, "%*s#%s <118>{<088>%s<118>}", level * 2, "", list_table[root->type].name, node->left);
			break;
		default:
			sprintf(buf, "#BUG: list_table[type].args == 0");
			break;
	}

	substitute(root->ses, buf, out, SUB_COL);

	tintin_printf2(root->ses, "%s", out);
}

/*
	list contens of a list on screen
*/

void show_list(struct listroot *root, int level)
{
	int i;

	if (root == root->ses->list[root->type])
	{
		tintin_header(root->ses, " %s ", list_table[root->type].name_multi);
	}

	for (i = 0 ; i < root->used ; i++)
	{
		show_node(root, root->list[i], level);
	}
}


int show_node_with_wild(struct session *ses, char *text, int type)
{
	struct listroot *root = ses->list[type];
	struct listnode *node;
	int i, flag = FALSE;

	node = search_node_list(root, text);

	if (node)
	{
		show_node(root, node, 0);

		return TRUE;
	}

	for (i = 0 ; i < root->used ; i++)
	{
		if (match(NULL, root->list[i]->left, text))
		{
			show_node(root, root->list[i], 0);

			flag = TRUE;
		}
	}
	return flag;
}


void delete_node_with_wild(struct session *ses, int type, char *text)
{
	struct listroot *root = ses->list[type];
	struct listnode *node;
	char arg1[BUFFER_SIZE];
	int i, found = FALSE;

	sub_arg_in_braces(ses, text, arg1, 1, SUB_VAR|SUB_FUN);

	node = search_node_list(root, arg1);

	if (node)
	{
		show_message(ses, type, "#OK. {%s} IS NO LONGER %s %s.", node->left, (*list_table[type].name == 'A' || *list_table[type].name == 'E') ? "AN" : "A", list_table[type].name);

		delete_node_list(ses, type, node);

		return;
	}

	for (i = root->used - 1 ; i >= 0 ; i--)
	{
		if (match(NULL, root->list[i]->left, arg1))
		{
			show_message(ses, type, "#OK. {%s} IS NO LONGER %s %s.", root->list[i]->left, (*list_table[type].name == 'A' || *list_table[type].name == 'E') ? "AN" : "A", list_table[type].name);

			delete_index_list(root, i);

			found = TRUE;
		}
	}

	if (found == 0)
	{
		show_message(ses, type, "#KILL: NO MATCHES FOUND FOR %s {%s}.", list_table[type].name, arg1);
	}
}


DO_COMMAND(do_kill)
{
	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
	int index;

	arg = get_arg_in_braces(arg, arg1, 0);
	      get_arg_in_braces(arg, arg2, 1);

	if (*arg1 == 0 || !strcasecmp(arg1, "ALL"))
	{
		for (index = 0 ; index < LIST_MAX ; index++)
		{
			kill_list(ses->list[index]);
		}
		tintin_printf(ses, "#KILL - ALL LISTS CLEARED.");

		return ses;
	}

	for (index = 0 ; index < LIST_MAX ; index++)
	{
		if (!is_abbrev(arg1, list_table[index].name_multi))
		{
			continue;
		}

		if (*arg2 == 0 || !strcasecmp(arg2, "ALL"))
		{
			kill_list(ses->list[index]);

			tintin_printf(ses, "#OK: #%s LIST CLEARED.", list_table[index].name);
		}
		else
		{
			delete_node_with_wild(ses, index, arg);
		}
		break;
	}

	if (index == LIST_MAX)
	{
		tintin_printf(ses, "#ERROR: #KILL {%s} {%s} - NO MATCH FOUND.", arg1, arg2);
	}
	return ses;
}

DO_COMMAND(do_message)
{
	char left[BUFFER_SIZE], right[BUFFER_SIZE];
	int index, found = FALSE;

	arg = get_arg_in_braces(arg, left,  FALSE);
	arg = get_arg_in_braces(arg, right, FALSE);

	if (*left == 0)
	{
		tintin_header(ses, " MESSAGES ");

		for (index = 0 ; index < LIST_MAX ; index++)
		{
			tintin_printf2(ses, "  %-20s %3s", list_table[index].name_multi, HAS_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE) ? "ON" : "OFF");
		}

		tintin_header(ses, "");
	}
	else
	{
		for (index = found = 0 ; index < LIST_MAX ; index++)
		{
			if (!is_abbrev(left, list_table[index].name_multi) && strcasecmp(left, "ALL"))
			{
				continue;
			}
			if (*right == 0)
			{
				TOG_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE);
			}
			else if (is_abbrev(right, "ON"))
			{
				SET_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE);
			}
			else if (is_abbrev(right, "OFF"))
			{
				DEL_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE);
			}
			else
			{
				tintin_printf(ses, "#SYNTAX: #MESSAGE [NAME] [ON|OFF]");
				break;
			}
			tintin_printf(ses, "#OK: #%s MESSAGES HAVE BEEN SET TO: %s.", list_table[index].name, HAS_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE) ? "ON" : "OFF");
			found = TRUE;
		}

		if (found == FALSE)
		{
			tintin_printf(ses, "#ERROR: #MESSAGE {%s} - NO MATCH FOUND.", left);
		}
	}
	return ses;
}


DO_COMMAND(do_ignore)
{
	char left[BUFFER_SIZE], right[BUFFER_SIZE];
	int index, found = FALSE;

	arg = get_arg_in_braces(arg, left,  0);
	arg = get_arg_in_braces(arg, right, 0);

	if (*left == 0)
	{
		tintin_header(ses, " IGNORES ");

		for (index = 0 ; index < LIST_MAX ; index++)
		{
			tintin_printf2(ses, "  %-20s %3s", list_table[index].name_multi, HAS_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE) ? "ON" : "OFF");
		}

		tintin_header(ses, "");
	}
	else
	{
		for (index = found = 0 ; index < LIST_MAX ; index++)
		{
			if (!is_abbrev(left, list_table[index].name_multi) && strcasecmp(left, "ALL"))
			{
				continue;
			}
			if (*right == 0)
			{
				TOG_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE);
			}
			else if (is_abbrev(right, "ON"))
			{
				SET_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE);
			}
			else if (is_abbrev(right, "OFF"))
			{
				DEL_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE);
			}
			else
			{
				tintin_printf(ses, "#SYNTAX: #IGNORE [NAME] [ON|OFF]");
				break;
			}
			tintin_printf(ses, "#OK: #%s IGNORE STATUS HAS BEEN SET TO: %s.", list_table[index].name, HAS_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE) ? "ON" : "OFF");
			found = TRUE;
		}

		if (found == FALSE)
		{
			tintin_printf(ses, "#ERROR: #IGNORE {%s} - NO MATCH FOUND.", left);
		}
	}
	return ses;
}


DO_COMMAND(do_debug)
{
	char left[BUFFER_SIZE], right[BUFFER_SIZE];
	int index, found = FALSE;

	arg = get_arg_in_braces(arg, left,  0);
	arg = get_arg_in_braces(arg, right, 0);

	if (*left == 0)
	{
		tintin_header(ses, " DEBUGS ");

		for (index = 0 ; index < LIST_MAX ; index++)
		{
			tintin_printf2(ses, "  %-20s %3s", list_table[index].name_multi, HAS_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG) ? "ON" : "OFF");
		}

		tintin_header(ses, "");
	}
	else
	{
		for (index = found = 0 ; index < LIST_MAX ; index++)
		{
			if (!is_abbrev(left, list_table[index].name_multi) && strcasecmp(left, "ALL"))
			{
				continue;
			}
			if (*right == 0)
			{
				TOG_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG);
			}
			else if (is_abbrev(right, "ON"))
			{
				SET_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG);
			}
			else if (is_abbrev(right, "OFF"))
			{
				DEL_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG);
			}
			else if (is_abbrev(right, "LOG"))
			{
				SET_BIT(ses->list[index]->flags, LIST_FLAG_LOG);
			}
			else
			{
				tintin_printf(ses, "#SYNTAX: #DEBUG [NAME] [ON|OFF|LOG]");
				break;
			}
			tintin_printf(ses, "#OK: #%s DEBUG STATUS HAS BEEN SET TO: %s.", list_table[index].name, is_abbrev(right, "LOG") ? "LOG" : HAS_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG) ? "ON" : "OFF");
			found = TRUE;
		}

		if (found == FALSE)
		{
			tintin_printf2(ses, "#DEBUG {%s} - NO MATCH FOUND.", left);
		}
	}
	return ses;
}