/*
Copyright (C) 1997-2001 Id Software, Inc.

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.

*/
// console.c

#include "client.h"
#include "prompt.h"
#include "version.h"

#define	CON_TIMES		4
#define CON_TIMES_MASK	( CON_TIMES - 1 )

#define CON_TOTALLINES			1024	// total lines in console scrollback
#define CON_TOTALLINES_MASK		( CON_TOTALLINES - 1 )

/* max chars in a single line.
 * this should be large enough to hold (maxscreenwidth / charwidth) chars,
 * plus some extra color escape codes
 */
#define CON_LINEWIDTH	512

typedef struct console_s {
	qboolean	initialized;

	char	text[CON_TOTALLINES][CON_LINEWIDTH];
	int		current;		// line where next message will be printed
	int		x;				// offset in current line for next print
	int		display;		// bottom of console displays this line

	int 	linewidth;		// characters across screen
	int		vidWidth, vidHeight;
	float	scale;

	int		times[CON_TIMES];	// cls.realtime time the line was generated
								// for transparent notify lines
	qboolean	addToNotify;

	qhandle_t	conbackImage;
	qhandle_t	charsetImage;

	int		charWidth, charHeight;

	float	currentHeight;	// aproaches scr_conlines at scr_conspeed
	float	destHeight;		// 0.0 to 1.0 lines of console to display
	float	maxHeight;

	inputField_t chatField;
	commandPrompt_t prompt;

	qboolean	chatTeam;
} console_t;

static console_t	con;

static cvar_t	*con_notifytime;
static cvar_t	*con_clock;
static cvar_t	*con_height;
static cvar_t	*con_speed;
static cvar_t	*con_chat;
static cvar_t	*con_alpha;
static cvar_t	*con_scale;
static cvar_t	*con_font;
static cvar_t	*con_background;

// ============================================================================

/*
================
Con_AddToNotify
================
*/
void Con_AddToNotify( qboolean add ) {
	con.addToNotify = add;
}

/*
================
Con_ClearTyping
================
*/
void Con_ClearTyping( void ) {
	// clear any typing
	IF_Clear( &con.prompt.inputLine );
}

/*
================
Con_Close
================
*/
void Con_Close( void ) {
	Key_SetDest( cls.key_dest & ~KEY_CONSOLE );
	con.currentHeight = 0;

	Con_ClearTyping();
	Con_ClearNotify_f();

	Cvar_Set( "cl_paused", "0" );
}

/*
================
Con_ToggleConsole_f
================
*/
void Con_ToggleConsole_f( void ) {
    if( sv_running->integer && cl.attractLoop == ATR_DEMO && !cls.demoplayback )
    {
		Cbuf_AddText( "killserver\n" );
		//return;
	}
	
	Con_ClearTyping();
	Con_ClearNotify_f();

	if( cls.key_dest & KEY_CONSOLE ) {
		Key_SetDest( cls.key_dest & ~KEY_CONSOLE );
		//Cvar_Set( "cl_paused", "0" );
		return;
	}

	// FIXME: use old q2 style
	Key_SetDest( ( cls.key_dest | KEY_CONSOLE ) & ~KEY_MESSAGE );

	// only pause in single player
	//if( Cvar_VariableInteger( "maxclients" ) == 1 ) {
	//	Cvar_Set( "cl_paused", "1" );
	//}
	
}

/*
================
Con_Clear_f
================
*/
void Con_Clear_f( void ) {
	memset( con.text, 0, sizeof( con.text ) );
	con.display = con.current;
}

static const char *Con_Dump_g( const char *partial, int state ) {
	return Com_FileNameGenerator( "", ".txt", partial, qtrue, state );
}
						
