/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/ 




#include "cfile/cfile.h"
#include "cmdline/cmdline.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "io/key.h"
#include "missionui/chatbox.h"
#include "network/multi.h"
#include "network/multi_pmsg.h"
#include "network/multimsgs.h"
#include "network/multiui.h"
#include "parse/parselo.h"
#include "playerman/player.h"
#include "ui/ui.h"



///////////////////////////////////////////////////////////
// Chat window UI 
///////////////////////////////////////////////////////////

// a little extra spacing for the team vs. team icons
#define CHATBOX_TEAM_ICON_SPACE			18

// SMALL CHATBOX ----------------------------------------------------------------------------------

// background bitmap
const char* Chatbox_small_bitmap_fname[GR_NUM_RESOLUTIONS] = {
	"Chatbox",		// GR_640
	"2_Chatbox"		// GR_1024
};

// background mask
const char* Chatbox_small_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
	"Chatbox-m",	// GR_640
	"2_Chatbox-m"	// GR_1024
};

// chatbox coords
int Chatbox_small_coords[GR_NUM_RESOLUTIONS][2] = {
	{ // GR_640
		192, 0
	},
	{ // GR_1024
		308, 0			 // ?
	}
};

// display area coods
int Chatbox_small_display_coords[GR_NUM_RESOLUTIONS][4] = {
	{	// GR_640
		196 + CHATBOX_TEAM_ICON_SPACE, 13, 410 - CHATBOX_TEAM_ICON_SPACE, 74
	},
	{	// GR_1024
		315 + CHATBOX_TEAM_ICON_SPACE, 22, 654 - CHATBOX_TEAM_ICON_SPACE, 116
	}
};

// input box coords
int Chatbox_small_input_coords[GR_NUM_RESOLUTIONS][4] = {
	{	// GR_640
		204, 100, 371, 22
	},
	{	// GR_1024
		328, 163, 591, 34
	}
};

// max # of lines
int Chatbox_small_max_lines[GR_NUM_RESOLUTIONS] = {
	7,				// GR_640
	12				// GR_1024
};

// BIG CHATBOX ----------------------------------------------------------------------------------

// background bitmap
const char* Chatbox_big_bitmap_fname[GR_NUM_RESOLUTIONS] = {
	"ChatboxBig",		// GR_640
	"2_ChatboxBig"		// GR_1024
};

// mask
const char* Chatbox_big_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
	"Chatbox-m",		// GR_640
	"2_Chatbox-m"			// GR_1024
};

// chatbox coords
int Chatbox_big_coords[GR_NUM_RESOLUTIONS][2] = {
	{ // GR_640
		192, 0
	},
	{ // GR_1024
		307, 0
	}
};

// display area coords
int Chatbox_big_display_coords[GR_NUM_RESOLUTIONS][4] = {
	{	// GR_640
		196 + CHATBOX_TEAM_ICON_SPACE, 13, 410 - CHATBOX_TEAM_ICON_SPACE, 326
	},
	{	// GR_1024
		315 + CHATBOX_TEAM_ICON_SPACE, 22, 654 - CHATBOX_TEAM_ICON_SPACE, 519
	}
};

// input box coords
int Chatbox_big_input_coords[GR_NUM_RESOLUTIONS][4] = {
	{	// GR_640
		204, 352, 371, 22
	},
	{	// GR_1024
		328, 565, 591, 34
	}
};

// max # of lines
int Chatbox_big_max_lines[GR_NUM_RESOLUTIONS] = {
	32,			// GR_640
	51				// GR_1024
};

// PAUSED CHATBOX ----------------------------------------------------------------------------------

// mask
const char* Chatbox_p_bitmap_fname[GR_NUM_RESOLUTIONS] = {
	"MPPause",			// GR_640
	"2_MPPause"			// GR_1024
};

// mask
const char* Chatbox_p_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
	"MPPause-m",		// GR_640
	"2_MPPause-m"		// GR_1024
};

// chatbox coords
int Chatbox_p_coords[GR_NUM_RESOLUTIONS][2] = {
	{ // GR_640
	//	192, 0
		0, 0
	},
	{ // GR_1024
	//	307, 0
		0, 0
	}
};

// display area coords
int Chatbox_p_display_coords[GR_NUM_RESOLUTIONS][4] = {
	{	// GR_640
		103 + CHATBOX_TEAM_ICON_SPACE, 149, 380 - CHATBOX_TEAM_ICON_SPACE, 143
	},
	{	// GR_1024
		165 + CHATBOX_TEAM_ICON_SPACE, 244, 654 - CHATBOX_TEAM_ICON_SPACE, 263
	}
};

// input box coords
int Chatbox_p_input_coords[GR_NUM_RESOLUTIONS][4] = {
	{	// GR_640
		106, 328, 379, 17
	},
	{	// GR_1024
		165, 525, 607, 27
	}
};

