#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "text.h"
#include "actors.h"
#include "asc.h"
#include "buddy.h"
#include "chat.h"
#include "console.h"
#include "consolewin.h"
#include "errors.h"
#include "filter.h"
#include "gl_init.h"
#include "global.h"
#include "hud.h"
#include "init.h"
#include "lights.h"
#include "misc.h"
#include "multiplayer.h"
#include "pm_log.h"
#include "translate.h"
#include "astrology.h"
#include "url.h"
#include "counters.h"
#include "io/elpathwrapper.h"
#ifdef SKY_FPV
#include "sky.h"
#endif // SKY_FPV
#ifdef NEW_SOUND
#include "sound.h"
#endif // NEW_SOUND
#ifdef EMOTES
#include "actor_scripts.h"
#endif // EMOTES

text_message display_text_buffer[DISPLAY_TEXT_BUFFER_SIZE];
int last_message = -1;
int buffer_full = 0;
int total_nr_lines = 0;
Uint8 current_filter = FILTER_ALL;

int console_msg_nr = 0;
int console_msg_offset = 0;

text_message input_text_line;

char last_pm_from[32];

Uint32 last_server_message_time;
int lines_to_show=0;

char not_from_the_end_console=0;

int log_chat = LOG_SERVER;

float	chat_zoom=1.0;
FILE	*chat_log=NULL;
FILE	*srv_log=NULL;

ec_reference harvesting_effect_reference = NULL;

/* forward declaration */
void put_small_colored_text_in_box (Uint8 color, const Uint8 *text_to_add, int len, int pixels_limit, char *buffer);

void alloc_text_message_data (text_message *msg, int size)
{
	msg->data = size > 0 ? calloc (size, 1) : NULL;
	msg->size = size;
	msg->len = 0;
}

void resize_text_message_data (text_message *msg, int len)
{
	if (msg->size <= len)
	{
		int nsize = msg->size ? 2 * msg->size : len+1;
		while (nsize < len)
			nsize += nsize;
		msg->data = realloc (msg->data, nsize);
		msg->size = nsize;
        }
}

void set_text_message_data (text_message *msg, const char* data)
{
	if (data == NULL || data[0] == '\0')
	{
		clear_text_message_data (msg);
	}
	else if (msg->size > 0)
	{
		safe_strncpy (msg->data, data, msg->size);
		msg->len = strlen (msg->data);
        }
}

void init_text_buffers ()
{
	int i;

	for (i = 0; i < DISPLAY_TEXT_BUFFER_SIZE; i++)
		init_text_message (display_text_buffer + i, 0);

	init_text_message (&input_text_line, MAX_TEXT_MESSAGE_LENGTH + 1);
	input_text_line.chan_idx = CHAT_ALL;
	set_text_message_color (&input_text_line, 1.0f, 1.0f, 1.0f);
}

void cleanup_text_buffers(void)
{
	int i;

	free_text_message_data (&input_text_line);
	for(i = 0; i < DISPLAY_TEXT_BUFFER_SIZE; i++)
		free_text_message_data (display_text_buffer + i);
}

void update_text_windows (text_message * pmsg)
{
	if (console_root_win >= 0) update_console_win (pmsg);
	switch (use_windowed_chat) {
		case 0:
			rewrap_message(pmsg, chat_zoom, console_text_width, NULL);
			lines_to_show += pmsg->wrap_lines;
			if (lines_to_show > 10) lines_to_show = 10;
			break;
		case 1:
			update_tab_bar (pmsg);
			break;
		case 2:
			update_chat_window (pmsg, 1);
			break;
	}
}

void open_chat_log(){
	char starttime[200], sttime[200];
	struct tm *l_time; time_t c_time;

	chat_log = open_file_config ("chat_log.txt", "a");
	if (log_chat == LOG_SERVER || log_chat == LOG_SERVER_SEPERATE)
		srv_log = open_file_config ("srv_log.txt", "a");

	if (chat_log == NULL)
	{
		LOG_TO_CONSOLE(c_red3, "Unable to open log file to write. We will NOT be recording anything.");
		log_chat = LOG_NONE;
		return;
	}
	else if ((log_chat == LOG_SERVER || log_chat == LOG_SERVER_SEPERATE) && srv_log == NULL)
	{
		LOG_TO_CONSOLE(c_red3, "Unable to open server log file to write. We will fall back to recording everything in chat_log.txt.");
		log_chat = LOG_CHAT;
		return;
	}
	time(&c_time);
	l_time = localtime(&c_time);
	strftime(sttime, sizeof(sttime), "\n\nLog started at %Y-%m-%d %H:%M:%S localtime", l_time);
	safe_snprintf(starttime, sizeof(starttime), "%s (%s)\n\n", sttime, tzname[l_time->tm_isdst>0]);
	fwrite (starttime, strlen(starttime), 1, chat_log);
}