/*
================
Con_Dump_f

Save the console contents out to a file
================
*/
void Con_Dump_f( void ) {
	int		l;
	char	*line;
	fileHandle_t	f;
	char	buffer[CON_LINEWIDTH];
	char	name[MAX_QPATH];

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <filename>\n", Cmd_Argv( 0 ) );
		return;
	}

	Cmd_ArgvBuffer( 1, name, sizeof( name ) );
	COM_DefaultExtension( name, ".txt", sizeof( buffer ) );

	FS_FOpenFile( name, &f, FS_MODE_WRITE );
	if( !f ) {
		Com_EPrintf( "Couldn't open %s for writing.\n", name );
		return;
	}

	// skip empty lines
	for( l = con.current - CON_TOTALLINES + 1 ; l <= con.current ; l++ ) {
		if( con.text[l & CON_TOTALLINES_MASK][0] ) {
			break;
		}
	}

	// write the remaining lines
	for( ; l <= con.current ; l++ ) {
		line = con.text[l & CON_TOTALLINES_MASK];
		Q_CleanColorStr( line, buffer, sizeof( buffer ) );

		FS_FPrintf( f, "%s\n", buffer );
	}

	FS_FCloseFile( f );

	Com_Printf( "Dumped console text to %s.\n", name );
}

						
/*
================
Con_ClearNotify_f
================
*/
void Con_ClearNotify_f( void ) {
	int		i;
	
	for( i = 0; i < CON_TIMES; i++ )
		con.times[i] = 0;
}

						
/*
================
Con_MessageMode_f
================
*/
void Con_MessageMode_f( void ) {
	con.chatTeam = qfalse;
	Key_SetDest( cls.key_dest | KEY_MESSAGE );
}

/*
================
Con_MessageMode2_f
================
*/
void Con_MessageMode2_f( void ) {
	con.chatTeam = qtrue;
	Key_SetDest( cls.key_dest | KEY_MESSAGE );
}

/*
================
Con_CheckResize

If the line width has changed, reformat the buffer.
================
*/
void Con_CheckResize( void ) {
	int		width;

	con.vidWidth = scr_glconfig.vidWidth * con.scale;
	con.vidHeight = scr_glconfig.vidHeight * con.scale;

	width = ( con.vidWidth / con.charWidth ) - 2;

	if( width == con.linewidth )
		return;

	con.linewidth = width > CON_LINEWIDTH ? CON_LINEWIDTH : width;
	con.prompt.inputLine.visibleChars = con.linewidth;
	con.prompt.widthInChars = con.linewidth;
	con.chatField.visibleChars = con.linewidth;
}


/*
================
Con_OnChangeVar
================
*/
static void Con_OnChangeVar( cvar_t *self, void *arg ) {
	if( con.initialized && cls.ref_initialized ) {
		Con_SetupDC();
	}
}


/*
================
Con_Init
================
*/
void Con_Init( void ) {
	memset( &con, 0, sizeof( con ) );

//
// register our commands
//
	con_notifytime = Cvar_Get( "con_notifytime", "3", 0 );
	con_clock = Cvar_Get( "con_clock", "0", CVAR_ARCHIVE );
	con_height = Cvar_Get( "con_height", "0.5", CVAR_ARCHIVE );
	con_speed = Cvar_Get( "scr_conspeed", "3", 0 );
	con_chat = Cvar_Get( "con_chat", "0", 0 );
	con_alpha = Cvar_Get( "con_alpha", "1", CVAR_ARCHIVE );
	con_scale = Cvar_Get( "con_scale", "1", CVAR_ARCHIVE );
	con_font = Cvar_Get( "con_font", "conchars", CVAR_ARCHIVE );
	con_font->changedFunc = Con_OnChangeVar;
	con_background = Cvar_Get( "con_background", "conback", CVAR_ARCHIVE );
	con_background->changedFunc = Con_OnChangeVar;

	Cmd_AddCommand( "toggleconsole", Con_ToggleConsole_f );
	Cmd_AddCommand( "messagemode", Con_MessageMode_f );
	Cmd_AddCommand( "messagemode2", Con_MessageMode2_f );
	Cmd_AddCommand( "clear", Con_Clear_f );
	Cmd_AddCommand( "clearnotify", Con_ClearNotify_f );
	Cmd_AddCommandEx( "condump", Con_Dump_f, Con_Dump_g );

	IF_Init( &con.prompt.inputLine, 1, MAX_FIELD_TEXT );
	IF_Init( &con.chatField, 1, MAX_FIELD_TEXT );

	con.prompt.Printf = Con_Printf;

	con.addToNotify = qtrue;

	// use default width if no video initialized yet
	scr_glconfig.vidWidth = 640;
	scr_glconfig.vidHeight = 480;
	con.linewidth = -1;
	con.charWidth = 8;
	con.charHeight = 8;
	con.maxHeight = 1;
	con.scale = 1;

	Con_CheckResize();

	con.initialized = qtrue;

	Com_Printf( "Console initialized.\n" );
}