// max # of lines
int Chatbox_p_max_lines[GR_NUM_RESOLUTIONS] = {
	17,			// GR_640
	26				// GR_1024
};

// defines for location and other info of chat box MULTI_PAUSED
/*
#define CHATBOX_MP_FNAME					NOX("MPPause")
#define CHATBOX_MP_MASK						NOX("MPPause-m")
#define CHATBOX_MP_X1						112
#define CHATBOX_MP_Y1						198
#define CHATBOX_MP_X2						477
#define CHATBOX_MP_Y2						308
#define CHATBOX_MP_ICON_X					(CHATBOX_MP_X1)
#define CHATBOX_MP_W							(CHATBOX_MP_X2 - CHATBOX_MP_X1 + 1)
#define CHATBOX_MP_H							(CHATBOX_MP_Y2 - CHATBOX_MP_Y1 + 1)
#define CHATBOX_MP_BEGIN_X					(CHATBOX_MP_X1 + 3 + CHATBOX_TEAM_ICON_SPACE)
#define CHATBOX_MP_BEGIN_Y					CHATBOX_MP_Y1
#define CHATBOX_MP_DISP_W					365
#define CHATBOX_MP_MAX_LINES				11
#define CHATBOX_MP_INPUTBOX_X				(CHATBOX_MP_X1 + 3)
#define CHATBOX_MP_INPUTBOX_W				(CHATBOX_MP_W)
#define CHATBOX_MP_TEXTENTER_Y			329
*/

// CHATBOX ----------------------------------------------------------------------------------

// the settings being used for this instance
char Chatbox_mask[50];
int Chatbox_x1;
int Chatbox_y1;
int Chatbox_icon_x;
int Chatbox_w;
int Chatbox_h;
int Chatbox_begin_x;
int Chatbox_begin_y;
int Chatbox_disp_w;
int Chatbox_max_lines;
int Chatbox_inputbox_x;
int Chatbox_inputbox_w;
int Chatbox_textenter_y;
int Chat_scroll_up_coord[2];
int Chat_scroll_down_coord[2];

// how many pixels to indent succesive lines of text from a given player
#define CHAT_LINE_INDENT					20

// what chars other than letters and number's we'll toss
#define CHATBOX_INVALID_CHARS				NOX("~`")			// this is primarily so that we don't interfere with the voice recording keys

// common defines and 
UI_WINDOW	Chat_window;
UI_INPUTBOX	Chat_inputbox;
UI_BUTTON Chat_enter_text;

// button controls
#define CHATBOX_NUM_BUTTONS				3
#define CHATBOX_SCROLL_UP					0
#define CHATBOX_SCROLL_DOWN				1
#define CHATBOX_TOGGLE_SIZE				2				// used for both big and small

// coordinate indicies
#define CHATBOX_X_COORD 0
#define CHATBOX_Y_COORD 1
#define CHATBOX_W_COORD 2
#define CHATBOX_H_COORD 3

// chatbox buttons
ui_button_info Chatbox_buttons[GR_NUM_RESOLUTIONS][CHATBOX_NUM_BUTTONS+1] = {
	{ // GR_640
		ui_button_info("CHB_00",	613,	3,		-1,	-1,	0),
		ui_button_info("CHB_01",	613,	41,	-1,	-1,	1),
		ui_button_info("CHB_02a",	607,	74,	-1,	-1,	2),
		ui_button_info("CHB_02b",	607,	74,	-1,	-1,	2),
	},	
	{ // GR_1024
		ui_button_info("2_CHB_00",		981,	5,		-1,	-1,	0),
		ui_button_info("2_CHB_01",		981,	67,	-1,	-1,	1),
		ui_button_info("2_CHB_02a",	971,	119,	-1,	-1,	2),
		ui_button_info("2_CHB_02b",	971,	119,	-1,	-1,	2),
	}
};

int Chatbox_mode_flags = 0;

int Chatbox_bitmap = -1;
int Chatbox_big_bitmap = -1;
int Chatbox_small_bitmap = -1;
int Chatbox_mp_bitmap = -1;
int Chatbox_created = 0;

///////////////////////////////////////////////////////////
// Chat window text
///////////////////////////////////////////////////////////
#define BRIEF_DISPLAY_SPACING 2   // pixel spacing between chat lines

// the first byte of the text string will be the net player id of the
SCP_list<brief_chat> Brief_chat;

// the index of the chat line we will start rendering at
int Brief_start_display_index;

// chatbox line recall data
#define CHATBOX_MAX_RECALL_LINES			10
int Chatbox_recall_count = 0;
int Chatbox_recall_index = 0;
int Chatbox_recall_last = -1;
char Chatbox_recall_lines[CHATBOX_MAX_RECALL_LINES][CHATBOX_MAX_LEN+2];