void timestamp_chat_log(){
	char starttime[200], sttime[200];
	struct tm *l_time; time_t c_time;

	if(log_chat == LOG_NONE) {
		return; //we're not logging anything
	}

	if (chat_log == NULL){
		open_chat_log();
	} else {
		time(&c_time);
		l_time = localtime(&c_time);
		strftime(sttime, sizeof(sttime), "Hourly time-stamp: log continued at %Y-%m-%d %H:%M:%S localtime", l_time);
		safe_snprintf(starttime, sizeof(starttime), "%s (%s)\n", sttime, tzname[l_time->tm_isdst>0]);
		fwrite (starttime, strlen(starttime), 1, chat_log);
	}
}


void write_to_log (Uint8 channel, const Uint8* const data, int len)
{
	int i, j;
	Uint8 ch;
	char str[1024];
	struct tm *l_time; time_t c_time;
	FILE *fout;

	if(log_chat == LOG_NONE || (channel == CHAT_SERVER && log_chat == LOG_CHAT))
		// We're not logging at all, or this is a server message and
		// we're not logging those
		return;

	if (chat_log == NULL){
		open_chat_log();
		if(chat_log == NULL){
			return;
		}
	}

	// The file we'll write to
	fout = (channel == CHAT_SERVER && log_chat >= 3) ? srv_log : chat_log;

	// Start filling the buffer with the time stamp
	time (&c_time);
	l_time = localtime (&c_time);
	j = strftime (str, sizeof(str), "[%H:%M:%S] ", l_time);

	i = 0;
	while (i < len)
	{
		for ( ; i < len && j < sizeof (str) - 1; i++)
		{
			ch = data[i];

			// remove colorization and soft wrapping characters when
			// writing to the chat log
			if (!is_color (ch) && ch != '\r')
				str[j++] = ch;
		}
		if (i >= len) str[j++]='\n';

		fwrite (str, j, 1, fout);

		// start again at the beginning of the buffer
		j = 0;
	}

	// Flush the file, so the content is written even when EL crashes.
	fflush (fout);
}

void send_input_text_line (char *line, int line_len)
{
	char str[256];
	int i,j;
	int len;
	Uint8 ch;

	switch(use_windowed_chat)
	{
		case 1:
			if(tabs[current_tab].channel != CHAT_ALL) {
				change_to_current_tab(line);
			}
		break;
		case 2:
			if(channels[active_tab].chan_nr != CHAT_ALL) {
				change_to_current_chat_tab(line);
			}
		break;
	}

	if ( caps_filter && line_len > 4 && my_isupper (line, -1) )
		my_tolower (line);

	i=0;
	j=1;
	if (line[0] != '/' && line[0] != char_slash_str[0])	//we don't have a PM
	{
		str[0] = RAW_TEXT;
	}
	else
	{
		str[0] = SEND_PM;
		i++;	// skip the leading /
	}

	for ( ; i < line_len && j < sizeof (str) - 1; i++)	// copy it, but ignore the enter
	{
		ch = line[i];
		if (ch != '\n' && ch != '\r')
		{
			str[j] = ch;
			j++;
		}
	}
	str[j] = 0;	// always a NULL at the end

	len = strlen (&str[1]);
	if (my_tcp_send (my_socket, (Uint8*)str, len+1) < len+1)
	{
		//we got a nasty error, log it
	}

	return;
}

#ifdef EMOTES
int match_emote(const char *command, const char *name)
{
	int i;
	actor *act = NULL;
	emote_types *this_emote = emotes;
	
	// Get the actor_type for the input player
	LOCK_ACTORS_LISTS();
	for (i = 0; i < max_actors; i++)
	{
		if (!strncasecmp(actors_list[i]->actor_name, name, strlen(name)) && (actors_list[i]->actor_name[strlen(name)] == ' ' || actors_list[i]->actor_name[strlen(name)] == '\0'))
		{
			act = actors_list[i];
			break;
		}
	}
	if (!act)
	{
		UNLOCK_ACTORS_LISTS();
		LOG_ERROR("Unable to find actor who just said local text?? name: %s", name);
		return 0;		// Eek! We don't have an actor match... o.O
	}
	
	// Try to match the input against an emote command and actor type
	for (; this_emote; this_emote = this_emote->next)
	{
		if ((this_emote->actor_type == -1 || this_emote->actor_type == act->actor_type) && !strcasecmp(command, this_emote->command))
		{
			add_emote_command_to_actor(act, this_emote->id);
			UNLOCK_ACTORS_LISTS();
			return 1;
		}
	}
	UNLOCK_ACTORS_LISTS();
	return 0;
}

int test_for_emote(const char *text, int len)
{
	int start, end;
	start = get_string_occurance("*", text, len, 1);
	if (start > -1)
	{
		end = get_string_occurance("*", text+start+1, len, 1) + start + 1;
		if (end > -1 && end - start >= 1)
		{
			return 1;
		}
	}
	return 0;
}