/*
================
Con_Shutdown
================
*/
void Con_Shutdown( void ) {
	Prompt_Clear( &con.prompt );
}


/*
===============
Con_Linefeed
===============
*/
void Con_Linefeed( void ) {
	con.x = 0;

	if( con.display == con.current )
		con.display++;
	con.current++;

	memset( con.text[con.current & CON_TOTALLINES_MASK], 0,
            sizeof( con.text[0] ) );
}

/*
================
Con_Print

Handles cursor positioning, line wrapping, etc
All console printing must go through this in order to be displayed on screen
If no console is visible, the text will appear at the top of the game window
================
*/
void Con_Print( const char *txt ) {
	int color;
	static qboolean	cr;
	char *p;
	int		l;

	if( !con.initialized )
		return;

	color = 0;
	while( *txt ) {
	// count word length
		l = 0;
		p = ( char * )txt;
		while( *p > 32 || *p == Q_COLOR_ESCAPE ) {
			if( Q_IsColorString( p ) ) {
				p += 2;
			} else {
				l++; p++;
			}
		}

	// word wrap
		p = con.text[con.current & CON_TOTALLINES_MASK];
		if( l < con.linewidth && ( Q_DrawStrlen( p ) + l > con.linewidth ) )
			con.x = 0;

		if( cr ) {
			cr = qfalse;
			con.current--;
		}
		
		if( !con.x ) {
			Con_Linefeed();

		// add color from last line
			if( color ) {
				p = con.text[con.current & CON_TOTALLINES_MASK];
				p[con.x++] = Q_COLOR_ESCAPE;
				p[con.x++] = color;
			}
		}

		switch( *txt ) {
		case '\r':
			cr = qtrue;
		case '\n':
			con.x = 0;
			//color = 0;
			break;
		case Q_COLOR_ESCAPE:
			if( !txt[1] ) {
                break;
            }
            txt++;
			color = *txt;
            p = con.text[con.current & CON_TOTALLINES_MASK];
            p[con.x++] = Q_COLOR_ESCAPE;
            p[con.x++] = color;
            break;
		default:	// display character and advance
			p = con.text[con.current & CON_TOTALLINES_MASK];
			p[con.x++] = *txt;
			if( Q_DrawStrlen( p ) > con.linewidth )
				con.x = 0;
			break;
		}

		txt++;
		
	}

	// mark time for transparent overlay
	// mark every time something is printed,
	// so long lines don't disappear to early
	if( con.current >= 0 && con.addToNotify )
		con.times[con.current & CON_TIMES_MASK] = cls.realtime;
}

/*
================
Con_Printf

Useful for printing text to graphical console only,
bypassing system console and logfiles
================
*/
void Con_Printf( const char *fmt, ... ) {
	va_list		argptr;
	char		msg[MAXPRINTMSG];

	va_start( argptr, fmt );
	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );

    Con_Print( msg );
}

/*
================
Con_SetupDC
================
*/
void Con_SetupDC( void ) {
	if( !( con.charsetImage = ref.RegisterFont( con_font->string ) ) ) {
        /* fall back to default */
		Com_WPrintf( "Couldn't load %s, falling back to default...\n", con_font->string );
		con.charsetImage = ref.RegisterFont( "conchars" );
	}
    if( !con.charsetImage ) {
        Com_Error( ERR_FATAL, "Couldn't load pics/conchars.pcx" );
    }
    ref.DrawGetFontSize( &con.charWidth, &con.charHeight, con.charsetImage );

	con.conbackImage = ref.RegisterPic( con_background->string );
	
}

/*
==============================================================================

DRAWING

==============================================================================
*/

#define CON_PRESTEP		( 10 + con.charHeight * 2 )