///////////////////////////////////////////////////////////
// forward declarations
///////////////////////////////////////////////////////////
void chatbox_chat_init();
void chatbox_render_chat_lines();
void chatbox_set_mode(int mode_flags);
void chatbox_toggle_size();
void chatbox_toggle_size_adjust_lines();
void chat_autosplit_line(char *msg,char *remainder);
void chatbox_recall_add(char *string);
void chatbox_recall_up();
void chatbox_recall_down();

// set the chatbox mode without checking for any previous modes which may need to be handled specially
void chatbox_set_mode(int mode_flags)
{
	int size;
	
	// set the stored mode
	Chatbox_mode_flags = mode_flags;
	
	// small pregame chatbox
	if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
		size = 0;
	}
	// big pregame chatbox
	else if(Chatbox_mode_flags & CHATBOX_FLAG_BIG){
		size = 1;
	}
	// multiplayer paused
	else {
		size = 2;
	}

	// set up the display/init variables based upon what mode we chode
	switch(size){
	case 0:		
		strcpy_s(Chatbox_mask, Chatbox_small_bitmap_mask_fname[gr_screen.res]);
		Chatbox_x1 = Chatbox_small_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_y1 = Chatbox_small_coords[gr_screen.res][CHATBOX_Y_COORD];		
		Chatbox_icon_x = Chatbox_small_display_coords[gr_screen.res][CHATBOX_X_COORD] - CHATBOX_TEAM_ICON_SPACE;
		Chatbox_w = Chatbox_small_display_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_h = Chatbox_small_display_coords[gr_screen.res][CHATBOX_H_COORD];
		Chatbox_begin_x = Chatbox_small_display_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_begin_y = Chatbox_small_display_coords[gr_screen.res][CHATBOX_Y_COORD];
		Chatbox_disp_w = Chatbox_small_display_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_max_lines = gr_get_dynamic_font_lines(Chatbox_small_max_lines[gr_screen.res]);
		Chatbox_inputbox_x = Chatbox_small_input_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_inputbox_w = Chatbox_small_input_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_textenter_y = Chatbox_small_input_coords[gr_screen.res][CHATBOX_Y_COORD];		
		break;

	case 1:		
		strcpy_s(Chatbox_mask, Chatbox_big_bitmap_mask_fname[gr_screen.res]);
		Chatbox_x1 = Chatbox_big_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_y1 = Chatbox_big_coords[gr_screen.res][CHATBOX_Y_COORD];		
		Chatbox_icon_x = Chatbox_big_display_coords[gr_screen.res][CHATBOX_X_COORD] - CHATBOX_TEAM_ICON_SPACE;
		Chatbox_w = Chatbox_big_display_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_h = Chatbox_big_display_coords[gr_screen.res][CHATBOX_H_COORD];
		Chatbox_begin_x = Chatbox_big_display_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_begin_y = Chatbox_big_display_coords[gr_screen.res][CHATBOX_Y_COORD];
		Chatbox_disp_w = Chatbox_big_display_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_max_lines = gr_get_dynamic_font_lines(Chatbox_big_max_lines[gr_screen.res]);
		Chatbox_inputbox_x = Chatbox_big_input_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_inputbox_w = Chatbox_big_input_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_textenter_y = Chatbox_big_input_coords[gr_screen.res][CHATBOX_Y_COORD];		
		break;

	case 2:				
		Chatbox_x1 = Chatbox_p_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_y1 = Chatbox_p_coords[gr_screen.res][CHATBOX_Y_COORD];		
		Chatbox_icon_x = Chatbox_p_display_coords[gr_screen.res][CHATBOX_X_COORD] - CHATBOX_TEAM_ICON_SPACE;
		Chatbox_w = Chatbox_p_display_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_h = Chatbox_p_display_coords[gr_screen.res][CHATBOX_H_COORD];
		Chatbox_begin_x = Chatbox_p_display_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_begin_y = Chatbox_p_display_coords[gr_screen.res][CHATBOX_Y_COORD];
		Chatbox_disp_w = Chatbox_p_display_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_max_lines = gr_get_dynamic_font_lines(Chatbox_p_max_lines[gr_screen.res]);
		Chatbox_inputbox_x = Chatbox_p_input_coords[gr_screen.res][CHATBOX_X_COORD];
		Chatbox_inputbox_w = Chatbox_p_input_coords[gr_screen.res][CHATBOX_W_COORD];
		Chatbox_textenter_y = Chatbox_p_input_coords[gr_screen.res][CHATBOX_Y_COORD];
		break;
	}
}

int chatbox_get_msg_target_type(char *msg )
{
	if (msg[0] != '/') {
		return MULTI_MSG_ALL;
	}

	if (!strnicmp (msg, "/f:", 3)) {
		return MULTI_MSG_FRIENDLY;
	}	

	if (!strnicmp (msg, "/h:", 3)) {
		return MULTI_MSG_HOSTILE;
	}	

	return MULTI_MSG_EXPR;
}