void parse_text_for_emote_commands(const char *text, int len)
{
	int i, j = 0, stage = 0;
	char username[20];	// Yeah, this should be done correctly
	char emote_text[MAX_EMOTE_LEN];

	if (test_for_emote(text, len))
	{
		// It is worthwile us wasting some time looping through this text looking for an emote to match
		i = -1;		// Start at -1 because of preincrementing i
		while (text[i+1] != '\0')
		{
			i++;
			if (is_color(text[i]))
				continue;		// Ignore colour chars
			if (stage == 0)		// Firstly, find the name of the player
			{
				if (j >= 20)
					break;		// Give up as this isn't a valid username
				if (text[i] == ':')
				{
					username[j] = '\0';
					stage++;
				}
				else
				{
					username[j] = text[i];
					j++;
				}
			}
			else if (stage == 1)		// Find the start of the emote
			{
				if (text[i] == '*')
				{
					j = 0;
					stage++;
				}
			}
			else if (stage == 2)		// Find the contents of the emote
			{
				if (text[i] == '*')
				{
					// Found something valid so check if it matches
					emote_text[j] = '\0';
					if (match_emote(emote_text, username))
					{
						// Check the remainder of the string for another emote (starting *after* this *)
						if (test_for_emote(text+i+1, len - i - 1))
						{
							j = 0;
							stage = 1;
							continue;	// This looks like another emote so keep going through the loop
						}
					}
				}
				else if (text[i] != ' ' && j < MAX_EMOTE_LEN)
				{
					emote_text[j] = text[i];
					j++;
					continue;		// Loop so we don't get to the check for another emote
				}
				// We didn't find an emote so check the remainder of the string (using this "end" * as the start)
				if (test_for_emote(text+i, len - i - 1))
				{
					i--;		// i will be incremented at the start of the next loop which would miss this *
					j = 0;
					stage = 1;
					continue;	// This looks like another emote so keep going through the loop
				}
				else
					break;
			}
		}
	}
}
#endif // EMOTES