/*
================
Con_DrawNotify

Draws the last few lines of output transparently over the game top
================
*/
void Con_DrawNotify( void ) {
	int		v;
	char	*text;
	int		i;
	int		time;
	int		skip;
	float	alpha;
	int		flags;

	/* only draw notify in game */
	if( cls.state != ca_active ) {
		return; 
	}
	if( cls.key_dest & ( KEY_MENU|KEY_CONSOLE ) ) {
		return;
	}
	if( con.currentHeight ) {
		return;
	}

	flags = 0;

	v = 0;
	for( i = con.current - CON_TIMES + 1; i <= con.current; i++ ) {
		if( i < 0 )
			continue;
		time = con.times[i & CON_TIMES_MASK];
		if( time == 0 )
			continue;
		// alpha fade the last string left on screen
		alpha = SCR_FadeAlpha( time, con_notifytime->value * 1000, 300 );
		if( !alpha )
			continue;
		text = con.text[i & CON_TOTALLINES_MASK];

		if( v || i != con.current ) {
			alpha = 1;	// don't fade
		}

        ref.SetColor( DRAW_COLOR_ALPHA, ( byte * )&alpha );
		ref.DrawString( con.charWidth, v, flags, con.linewidth, text,
                con.charsetImage );

		v += con.charHeight;
	}
    
    ref.SetColor( DRAW_COLOR_CLEAR, NULL );

	if( cls.key_dest & KEY_MESSAGE ) {
		if( con.chatTeam ) {
			text = "say_team:";
			skip = 11;
		} else {
			text = "say:";
			skip = 5;
		}

		ref.DrawString( con.charWidth, v, flags, MAX_STRING_CHARS, text,
                con.charsetImage );
		IF_Draw( &con.chatField, skip * con.charWidth, v,
                flags | UI_DRAWCURSOR, con.charsetImage );

	}
	

}