size_t chatbox_get_msg_target_length(char *msg)
{
	if ((msg[0] != '/') && (strchr(msg, ':') != NULL) ) {
		return 0;
	}

	// find the first space
	return strcspn(msg, ":") + 1 ;

}

// send a msg to the game chat
void chatbox_send_msg(char* msg)
{
	// check if this message is supposed to have a recipient
	int target = chatbox_get_msg_target_type(msg);

	// tack on the null terminator in the boundary case
	size_t x = strlen(msg);
	if (x >= CHATBOX_MAX_LEN) {
		msg[CHATBOX_MAX_LEN - 1] = '\0';
	}

	// if I'm the server, then broadcast the packet
	chatbox_recall_add(msg);
	if (target != MULTI_MSG_EXPR) {
		send_game_chat_packet(Net_player, msg, target);
	} else {
		// copy the name of the player the message is being sent to
		char temp[CHATBOX_MAX_LEN];
		size_t target_length = chatbox_get_msg_target_length(msg);
		strncpy(temp, msg + 1, target_length - 2);
		temp[target_length - 2] = '\0';
		send_game_chat_packet(Net_player, msg, target, nullptr, temp);
	}

	chatbox_add_line(msg, MY_NET_PLAYER_NUM);
}

// automatically split up any input text, send it, and leave the remainder 
void chatbox_autosplit_line()
{
	char msg[CHATBOX_MAX_LEN];
	
	// if the chat line is getting too long, fire off the message, putting the last
	// word on the next input line.
	memset(msg, 0, CHATBOX_MAX_LEN);
	Chat_inputbox.get_text(msg);
	const char* remainder = ""; 

	// determine if the width of the string in pixels is > than the inputbox width -- if so,
	// then send the message
	int msg_pixel_width;
	gr_get_string_size(&msg_pixel_width, nullptr, msg);
	// if ( msg_pixel_width >= (Chatbox_inputbox_w - Player->short_callsign_width) ) {
	if ( msg_pixel_width >= (Chatbox_inputbox_w - 25)) {
		auto last_space = strrchr(msg, ' ');
		if ( last_space ) {
			*last_space = '\0';
			remainder = last_space + 1;
		} else {
			remainder = "";
		}	
		
		chatbox_send_msg(msg);

		// check if this message is supposed to have a recipient
		int target = chatbox_get_msg_target_type(msg);

		if (target != MULTI_MSG_ALL) {
			// we need to add the target the message is going to before we add the rest of the string
			char temp[CHATBOX_MAX_LEN];
			size_t target_length = chatbox_get_msg_target_length(msg);
			strncpy(temp, msg, target_length);
 			temp[target_length] = ' '; 
 			temp[target_length+1] = '\0'; 
			strcat_s(temp, remainder); 
			Chat_inputbox.set_text(temp);
		}
		else {
			// display any remainder of text on the next line
			Chat_inputbox.set_text(remainder);
		}
	} else if((Chat_inputbox.pressed() && (msg[0] != '\0')) || (strlen(msg) >= CHATBOX_MAX_LEN)) { 
		chatbox_send_msg(msg);
		// display any remainder of text on the next line
		Chat_inputbox.set_text(remainder);		
	}	
}