int filter_or_ignore_text (char *text_to_add, int len, int size, Uint8 channel)
{
	int l, idx;

	if (len <= 0) return 0;	// no point

#ifdef NEW_SOUND
	// Check if this string matches text we play a sound for
	check_sound_alerts(text_to_add, channel);
#endif // NEW_SOUND

	//check for auto receiving #help
	for (idx = 0; idx < len; idx++)
	{
		if (!is_color (text_to_add[idx])) break;
	}
	l = len - idx;
	if (l >= strlen(help_request_str) && text_to_add[idx] == '#' && (strncasecmp (&text_to_add[idx], help_request_str, strlen(help_request_str)) == 0 || strncasecmp (&text_to_add[idx], "#mod chat", 9) == 0))
	{
		auto_open_encyclopedia = 0;
	}

	/*
	DANGER, WILL ROBINSON!

	The below code should not exist in it's present form.  I'd change it,
	but I'd need access to the server.  Simply checking text output (which
	is used for all sorts of things) for the phrase "Game Date" is very
	dangerous.  Example: what if, in the future, we allow spaces in
	character names?  Someone chooses the name "Game Date" and walks around
	saying "hi".  Everyone's clients in the area interpret this as being a
	Game Date command.

	I've made the below code not *as* dangerous. Had a user been able to
	fake out the below code, previously, it would have caused a buffer overflow
	in their client if they didn't write in only numbers after it.  Now, they
	won't crash; it'll just be misparsed.

	General practice recommendation: don't mix server commands with user
	input.

	 - Karen
	*/
	/*
	ed (ttlanhil): made it check if it's a server colour. still not perfect
	(this should have been done server-side instead of parsing the date), but safer
	*/
	if (from_color_char (text_to_add[0]) == c_green1 && my_strncompare(text_to_add+1,"Game Date", 9))
	{
		//we assume that the server will still send little-endian dd/mm/yyyy... we could make it safer by parsing the format too, but it's simpler to assume
		const char * const month_names[] = { "Aluwia", "Seedar", "Akbar", "Zartia", "Elandra", "Viasia", "Fruitfall", "Mortia", "Carnelar", "Nimlos", "Chimar", "Vespia" };
		const char * const day_names[] = { "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "29th", "30th" };
		char new_str[100];
		const char *ptr=text_to_add;
		short unsigned int day=1, month=1, year=0;
		int offset = 0;

		while(!isdigit(ptr[offset]))
		{
			offset++;
			if (offset >= sizeof(new_str))
			{
				LOG_ERROR("error (1) parsing date string: %s",text_to_add);
				//something evil this way comes...
				return 0;
			}
		}
		ptr += offset;

		if (sscanf (ptr,"%hu%*[-/]%hu%*[-/]%hu",&day,&month,&year) < 3
		    || day <= 0 || month <= 0
		    || day > 30 || month > 12 || year > 9999)
		{
			LOG_ERROR("error (2) parsing date string: %s",text_to_add);
			//something evil this way comes...
		}
		else
		{
			safe_snprintf(new_str, sizeof(new_str), date_format, day_names[day-1], month_names[month-1], year);
			LOG_TO_CONSOLE(c_green1, new_str);
#ifdef SKY_FPV

			//Calculate fraction Big Lunar month (2 conjunction months) less game clock time
			//Represented in Degrees.
			skybox_time_d = (SDL_GetTicks()%( 1296000 * 1000 ));
			skybox_time_d *= 360.0/( 1296000.0 * 1000.0);
			skybox_time_d = -skybox_time_d;
			skybox_time_d += 360.0 * (((month%2)*30 + day-1)*360 + game_minute)/21600.0;
			skybox_update_positions();
#endif // SKY_FPV
			return 0;
		}
	}

#ifdef SKY_FPV
	if (from_color_char (text_to_add[0]) == c_green1 && my_strncompare(text_to_add+1,"Game Time", 9))
	{
		real_game_second = atoi(&text_to_add[18]);
		next_second_time = cur_time + 1000;
        new_second();
	}
#endif // SKY_FPV

#ifdef EMOTES
	// Check for local messages to be translated into actor movements (contains [somthing])
	if (channel == CHAT_LOCAL)
	{
		parse_text_for_emote_commands(text_to_add, len);
	}
#endif // EMOTES

	if (channel == CHAT_SERVER) {
		if (my_strncompare(text_to_add+1, "You started to harvest ", 23)) {
			strncpy(harvest_name, text_to_add+1+23, len-1-23-1);
			harvest_name[len-1-23-1] = '\0';
			harvesting = 1;
			if (use_eye_candy == 1 && use_harvesting_eye_candy == 1)
			{
				if (harvesting_effect_reference == NULL)
				{
					actor *act;
					LOCK_ACTORS_LISTS();
					act = get_actor_ptr_from_id(yourself);
					if (act != NULL)
					{
						harvesting_effect_reference = ec_create_ongoing_harvesting2(act, 1.0, 1.0, (poor_man ? 6 : 10), 1.0);
					}
					UNLOCK_ACTORS_LISTS();
				}
			}
		}
		else if ((my_strncompare(text_to_add+1, "You stopped harvesting.", 23)) ||
			(my_strncompare(text_to_add+1, "You can't harvest while fighting (duh)!", 39)) ||
			(my_strncompare(text_to_add+1, "You can't do that while trading!", 32)) ||
			(my_strncompare(text_to_add+1, "You are too far away! Get closer!", 33)) ||
			(my_strncompare(text_to_add+1, "You can't harvest here", 22)) ||
			((my_strncompare(text_to_add+1, "You need to have a ", 19) && strstr(text_to_add, "order to harvest") != NULL)))
		{
			harvesting = 0;
			if (harvesting_effect_reference != NULL)
			{
				ec_recall_effect(harvesting_effect_reference);
				harvesting_effect_reference = NULL;
			}
		}
		else if (is_death_message(text_to_add+1)) {
			// nothing to be done here cause all is done in the test function
		}
	} else if (channel == CHAT_LOCAL) {
		if (harvesting && my_strncompare(text_to_add+1, username_str, strlen(username_str))) {
			char *ptr = text_to_add+1+strlen(username_str);
			if (my_strncompare(ptr, " found a ", 9)) {
				ptr += 9;
				if (my_strncompare(ptr, "bag of gold, getting ", 21)) {
					decrement_harvest_counter(atoi(ptr+21));
				} else if (!strstr(ptr, " could not carry ")) {
					decrement_harvest_counter(1);
				}
			}
		} else if (my_strncompare(text_to_add+1, "(*) ", 4)) {
			increment_summon_counter(text_to_add+1+4);
		}
	}
	/* check for misc counter strings */
	catch_counters_text(text_to_add+1);

	//Make sure we don't check our own messages.
	if( !(channel == CHAT_PERSONAL && len >= strlen(pm_from_str) && strncasecmp (text_to_add+1, pm_from_str, strlen(pm_from_str)) != 0) &&
		!(channel == CHAT_MODPM && len >= strlen(mod_pm_from_str) && strncasecmp (text_to_add+1, mod_pm_from_str, strlen(mod_pm_from_str)) != 0)
	) {

		//check if ignored - pre_check_if_ignored() checks for Mod PM's etc to not ignore (or it  would be asking for trouble)
		if (pre_check_if_ignored (text_to_add, len, channel))
		{
			return 0;
		}
		//All right, we do not ignore the person
		if (afk)
		{
			if (channel == CHAT_PERSONAL || channel == CHAT_MODPM)
			{
				// player sent us a PM
				add_message_to_pm_log (text_to_add, len, channel);
#ifdef NEW_SOUND
				if (afk_snd_warning) {
					add_sound_object(get_index_for_sound_type_name("AFK Message"), 0, 0, 1);
				}
#endif // NEW_SOUND
			}
			else if (channel == CHAT_LOCAL && from_color_char (text_to_add[0]) == c_grey1 && is_talking_about_me (&text_to_add[1], len-1, 0))
			{
				// player mentions our name in local chat
				if (afk_local) {
					add_message_to_pm_log (&text_to_add[1], len - 1, channel);
#ifdef NEW_SOUND
					if (afk_snd_warning) {
						add_sound_object(get_index_for_sound_type_name("AFK Message"), 0, 0, 1);
					}
#endif // NEW_SOUND
				} else {
					send_afk_message (&text_to_add[1], len - 1, channel);
				}
			}
			else if (channel == CHAT_SERVER)
			{
				// check if this was a trade attempt
				int i;
				for (i = 1; i < len; i++) {
					if (text_to_add[i] == ' ' || text_to_add[i] == ':' || is_color (text_to_add[i])) {
						break;
					}
				}
				if (i < len-15 && strncasecmp (&text_to_add[i], " wants to trade", 15) == 0) {
					send_afk_message (&text_to_add[1], len - 1, channel);
#ifdef NEW_SOUND
					if (afk_snd_warning) {
						add_sound_object(get_index_for_sound_type_name("AFK Message"), 0, 0, 1);
					}
#endif // NEW_SOUND
				}
			}
		}
	} else {	//We sent this PM or MODPM. Can we expect a reply?
		int len = 0;
		char name[MAX_USERNAME_LENGTH];
		for(;text_to_add[len+8] != ':' && len < MAX_USERNAME_LENGTH - 1; ++len);
		safe_strncpy(name, text_to_add+8, len+1);
		if(check_if_ignored(name)){
			char msg[65];
			safe_snprintf(msg, sizeof(msg), warn_currently_ignoring, name);
			LOG_TO_CONSOLE(c_red2, msg);
		}
	}

	// parse for URLs
	find_all_url (text_to_add, len);

	// look for buddy-wants-to-add-you messages
	if(channel == CHAT_SERVER && from_color_char (text_to_add[0]) == c_green1)
	{
		for (l = 1; l < len; l++)
		{
			if (text_to_add[l] == ' ') break;
		}
		if (len - l >= strlen(msg_accept_buddy_str) && strncmp (&text_to_add[l], msg_accept_buddy_str, strlen(msg_accept_buddy_str)) == 0 && l <=32)
		{
			char name[32];
			int i;
			int cur_char;
			/*entropy says: I really fail to understand the logic of that safe_snprintf. And gcc can't understand it either
			  because the name is corrupted. so we implement it the old fashioned way.
			  Grum responds: actually it's the MingW compiler on windows that doesn't understand it, because it doesn't
			  terminate the string when the buffer threatens to overflow. It works fine with gcc on Unix, and using
			  sane_safe_snprintf should also fix it on windows.
			safe_snprintf (name, l, "%s", &text_to_add[1]);
			*/
			for (i = 0; i < sizeof (name); i++)
			{
				cur_char = text_to_add[i+1];
				name[i] = cur_char;
				if (cur_char == ' ')
				{
					name[i] = '\0';
					break;
				}
			}
			add_buddy_confirmation (name);
		}
	}

	// look for astrology messages
	if((channel == CHAT_SERVER) && is_astrology_message (text_to_add))
	{
		return 0;
	}

	// filter any naughty words out
	return filter_text (text_to_add, len, size);
}

void put_text_in_buffer (Uint8 channel, const Uint8 *text_to_add, int len)
{
	put_colored_text_in_buffer (c_grey1, channel, text_to_add, len);
}

//-- Logan Dugenoux [5/26/2004]
// Checks chat string, if it begins with an actor name,
// and the actor is displayed, put said sentence into an overtext bubble
#define ALLOWED_CHAR_IN_NAME(_x_)		(isalnum(_x_)||(_x_=='_'))
void check_chat_text_to_overtext (const Uint8 *text_to_add, int len, Uint8 channel)
{
	if (!view_chat_text_as_overtext || channel != CHAT_LOCAL)
		return;		// disabled

	if (from_color_char (text_to_add[0]) == c_grey1)
	{
		char playerName[128];
		char textbuffer[1024];
		int i;
		int j;

		j = 0;
		i = 1;
		while (text_to_add[i] < 128 && i < len)
		{
			if (text_to_add[i] != '[')
			{
				playerName[j] = (char)text_to_add[i];
				j++;
				if (j >= sizeof (playerName))
					return;//over buffer
			}
			i++;
		}

		if (i < len)
		{
			playerName[j] = '\0';
			while ( j > 0 && !ALLOWED_CHAR_IN_NAME (playerName[j]) )
				playerName[j--] = '\0';
			j = 0;
			while (i < len)
			{
				if ( j >= sizeof (textbuffer) )
					return;//over buffer
				textbuffer[j] = (char) text_to_add[i];
				i++; j++;
			}
			textbuffer[j] = '\0';
			for (i = 0; i < max_actors; i++)
			{
				char actorName[128];
				j = 0;
				// Strip clan info
				while ( ALLOWED_CHAR_IN_NAME (actors_list[i]->actor_name[j]) )
				{
					actorName[j] = actors_list[i]->actor_name[j];
					j++;
					if ( j >= sizeof (actorName) )
						return;	// over buffer
				}
				actorName[j] = '\0';
				if (strcmp (actorName, playerName) == 0)
				{
					add_displayed_text_to_actor (actors_list[i], textbuffer);
					break;
				}
			}
		}
	}
}

int put_char_in_buffer (text_message *buf, Uint8 ch, int pos)
{
	int i, nlen;

	if (pos < 0 || pos > buf->len || pos >= buf->size) {
		return 0;
	}

	// First shift everything after pos to the right
	nlen = buf->len + 1;
	if (nlen >= buf->size)
		nlen = buf->size - 1;
	buf->data[nlen] = '\0';
	for (i = nlen - 1; i > pos; i--)
		buf->data[i] = buf->data[i-1];

	// insert the new character, and update the length
	buf->data[pos] = ch;
	buf->len = nlen;

	return 1;
}

int put_string_in_buffer (text_message *buf, const Uint8 *str, int pos)
{
	int nr_free, nr_paste, ib, jb, nb;
	Uint8 ch;

	if (pos < 0 || pos > buf->len) return 0;
	if (str == NULL) return 0;

	// find out how many characters to paste
	nr_free = buf->size - pos - 1;
	nr_paste = 0;
	for (ib = 0; str[ib] && nr_paste < nr_free; ib++)
	{
		ch = str[ib];
		if (is_printable (ch))
			nr_paste++;
	}

	if (nr_paste == 0) {
		return 0;
	}

	// now move the characters right of the cursor (if any)
	nb = buf->len - pos;
	if (nb > nr_free - nr_paste)
		nb = nr_free - nr_paste;
	if (nb > 0)
	{
		for (ib = nb-1; ib >= 0; ib--)
			buf->data[pos+ib+nr_paste] = buf->data[pos+ib];
	}
	buf->data[pos+nb+nr_paste] = '\0';
	buf->len = pos+nb+nr_paste;

	// insert the pasted text
	jb = 0;
	for (ib = 0; str[ib]; ib++)
	{
		ch = str[ib];
		if (is_printable (ch))
		{
			buf->data[pos+jb] = ch;
			if (++jb >= nr_paste) break;
		}
	}

	return nr_paste;
}

void put_colored_text_in_buffer (Uint8 color, Uint8 channel, const Uint8 *text_to_add, int len)
{
	text_message *msg;
	int minlen;
	Uint32 cnr = 0, ibreak = -1;

	check_chat_text_to_overtext (text_to_add, len, channel);

	// check for auto-length
	if (len < 0)
		len = strlen ((char*)text_to_add);

	// set the time when we got this message
	last_server_message_time = cur_time;

	if (++last_message >= DISPLAY_TEXT_BUFFER_SIZE)
	{
		buffer_full = 1;
		last_message = 0;
	}

	msg = &(display_text_buffer[last_message]);

	if (buffer_full && msg->data) {
		total_nr_lines -= msg->wrap_lines;
		msg->deleted = 1;
		update_text_windows(msg);
		free_text_message_data (msg);
	}

	// Try to make a guess at the number of wrapping newlines required,
	// but allow al least for a null byte and up to 8 extra newlines and
	// colour codes
	minlen = len + 18 + (len/60);
	cnr = get_active_channel (channel);
	if (cnr != 0)
		// allow some space for the channel number
		minlen += 20;
	if (msg->data == NULL)
		alloc_text_message_data (msg, minlen);
	else
		resize_text_message_data (msg, minlen);

	if (cnr != 0)
	{
		for (ibreak = 0; ibreak < len; ibreak++)
		{
			if (text_to_add[ibreak] == ']') break;
		}
	}

	if (ibreak < 0 || ibreak >= len)
	{
		// not a channel, or something's messed up
		if(!is_color (text_to_add[0]))
		{
			// force the color
			safe_snprintf (msg->data, msg->size, "%c%.*s", to_color_char (color), len, text_to_add);
		}
		else
		{
			// color set by server
			safe_snprintf (msg->data, msg->size, "%.*s", len, text_to_add);
		}
	}
	else
	{
		char nr_str[16];
		if (cnr >= 1000000000)
			safe_snprintf (nr_str, sizeof (nr_str), "guild");
		else
			safe_snprintf (nr_str, sizeof (nr_str), "%u", cnr);

		if(!is_color (text_to_add[0]))
		{
			// force the color
			safe_snprintf (msg->data, msg->size, "%c%.*s @ %s%.*s", to_color_char (color), ibreak, text_to_add, nr_str, len-ibreak, &text_to_add[ibreak]);
		}
		else
		{
			// color set by server
			safe_snprintf (msg->data, msg->size, "%.*s @ %s%.*s", ibreak, text_to_add, nr_str, len-ibreak, &text_to_add[ibreak]);
		}
	}

	msg->len = strlen (msg->data);
	msg->chan_idx = channel;
	msg->channel = cnr;

	// set invalid wrap data to force rewrapping
	msg->wrap_lines = 0;
	msg->wrap_zoom = 0.0f;
	msg->wrap_width = 0;

	msg->deleted = 0;
	recolour_message(msg);
	update_text_windows(msg);

	// log the message
	write_to_log (channel, (unsigned char*)msg->data, msg->len);

	return;
}

void put_small_text_in_box (const Uint8 *text_to_add, int len, int pixels_limit, char *buffer)
{
	put_small_colored_text_in_box (c_grey1, text_to_add, len, pixels_limit, buffer);
}

void put_small_colored_text_in_box (Uint8 color, const Uint8 *text_to_add, int len, int pixels_limit, char *buffer)
{
	int i;
	Uint8 cur_char;
	int last_text = 0;
	int x_chars_limit;

	// force the color
	if (!is_color (text_to_add[0]))
		buffer[last_text++] = to_color_char (color);

	//see if the text fits on the screen
	x_chars_limit = pixels_limit / 8;
	if (len <= x_chars_limit)
	{
		for (i = 0; i < len; i++)
		{
			cur_char = text_to_add[i];

			if (cur_char == '\0')
				break;

			buffer[last_text++] = cur_char;
		}
		if (last_text > 0 && buffer[last_text-1] != '\n')
			buffer[last_text++] = '\n';
		buffer[last_text] = '\0';
	}
	else //we have to add new lines to our text...
	{
		int k;
		int new_line_pos = 0;
		char semaphore = 0;
		Uint8 current_color = to_color_char (color);

		// go trought all the text
		for (i = 0; i < len; i++)
		{
			if (!semaphore && new_line_pos + x_chars_limit < len) //don't go through the last line
			{
				//find the closest space from the end of this line
				//if we have one really big word, then parse the string from the
				//end of the line backwards, untill the beginning of the line +2
				//the +2 is so we avoid parsing the ": " thing...
				for (k = new_line_pos + x_chars_limit - 1; k > new_line_pos + 2; k--)
				{
					cur_char = text_to_add[k];
					if (k > len) continue;
					if (cur_char == ' ' || cur_char == '\n')
					{
						k++; // let the space on the previous line
						break;
					}
				}
				if (k == new_line_pos + 2)
					new_line_pos += x_chars_limit;
				else
					new_line_pos = k;
				semaphore = 1;
			}

			cur_char = text_to_add[i];
			if (cur_char == '\0') break;

			if (is_color (cur_char)) // we have a color, save it
			{
				current_color = cur_char;
				if (last_text > 0 && is_color (buffer[last_text-1]))
					last_text--;
			}
			else if (cur_char == '\n')
			{
				new_line_pos = i;
			}

			if (i == new_line_pos)
			{
				buffer[last_text++] = '\n';
				// don't add color codes after the last newline
				if (i < len-1)
					buffer[last_text++] = current_color;
				semaphore = 0;
			}
			//don't add another new line, if the current char is already a new line...
			if (cur_char != '\n')
				buffer[last_text++] = cur_char;

		}
		// don't add extra newlines if there already is one
		if (last_text > 0 && buffer[last_text-1] != '\n')
			buffer[last_text++] = '\n';
		buffer[last_text] = '\0';
	}
}


// find the last lines, according to the current time
int find_last_lines_time (int *msg, int *offset, Uint8 filter, int width)
{
	// adjust the lines_no according to the time elapsed since the last message
	if ( (cur_time - last_server_message_time) / 1000 > 3)
	{
		if (lines_to_show > 0)
			lines_to_show--;
		last_server_message_time = cur_time;
	}
	if (lines_to_show <= 0) return 0;

	return find_line_nr (total_nr_lines, total_nr_lines - lines_to_show, filter, msg, offset, chat_zoom, width);
}

int find_last_console_lines (int lines_no)
{
	return find_line_nr (total_nr_lines, total_nr_lines - lines_no, FILTER_ALL, &console_msg_nr, &console_msg_offset, chat_zoom, console_text_width);
}


int find_line_nr (int nr_lines, int line, Uint8 filter, int *msg, int *offset, float zoom, int width)
{
	int line_count = 0, lines_no = nr_lines - line;
	int imsg, ichar;
	char *data;

	imsg = last_message;
	if ( imsg<0 ) {
		/* No data in buffer */
		*msg = *offset = 0;
		return 1;
	}
	do
	{
		int msgchan = display_text_buffer[imsg].chan_idx;

		switch (msgchan) {
			case CHAT_LOCAL:    if (!local_chat_separate)    msgchan = CHAT_ALL; break;
			case CHAT_PERSONAL: if (!personal_chat_separate) msgchan = CHAT_ALL; break;
			case CHAT_GM:       if (!guild_chat_separate)    msgchan = CHAT_ALL; break;
			case CHAT_SERVER:   if (!server_chat_separate)   msgchan = CHAT_ALL; break;
			case CHAT_MOD:      if (!mod_chat_separate)      msgchan = CHAT_ALL; break;
			case CHAT_MODPM:                                 msgchan = CHAT_ALL; break;
		}

		if (msgchan == filter || msgchan == CHAT_ALL || filter == FILTER_ALL)
		{
			data = display_text_buffer[imsg].data;
			if (data == NULL)
				// Hmmm... we messed up. This should not be
				// happening.
				break;

			rewrap_message(&display_text_buffer[imsg], zoom, width, NULL);

			for (ichar = display_text_buffer[imsg].len - 1; ichar >= 0; ichar--)
			{
				if (data[ichar] == '\n' || data[ichar] == '\r')
				{
					line_count++;
					if (line_count >= lines_no)
					{
						*msg = imsg;
						*offset = ichar+1;
						return 1;
					}
				}
			}

			line_count++;
			if (line_count >= lines_no)
			{
				*msg = imsg;
				*offset = 0;
				return 1;
			}
		}

		if (--imsg < 0 && buffer_full)
			imsg = DISPLAY_TEXT_BUFFER_SIZE - 1;
	} while (imsg >= 0 && imsg != last_message);

	*msg = 0;
	*offset = 0;
	return 1;
}
void console_move_up ()
{
	int nl_found, ichar;
	int max_lines;

	// get the number of lines we have - the last one, which is the
	// command line
	max_lines = (window_height - hud_y) / 18 - 1;
	if (not_from_the_end_console) max_lines--;

	// if we have less lines of text than the max lines onscreen, don't
	// scroll up
	if (total_nr_lines > max_lines)
	{
		not_from_the_end_console = 1;

		nl_found = 0;
		while (1)
		{
			const char *data = display_text_buffer[console_msg_nr].data;
			for (ichar = console_msg_offset; ichar >= 0; ichar--)
			{
				if (data[ichar] == '\n' || data[ichar] == '\r')
				{
					if (nl_found) break;
					nl_found = 1;
				}
			}
			if (nl_found)
			{
				console_msg_offset = ichar + 1;
				return;
			}
			if (--console_msg_nr < 0 && buffer_full)
				console_msg_nr = DISPLAY_TEXT_BUFFER_SIZE - 1;
			nl_found = 1;
		}
	}
}


void console_move_down ()
{
	int ichar;
	int max_lines;
	const char *data = display_text_buffer[console_msg_nr].data;

	if (!not_from_the_end_console)
		// we can't scroll down anymore
		return;

	// get the number of lines we have on screen
	max_lines = (window_height-hud_y)/18 - 1;
	max_lines--;

	for (ichar = console_msg_offset; data[ichar] != '\0'; ichar++)
		if (data[ichar] == '\n' || data[ichar] == '\r')
			break;

	if (data[ichar] == '\n' || data[ichar] == '\r')
	{
		console_msg_offset = ichar + 1;
	}
	else
	{
		if (++console_msg_nr >= DISPLAY_TEXT_BUFFER_SIZE)
			console_msg_nr = 0;
		console_msg_offset = 0;
	}

	if (console_msg_nr == last_message)
	{
		data = display_text_buffer[console_msg_nr].data;
		for (ichar = console_msg_offset; data[ichar] != '\0'; ichar++)
			if (data[ichar] == '\n' || data[ichar] == '\r')
				break;
		if (data[ichar] == '\0')
			not_from_the_end_console = 0;
	}
}

void console_move_page_down()
{
	int max_lines;
	int i;

	max_lines=(window_height-hud_y)/18-3;

	for(i=0;i<max_lines;i++)
		{
			console_move_down();
		}
}

void console_move_page_up()
{
	int max_lines;
	int i;

	max_lines=(window_height-hud_y)/18-3;

	for(i=0;i<max_lines;i++)
		{
			console_move_up();
		}
}

void clear_display_text_buffer ()
{
	int i;
	for (i = 0; i < DISPLAY_TEXT_BUFFER_SIZE; ++i)
	{
		if (!display_text_buffer[i].deleted && display_text_buffer[i].data != NULL && display_text_buffer[i].data[0] != '\0'){
			free_text_message_data (display_text_buffer + i);
		}
		display_text_buffer[i].deleted= 1;
	}

	buffer_full = 0;
	console_msg_nr = 0;
	console_msg_offset = 0;
	last_message = -1;
	last_server_message_time = cur_time;
	total_nr_lines = 0;
	not_from_the_end_console = 1;

	clear_console();
	if(use_windowed_chat == 2){
		clear_chat_wins();
	}
}

int rewrap_message(text_message * msg, float zoom, int width, int * cursor)
{
	int nlines;
	float max_line_width = 0;

	if (msg == NULL || msg->data == NULL || msg->deleted)
		return 0;

	if (msg->wrap_width != width || msg->wrap_zoom != zoom)
	{
		if (msg->chan_idx != CHAT_NONE)
			total_nr_lines -= msg->wrap_lines;
 		nlines = reset_soft_breaks(msg->data, msg->len, msg->size, zoom, width, cursor, &max_line_width);
		if (msg->chan_idx != CHAT_NONE)
			total_nr_lines += nlines;
		msg->len = strlen(msg->data);
		msg->wrap_lines = nlines;
		msg->wrap_width = width;
		msg->wrap_zoom = zoom;
		msg->max_line_width = max_line_width;
	} else {
		nlines = msg->wrap_lines;
	}

	return nlines;
}