/*
================
Con_DrawSolidConsole

Draws the console with the solid background
================
*/
void Con_DrawSolidConsole( void ) {
	int				i, y;
	int				rows;
	char			*text;
	int				row;
	char			buffer[CON_LINEWIDTH];
	int				vislines;
	float			alpha;
	int				flags;
	clipRect_t		clip;

	vislines = con.vidHeight * con.currentHeight;
	if( vislines <= 0 )
		return;

	if( vislines > con.vidHeight )
		vislines = con.vidHeight;

// setup transparency
	if( cls.state == ca_active &&
		con_alpha->value &&
        ( cls.key_dest & KEY_MENU ) == 0 )
    {
		alpha = 0.5f + 0.5f * ( con.currentHeight / con_height->value );

		Cvar_ClampValue( con_alpha, 0, 1 );
		alpha *= con_alpha->value;

		ref.SetColor( DRAW_COLOR_ALPHA, ( byte * )&alpha );
	}

	clip.left = 0;
	clip.top = 0;
	clip.right = 0;
	clip.bottom = 0;
	ref.SetClipRect( DRAW_CLIP_TOP, &clip );

// draw the background
	if( cls.state != ca_active || ( cls.key_dest & KEY_MENU ) || con_alpha->value ) {
		ref.DrawStretchPic( 0, vislines - con.vidHeight,
            con.vidWidth, con.vidHeight, con.conbackImage );
	}

// setup text rendering flags
	flags = 0;

	ref.SetColor( DRAW_COLOR_RGBA, colorCyan );

// draw clock
	if( con_clock->integer ) {
		extern void Com_Time_m( char *buffer, int bufferSize );

		Com_Time_m( buffer, sizeof( buffer ) );

		UIS_DrawStringEx( con.vidWidth - 8,
			vislines - CON_PRESTEP, flags | UI_RIGHT, MAX_STRING_CHARS,
			    buffer, con.charsetImage );
	}

// draw version
	UIS_DrawStringEx( con.vidWidth - 8,
		vislines - CON_PRESTEP + con.charHeight, flags | UI_RIGHT,
		    MAX_STRING_CHARS, APPLICATION " " VERSION, con.charsetImage );


// draw the text
	y = vislines - CON_PRESTEP;
	rows = y / con.charHeight + 1;		// rows of text to draw

// draw arrows to show the buffer is backscrolled
	if( con.display != con.current ) {
		ref.SetColor( DRAW_COLOR_RGBA, colorRed );
		for( i = 1; i < con.linewidth; i += 4 ) {
			ref.DrawChar( i * con.charWidth, y, flags, '^', con.charsetImage );
		}
	
		y -= con.charHeight;
		rows--;
	}
	
// draw from the bottom up
	ref.SetColor( DRAW_COLOR_CLEAR, NULL );
	row = con.display;
	for( i = 0; i < rows; i++ ) {
		if( row < 0 )
			break;
		if( con.current - row > CON_TOTALLINES - 1 )
			break;		// past scrollback wrap point
			
		text = con.text[row & CON_TOTALLINES_MASK];

		ref.DrawString( con.charWidth, y, flags, con.linewidth, text,
                con.charsetImage );

		y -= con.charHeight;
		row--;

	}

//ZOID
	// draw the download bar
	// figure out width
	if( cls.download ) {
		int x, n, j;

		if( ( text = strrchr( cls.downloadname, '/') ) != NULL )
			text++;
		else
			text = cls.downloadname;

		x = con.linewidth - ( ( con.linewidth * 7 ) / 40 );
		y = x - strlen( text ) - 8;
		i = con.linewidth / 3;
		if ( strlen( text ) > i ) {
			y = x - i - 11;
			strncpy( buffer, text, i );
			buffer[i] = 0;
			strcat( buffer, "..." );
		} else {
			strcpy( buffer, text );
		}
		strcat( buffer, ": " );
		i = strlen( buffer );
		buffer[i++] = '\x80';
		// where's the dot go?
		n = y * cls.downloadpercent / 100;		
		for ( j = 0; j < y; j++ ) {
			if ( j == n ) {
				buffer[i++] = '\x83';
			} else {
				buffer[i++] = '\x81';
			}
		}
		buffer[i++] = '\x82';
		buffer[i] = 0;

		sprintf( buffer + i, " %02d%%", cls.downloadpercent );

		// draw it
		y = vislines - 10;
		ref.DrawString( con.charWidth, y, 0, CON_LINEWIDTH, buffer, con.charsetImage );
	}
//ZOID

// draw the input prompt, user text, and cursor if desired
	if( cls.key_dest & KEY_CONSOLE ) {
		y = vislines - CON_PRESTEP + con.charHeight;

		// draw it
		IF_Draw( &con.prompt.inputLine, 2 * con.charWidth, y,
                flags | UI_DRAWCURSOR, con.charsetImage );

		// draw command prompt
		ref.SetColor( DRAW_COLOR_RGBA, colorYellow );
		ref.DrawChar( con.charWidth, y, flags, 17, con.charsetImage );
	}

	// restore rendering parameters
    ref.SetColor( DRAW_COLOR_CLEAR, NULL );
	ref.SetClipRect( DRAW_CLIP_DISABLED, NULL );

}

//=============================================================================

void Con_SetMaxHeight( float frac ) {
	con.maxHeight = frac;
}

/*
==================
Con_RunConsole

Scroll it up or down
==================
*/
void Con_RunConsole( void ) {
	Cvar_ClampValue( con_height, 0.1f, con.maxHeight );

	if( cls.state == ca_disconnected && !( cls.key_dest & KEY_MENU ) ) {
		/* draw fullscreen console */
		con.destHeight = con.maxHeight;
		con.currentHeight = con.destHeight;
		return;
	}

	if( cls.state > ca_disconnected && cls.state < ca_active ) {
		if( !cls.ui_initialized ) {
			/* draw half-screen console */
			con.destHeight = min( con.maxHeight, 0.5f );
			con.currentHeight = con.destHeight;
			return;
		}
	}

// decide on the height of the console
	if( cls.key_dest & KEY_CONSOLE ) {
		con.destHeight = con_height->value;		// half screen
	} else {
		con.destHeight = 0;				// none visible
	}

	if( con.currentHeight > con.destHeight ) {
		con.currentHeight -= con_speed->value * cls.frametime;
		if( con.currentHeight < con.destHeight ) {
			con.currentHeight = con.destHeight;
		}
	} else if( con.currentHeight < con.destHeight ) {
		con.currentHeight += con_speed->value * cls.frametime;
		if( con.currentHeight > con.destHeight ) {
			con.currentHeight = con.destHeight;
		}
	}
}