// initialize all chatbox details with the given mode flags
int chatbox_create(int mode_flags)
{
	int idx;
	
	// don't do anything if the chatbox is already initialized
	if (Chatbox_created){
		return -1;
	}

	// probably shouldn't be using the chatbox in single player mode
	Assert(Game_mode & GM_MULTIPLAYER);

	// setup all data to correspond to our mode flags
	chatbox_set_mode(mode_flags);
	
	// initialize all low-level details related to chatting
	chatbox_chat_init();		

	// attempt to load in the chatbox background bitmap
	if(Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX){
		Chatbox_big_bitmap = bm_load(Chatbox_big_bitmap_fname[gr_screen.res]);
		Chatbox_small_bitmap = bm_load(Chatbox_small_bitmap_fname[gr_screen.res]);
		Chatbox_mp_bitmap = bm_load(Chatbox_p_bitmap_fname[gr_screen.res]);
		
		if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
			Chatbox_bitmap = Chatbox_small_bitmap;
		} else if(Chatbox_mode_flags & CHATBOX_FLAG_BIG){
			Chatbox_bitmap = Chatbox_big_bitmap;
		} else {
			Chatbox_bitmap = Chatbox_mp_bitmap;
		}

		if((Chatbox_bitmap == -1) || (Chatbox_small_bitmap == -1) || (Chatbox_big_bitmap == -1) || (Chatbox_mp_bitmap == -1)){
			return -1;
		}
	}
	
	// attempt to create the ui window for the chatbox and assign the mask
	Chat_window.create( 0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0 );
	Chat_window.set_mask_bmap(Chatbox_mask);	

   // create the chat text enter input area	
	Chat_inputbox.create( &Chat_window, Chatbox_inputbox_x, Chatbox_textenter_y, Chatbox_inputbox_w, CHATBOX_MAX_LEN, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_EAT_USED, Chatbox_w, Color_netplayer[MY_NET_PLAYER_NUM]);	
	Chat_inputbox.set_focus();
	Chat_inputbox.set_invalid_chars(CHATBOX_INVALID_CHARS);

	// if we're supposed to supply and check for out own buttons
	if((Chatbox_mode_flags & CHATBOX_FLAG_BUTTONS) && (Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX)){
		for(idx=0; idx<CHATBOX_NUM_BUTTONS; idx++){
			// create the button			
			Chatbox_buttons[gr_screen.res][idx].button.create(&Chat_window, "", Chatbox_buttons[gr_screen.res][idx].x, Chatbox_buttons[gr_screen.res][idx].y, 60, 30, (idx == CHATBOX_TOGGLE_SIZE) ? 0 : 1);
			
			// set the highlight action
			Chatbox_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);

			// set the bitmap
			Chatbox_buttons[gr_screen.res][idx].button.set_bmaps(Chatbox_buttons[gr_screen.res][idx].filename);

			// set the hotspot
			Chatbox_buttons[gr_screen.res][idx].button.link_hotspot(Chatbox_buttons[gr_screen.res][idx].hotspot);
		}
		
		// now create the toggle size button with the appropriate button
		if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
			Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].filename);
		} else {
			Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE+1].filename);
		}
	}

	// an invisible button that will set focus to input box when clicked on
	Chat_enter_text.create( &Chat_window, "", 0, 0, 60, 30, 0);
	Chat_enter_text.hide();					// button doesn't show up
	Chat_enter_text.link_hotspot(50);	

	Chatbox_created = 1;
	return 0;
}

// process this frame for the chatbox
int chatbox_process(int key_in)
{	
	int key_out;

	key_out = key_in;

	// if the chatbox hasn't explicitly been created, we can't do any processing
	if(!Chatbox_created){
		return key_out;
	}

	// process the incoming key appropriately
	if (key_in == -1) {
		key_out = Chat_window.process();
	} else {
		key_out = Chat_window.process(key_in);
	}

	// look for special keypresses
	switch(key_out){
	// line recall up one
	case KEY_UP:
		chatbox_recall_up();
		key_out = 0;
		break;
	
	// line recall down one
	case KEY_DOWN:
		chatbox_recall_down();
		key_out = 0;
		break;
	}

	// if we're supposed to be checking our own scroll buttons
	if((Chatbox_mode_flags & CHATBOX_FLAG_BUTTONS) && (Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX)){
		if ( Chatbox_buttons[gr_screen.res][CHATBOX_SCROLL_UP].button.pressed() ) {
			chatbox_scroll_up();
		}

		if ( Chatbox_buttons[gr_screen.res][CHATBOX_SCROLL_DOWN].button.pressed() ) {
			chatbox_scroll_down();
		}

		if ( Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.pressed() ){
			chatbox_toggle_size();
		}
	}

	// check to see if the enter text button has been pressed
	if ( Chat_enter_text.pressed() ) {
		Chat_inputbox.set_focus();
	}

	// check to see if the current input text needs to be split up and sent automaticall
	chatbox_autosplit_line();	

	return key_out;
}

void chatbox_close()
{
	if (!Chatbox_created)
		return;

	// destory the UI window
	Chat_window.destroy();

	// unload any bitmaps
	if (Chatbox_small_bitmap != -1) {
		bm_release(Chatbox_small_bitmap);
		Chatbox_small_bitmap = -1;
	}

	if (Chatbox_big_bitmap != -1) {
		bm_release(Chatbox_big_bitmap);
		Chatbox_big_bitmap = -1;
	}

	if (Chatbox_mp_bitmap != -1) {
		bm_release(Chatbox_mp_bitmap);
		Chatbox_mp_bitmap = -1;
	}	

	// clear all the text lines in the
	chatbox_clear();
	Chatbox_created = 0;
}

// shutdown all chatbox functionality
void chatbox_clear()
{
	// only do this if we have valid data
	if(Chatbox_created){
		// clear all chat data	
		Brief_chat.clear();
		Brief_start_display_index=0;

		// clear all line recall data
		Chatbox_recall_count = 0;
		Chatbox_recall_index = 0;
		Chatbox_recall_last = -1;
		memset(Chatbox_recall_lines,0,CHATBOX_MAX_RECALL_LINES * (CHATBOX_MAX_LEN + 2));	
	}
}

// render the chatbox for this frame
void chatbox_render()
{
	if (!Chatbox_created){
		return;
	}

	// clear the multiplayer chat window
	// gr_set_clip(Chatbox_x1, Chatbox_y1, Chatbox_w, Chatbox_h, GR_RESIZE_MENU);
	// gr_clear();
	// gr_reset_clip();

	// draw the background bitmap if we're supposed to
	if ( (Chatbox_bitmap != -1) && (Chatbox_mode_flags & CHATBOX_FLAG_DRAW_BOX)) {
		gr_set_bitmap( Chatbox_bitmap );
		gr_bitmap(Chatbox_x1, Chatbox_y1, GR_RESIZE_MENU);		
	}

	// render the chat lines
	chatbox_render_chat_lines();

	// render any UI window stuff
	Chat_window.draw();
}

// try and scroll the chatbox up. return 0 or 1 on fail or success
int chatbox_scroll_up()
{	
	if (static_cast<int>(Brief_chat.size()) > Chatbox_max_lines) {
		if (Brief_start_display_index > 0) {
			Brief_start_display_index--;
			return 1;
		}		
	} 
	return 0;
}

// try and scroll the chatbox down, return 0 or 1 on fail or success
int chatbox_scroll_down()
{
	if (static_cast<int>(Brief_chat.size()) > Chatbox_max_lines) {
		if (Brief_start_display_index < (static_cast<int>(Brief_chat.size()) - Chatbox_max_lines)) {
			Brief_start_display_index++;
			return 1;
		}
	}
	return 0;
}

void chatbox_chat_init()
{
	chatbox_clear();	

	// initialize the line recall data
	Chatbox_recall_count = 0;
	Chatbox_recall_index = 0;
	Chatbox_recall_last = -1;
	memset(Chatbox_recall_lines,0,CHATBOX_MAX_RECALL_LINES * (CHATBOX_MAX_LEN + 2));	
}

void chat_box_add_chat_to_list(brief_chat& chat)
{
	if (Brief_chat.size() >= MAX_BRIEF_CHAT_LINES) {
		Brief_chat.erase(Brief_chat.begin());
	}

	Brief_chat.push_back(chat);
}

// int Test_color = 0;
void chatbox_add_line(const char *msg, int pid, int add_id)
{	
	if(!Chatbox_created){
		return;
	}

	brief_chat chat;
	chat.indent = false;
	chat.player_id = pid;

	// maybe stick on who sent the message	
	if(add_id){
		if(MULTI_STANDALONE(Net_players[pid])){
			strcpy_s(chat.callsign, "<SERVER>");
		} else {
			strcpy_s(chat.callsign, Net_players[pid].m_player->short_callsign);
		}
	} else {
		chat.callsign[0] = '\0';
	}	

	// split the text up into as many lines as necessary
	int n_chars[3];	
	const char* p_str[3]; // for the initial line (unindented)
	int n_lines = split_str(msg, Chatbox_disp_w, n_chars, p_str, 3, CHATBOX_STRING_LEN);
	Assertion(n_lines != -1, "Chat split string returned an invalid number of lines, please report!");	

	// will happen if msg is all whitespace, in which case we should ignore it
	if (n_lines == 0) {
		return;
	}

	// copy in the chars
	strcpy_s(chat.text, p_str[0]);

	chat_box_add_chat_to_list(chat);
	
	// if we have more than 1 line, re-split everything so that the rest are indented
	if(n_lines > 1){
		// split up the string after the first break-marker
		char msg_extra[CHATBOX_STRING_LEN];
		n_lines = split_str(msg_extra + n_chars[0],Chatbox_disp_w - CHAT_LINE_INDENT,n_chars,p_str,3, CHATBOX_STRING_LEN);
		Assertion(n_lines != -1, "Chat split string returned an invalid number of lines, please report!");		

		// setup these remaining lines
		for(int idx=0;idx<n_lines;idx++){
			brief_chat i_chat;
			i_chat.player_id = pid;
			i_chat.indent = true;
			strcpy_s(i_chat.text, p_str[idx]);

			chat_box_add_chat_to_list(i_chat);
		}
	}
			
	// COMMAND LINE OPTION
	if(Cmdline_multi_stream_chat_to_file && Multi_chat_stream!=NULL && msg[0] != '\0'){ // stream to the file if we're supposed to
		cfwrite_string(msg,Multi_chat_stream);
		cfwrite_char('\n',Multi_chat_stream);
	}	

	// if this line of text is from the player himself, automatically go to the bottom of
	// the chat list
	if(pid == MY_NET_PLAYER_NUM){
		if (static_cast<int>(Brief_chat.size()) > Chatbox_max_lines) {
			Brief_start_display_index = (static_cast<int>(Brief_chat.size()) - Chatbox_max_lines);
		}
	}
	// if we have enough lines of text to require scrolling, scroll down by one.
	else { 
		chatbox_scroll_down();
	}	
}