/*
==================
SCR_DrawConsole
==================
*/
void Con_DrawConsole( void ) {
	Cvar_ClampValue( con_scale, 1, 9 );

	con.scale = 1.0f / con_scale->value;
	ref.SetScale( &con.scale );

	Con_CheckResize();
	Con_DrawSolidConsole();
	Con_DrawNotify();	

	ref.SetScale( NULL );
	
}


/*
==============================================================================

			LINE TYPING INTO THE CONSOLE AND COMMAND COMPLETION

==============================================================================
*/

/*
====================
Key_Console

Interactive line editing and console scrollback
====================
*/
void Key_Console( int key ) {
	if( key == 'l' && Key_IsDown( K_CTRL ) ) {
		Con_Clear_f();
		return;
	}

	if( key == K_ENTER || key == K_KP_ENTER ) {
		char *cmd;
		
		if( !( cmd = Prompt_Action( &con.prompt ) ) ) {
			Con_Printf( "]\n" );
			return;
		}
		
		// backslash text are commands, else chat
		if( cmd[0] == '\\' || cmd[0] == '/' ) {
			Cbuf_AddText( cmd + 1 );	// skip slash
		} else {
			if( cls.state == ca_active && con_chat->integer ) {
				Cbuf_AddText( va( "cmd say \"%s\"", cmd ) );
			} else {
				Cbuf_AddText( cmd );
			}
		}
		Cbuf_AddText( "\n" );

		Con_Printf( "]%s\n", cmd );
	
		if( cls.state == ca_disconnected ) {
			SCR_UpdateScreen ();	// force an update, because the command
									// may take some time
		}
		return;
	}

	if( key == K_TAB ) {
		Prompt_CompleteCommand( &con.prompt, qtrue );
		return;
	}

	if( key == K_UPARROW ||
            ( key == 'p' && Key_IsDown( K_CTRL ) ) )
    {
		Prompt_HistoryUp( &con.prompt );
		return;
	}

	if( key == K_DOWNARROW ||
            ( key == 'n' && Key_IsDown( K_CTRL ) ) )
    {
		Prompt_HistoryDown( &con.prompt );
		return;
	}


	if( key == K_PGUP || key == K_MWHEELUP ) {
		if( Key_IsDown( K_CTRL ) ) {
			con.display -= 6;
		} else {
			con.display -= 2;
		}

		if( con.display < 1 ) {
			con.display = 1;
		}
		return;
	}

	if( key == K_PGDN || key == K_MWHEELDOWN ) {
		if( Key_IsDown( K_CTRL ) ) {
			con.display += 6;
		} else {
			con.display += 2;
		}
		if( con.display > con.current ) {
			con.display = con.current;
		}
		return;
	}


	if( key == K_HOME && Key_IsDown( K_CTRL ) ) {
		con.display = 1;
		return;
	}

	if( key == K_END && Key_IsDown( K_CTRL ) ) {
		con.display = con.current;
		return;
	}

	IF_KeyEvent( &con.prompt.inputLine, key );

}

void Char_Console( int key ) {
	IF_CharEvent( &con.prompt.inputLine, key );
}

/*
====================
Key_Message
====================
*/
void Key_Message( int key ) {
	if( key == 'l' && Key_IsDown( K_CTRL ) ) {
		IF_Clear( &con.chatField );
		return;
	}

	if( key == K_ENTER || key == K_KP_ENTER ) {
		Cbuf_AddText( con.chatTeam ? "say_team \"" : "say \"" );
		Cbuf_AddText( con.chatField.text );
		Cbuf_AddText( "\"\n" );
	
		Key_SetDest( cls.key_dest & ~KEY_MESSAGE );
		IF_Clear( &con.chatField );
		return;
	}

	if( key == K_ESCAPE ) {
		Key_SetDest( cls.key_dest & ~KEY_MESSAGE );
		IF_Clear( &con.chatField );
		return;
	}

	IF_KeyEvent( &con.chatField, key );
}

void Char_Message( int key ) {
	IF_CharEvent( &con.chatField, key );
}