void chatbox_render_chat_lines()
{
	int count = 0;	
	int ly = Chatbox_begin_y;
	int line_height = gr_get_font_height() + 1;

	if (Brief_chat.empty()) {
		return;
	}

	Assertion(Brief_start_display_index >= 0 && Brief_start_display_index < static_cast<int>(Brief_chat.size()),
		"Chat display index is invalid, please report!");

	auto chat = Brief_chat.begin();
	std::advance(chat, Brief_start_display_index);

	while (chat != Brief_chat.end() && count < Chatbox_max_lines) {	
		char msg[CHATBOX_STRING_LEN];

		// add the player callsign
		sprintf(msg, "%s %s", chat->callsign, chat->text);
		Assertion(strlen(msg) < (CHATBOX_STRING_LEN - 2), "Chat string is too long, please report!");

		// print the line out
		gr_set_color_fast(Color_netplayer[chat->player_id]);

		// draw the first line (no indent)
		int indent = 0;
		if (chat->indent) {
			indent = CHAT_LINE_INDENT;
		}
		gr_string(Chatbox_begin_x + indent, ly, msg, GR_RESIZE_MENU);

		// if we're in a team vs. team game, blit the player color icon
		if (Netgame.type_flags & NG_TYPE_TEAM) {
			switch (Net_players[chat->player_id].p_info.team) {
			case 0:
				// if he's a team captain
				if (Net_players[chat->player_id].flags & NETINFO_FLAG_TEAM_CAPTAIN) {
					if (Multi_common_icons[MICON_TEAM0_SELECT] != -1) {
						gr_set_bitmap(Multi_common_icons[MICON_TEAM0_SELECT]);
						gr_bitmap(Chatbox_icon_x, ly - 2, GR_RESIZE_MENU);
					}
				}
				// just you're average peon
				else {
					if (Multi_common_icons[MICON_TEAM0] != -1) {
						gr_set_bitmap(Multi_common_icons[MICON_TEAM0]);
						gr_bitmap(Chatbox_icon_x, ly - 2, GR_RESIZE_MENU);
					}
				}
				break;
			case 1:
				// if he's a team captain
				if (Net_players[chat->player_id].flags & NETINFO_FLAG_TEAM_CAPTAIN) {
					if (Multi_common_icons[MICON_TEAM1_SELECT] != -1) {
						gr_set_bitmap(Multi_common_icons[MICON_TEAM1_SELECT]);
						gr_bitmap(Chatbox_icon_x, ly - 2, GR_RESIZE_MENU);
					}
				}
				// just your average peon
				else {
					if (Multi_common_icons[MICON_TEAM1] != -1) {
						gr_set_bitmap(Multi_common_icons[MICON_TEAM1]);
						gr_bitmap(Chatbox_icon_x, ly - 2, GR_RESIZE_MENU);
					}
				}
				break;
			}
		}

		// increment the count and line position
		count++;
		++chat;
		ly += line_height;
	}		
}

void chatbox_toggle_size()
{
	// if we're in "small" mode
	if(Chatbox_mode_flags & CHATBOX_FLAG_SMALL){
		chatbox_force_big();
		
		// play a sound
		gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
	}
	// if we're in "big" mode
	else if(Chatbox_mode_flags & CHATBOX_FLAG_BIG){
		chatbox_force_small();
		
		// play a sound
		gamesnd_play_iface(InterfaceSounds::IFACE_MOUSE_CLICK);
	}
}

void chatbox_toggle_size_adjust_lines()
{
	if ((static_cast<int>(Brief_chat.size()) > Chatbox_max_lines)) {
		Brief_start_display_index = (static_cast<int>(Brief_chat.size()) - Chatbox_max_lines);
	}
	else {
		Brief_start_display_index = 0;
	}
}

// force the chatbox to go into small mode (if its in large mode) - will not wotk if in multi paused chatbox mode
void chatbox_force_small()
{
	int new_mode_flags;

	// don't do anything unless we're currently in "big" mode
	if(!(Chatbox_mode_flags & CHATBOX_FLAG_BIG)){
		return;
	}

	new_mode_flags = Chatbox_mode_flags;

	// switch to the appropriate mode
	new_mode_flags &= ~(CHATBOX_FLAG_SMALL | CHATBOX_FLAG_BIG);	
	new_mode_flags |= CHATBOX_FLAG_SMALL;
	Chatbox_bitmap = Chatbox_small_bitmap;
		
	// flip the up/down arrow
	Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].filename);
	
	// call this to set everything up correctly
	chatbox_set_mode(new_mode_flags);		
	
	// change the location of the input box
	Chat_inputbox.update_dimensions(Chatbox_inputbox_x, Chatbox_textenter_y, Chatbox_inputbox_w,15);
	Chat_inputbox.set_focus();

	// adjust what line we start displaying from based upon the new size of the window
	chatbox_toggle_size_adjust_lines();
}

// force the chatbox to go into big mode (if its in small mode) - will not work if in multi paused chatbox mode
void chatbox_force_big()
{
	int new_mode_flags;

	// don't do anything unless we're currently in "small" mode
	if(!(Chatbox_mode_flags & CHATBOX_FLAG_SMALL)){
		return;
	}

	new_mode_flags = Chatbox_mode_flags;	

	// switch to the appropriate mode
	new_mode_flags &= ~(CHATBOX_FLAG_SMALL | CHATBOX_FLAG_BIG);	
	new_mode_flags |= CHATBOX_FLAG_BIG;
	Chatbox_bitmap = Chatbox_big_bitmap;
		
	// flip the up/down arrow
	Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE].button.set_bmaps(Chatbox_buttons[gr_screen.res][CHATBOX_TOGGLE_SIZE+1].filename);
	
	// call this to set everything up correctly
	chatbox_set_mode(new_mode_flags);		
	
	// change the location of the input box
	Chat_inputbox.update_dimensions(Chatbox_inputbox_x, Chatbox_textenter_y, Chatbox_inputbox_w,15);
	Chat_inputbox.set_focus();

	// adjust what line we start displaying from based upon the new size of the window
	chatbox_toggle_size_adjust_lines();
}

// "lose" the focus on the chatbox inputbox
void chatbox_lose_focus()
{
	if(!Chatbox_created){
		return;
	}

	// clear the focus on the inputbox
	Chat_inputbox.clear_focus();
}

// return if the inputbox for the chatbox currently has focus
int chatbox_has_focus()
{
	return Chat_inputbox.has_focus();
}

// grab the focus for the chatbox inputbox
void chatbox_set_focus()
{
	Chat_inputbox.set_focus();
}

// return if the inputbox was pressed - "clicked on"
int chatbox_pressed()
{
	return Chat_inputbox.pressed();
}

// add the string to the line recall list
void chatbox_recall_add(char *string)
{
	int idx;	

	// aleays reset the recall index when adding
	Chatbox_recall_index = 0;
	Chatbox_recall_last = -1;

	// if this string matches the last string we entered, don't add it again
	if(!strcmp(string,Chatbox_recall_lines[0])){
		return;
	}

	// move all items up (this works fine for small #'s of recall lines
	for(idx=CHATBOX_MAX_RECALL_LINES-1;idx > 0;idx--){
		memcpy(&Chatbox_recall_lines[idx],&Chatbox_recall_lines[idx-1],CHATBOX_MAX_LEN+2);
	}

	// copy the new item into spot 0
	strcpy_s(Chatbox_recall_lines[0],string);

	// increment the recall count if necessary
	if(Chatbox_recall_count < CHATBOX_MAX_RECALL_LINES){
		Chatbox_recall_count++;
	}	
}

// user has pressed the "up" key
void chatbox_recall_up()
{
	// if we've got no recall lines, do nothing
	if(Chatbox_recall_count <= 0){
		return;
	}

	// if we can increment up
	if(Chatbox_recall_index < (Chatbox_recall_count - 1)){
		// if this is the last line we recalled, pre-increment
		if(Chatbox_recall_last == Chatbox_recall_index){
			Chat_inputbox.set_text(Chatbox_recall_lines[++Chatbox_recall_index]);
			Chatbox_recall_last = Chatbox_recall_index;
		}
		// otherwise, post increment
		else {
			Chat_inputbox.set_text(Chatbox_recall_lines[Chatbox_recall_index++]);
			Chatbox_recall_last = Chatbox_recall_index - 1;
		}
	} 
	// if we can't increment up
	else {
		Chat_inputbox.set_text(Chatbox_recall_lines[Chatbox_recall_index]);
		Chatbox_recall_last = Chatbox_recall_index;
	}	
}

// user has pressed the "down" key
void chatbox_recall_down()
{	
	// if we've got no recall lines, do nothing
	if(Chatbox_recall_count <= 0){
		return;
	}

	// if we can decrement down
	if(Chatbox_recall_index > 0){
		// if this is the last line we recalled, pre-decrement
		if(Chatbox_recall_last == Chatbox_recall_index){
			Chat_inputbox.set_text(Chatbox_recall_lines[--Chatbox_recall_index]);
			Chatbox_recall_last = Chatbox_recall_index;
		} 
		// otherwise post,decrement
		else {
			Chat_inputbox.set_text(Chatbox_recall_lines[Chatbox_recall_index--]);
			Chatbox_recall_last = Chatbox_recall_index + 1;
		}
	} 
	// if we can't decrement down
	else {		
		Chat_inputbox.set_text("");
		Chatbox_recall_last = -1;
	}	
}

// reset all timestamps associated with the chatbox
void chatbox_reset_timestamps()
{
	int idx;

	// if there is no chatbox created, do nothing
	if(!Chatbox_created){
		return;
	}

	// otherwise clear all timestamps on all the buttons
	for(idx=0; idx<CHATBOX_NUM_BUTTONS+1; idx++){
		Chatbox_buttons[gr_screen.res][idx].button.reset_timestamps();
	}
}
