/*
 * 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"

console_t	con;

cvar_t         *con_notify;
cvar_t         *con_notifytime;
cvar_t         *con_transparency;


#define		MAXCMDLINE	256
extern char	key_lines[32][MAXCMDLINE];
extern int	edit_line;
extern int	key_linepos;


void
DrawString(int x, int y, char *s)
{
	while (*s) {
		re.DrawChar(x, y, *s, 256);
		x += 8;
		s++;
	}
}

void
DrawAltString(int x, int y, char *s)
{
	while (*s) {
		re.DrawChar(x, y, *s ^ 0x80, 256);
		x += 8;
		s++;
	}
}


void
Key_ClearTyping(void)
{
	key_lines[edit_line][1] = 0;	/* clear any typing */
	key_linepos = 1;
}

/*
 * ================ Con_ToggleConsole_f ================
 */
void
Con_ToggleConsole_f(void)
{
	SCR_EndLoadingPlaque();	/* get rid of loading plaque */

	if (cl.attractloop) {
		Cbuf_AddText("killserver\n");
		return;
	}
	if (cls.state == ca_disconnected) {	/* start the demo loop again */
		Cbuf_AddText("d1\n");
		return;
	}
	Key_ClearTyping();
	Con_ClearNotify();

	if (cls.key_dest == key_console) {
		M_ForceMenuOff();
		Cvar_Set("paused", "0");
	} else {
		M_ForceMenuOff();
		cls.key_dest = key_console;

		if (Cvar_VariableValue("maxclients") == 1
		    && Com_ServerState())
			Cvar_Set("paused", "1");
	}
}

/*
 * ================ Con_ToggleChat_f ================
 */
void
Con_ToggleChat_f(void)
{
	Key_ClearTyping();
	if (cls.key_dest == key_console) 
	{
		if (cls.state == ca_active) {
			M_ForceMenuOff();
			cls.key_dest = key_game;
		}
	} else
		cls.key_dest = key_console;
	Con_ClearNotify();
}

/*
 * ================ Con_Clear_f ================
 */
void
Con_Clear_f(void)
{
	memset(con.text, ' ', CON_TEXTSIZE);
}


/*
 * ================
 * Con_Dump_f
 *
 * Save the console contents out to a file.
 * ================
 */
void
Con_Dump_f(void)
{
	int		l, x;
	char           *line;
	FILE           *f;
	char		buffer[1024];
	char		name[MAX_OSPATH];

	if (Cmd_Argc() != 2) {
		Com_Printf("usage: condump <filename>\n");
		return;
	}
	Com_sprintf(name, sizeof(name), "%s/%s.txt", FS_Gamedir(), Cmd_Argv(1));

	Com_Printf("Dumped console text to %s.\n", name);
	FS_CreatePath(name);
	f = fopen(name, "w");
	if (!f) {
		Com_Printf("ERROR: couldn't open.\n");
		return;
	}
	/* skip empty lines */
	for (l = con.current - con.totallines + 1; l <= con.current; l++) {
		line = con.text + (l % con.totallines) * con.linewidth;
		for (x = 0; x < con.linewidth; x++)
			if (line[x] != ' ')
				break;
		if (x != con.linewidth)
			break;
	}

	/* write the remaining lines */
	buffer[con.linewidth] = 0;
	for (; l <= con.current; l++) {
		line = con.text + (l % con.totallines) * con.linewidth;
		Q_strncpyz(buffer, line, con.linewidth);
		for (x = con.linewidth - 1; x >= 0; x--) {
			if (buffer[x] == ' ')
				buffer[x] = 0;
			else
				break;
		}
		for (x = 0; buffer[x]; x++)
			buffer[x] &= 0x7f;

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

	fclose(f);
}


/*
 * ================ Con_ClearNotify ================
 */
void
Con_ClearNotify(void)
{
	int		i;

	for (i = 0; i < NUM_CON_TIMES; i++)
		con.times[i] = 0;
}


/*
 * ================ Con_MessageMode_f ================
 */
void
Con_MessageMode_f(void)
{
	chat_team = false;
	cls.key_dest = key_message;
}

/*
 * ================ Con_MessageMode2_f ================
 */
void
Con_MessageMode2_f(void)
{
	chat_team = true;
	cls.key_dest = key_message;
}

/*
 * ================ Con_CheckResize
 *
 * If the line width has changed, reformat the buffer. ================
 */
void
Con_CheckResize(void)
{
	int		i         , j, width, oldwidth, oldtotallines, numlines, numchars;
	char		tbuf      [CON_TEXTSIZE];

	width = (viddef.width >> 3) - 2;

	if (width == con.linewidth)
		return;

	if (width < 1) {	/* video hasn't been initialized yet */
		width = 38;
		con.linewidth = width;
		con.totallines = CON_TEXTSIZE / con.linewidth;
		memset(con.text, ' ', CON_TEXTSIZE);
	} else {
		oldwidth = con.linewidth;
		con.linewidth = width;
		oldtotallines = con.totallines;
		con.totallines = CON_TEXTSIZE / con.linewidth;
		numlines = oldtotallines;

		if (con.totallines < numlines)
			numlines = con.totallines;

		numchars = oldwidth;

		if (con.linewidth < numchars)
			numchars = con.linewidth;

		memcpy(tbuf, con.text, CON_TEXTSIZE);
		memset(con.text, ' ', CON_TEXTSIZE);

		for (i = 0; i < numlines; i++) {
			for (j = 0; j < numchars; j++) {
				con.text[(con.totallines - 1 - i) * con.linewidth + j] =
				    tbuf[((con.current - i + oldtotallines) %
				    oldtotallines) * oldwidth + j];
			}
		}

		Con_ClearNotify();
	}

	con.current = con.totallines - 1;
	con.display = con.current;
}


/*
 * ================ Con_Init ================
 */
void
Con_Init(void)
{
	con.linewidth = -1;

	Con_CheckResize();

	Com_Printf("Console initialized.\n");

	//
	/* register our commands */
	//
	con_notify = Cvar_Get("con_notify", "1", 0);
	con_notifytime = Cvar_Get("con_notifytime", "3", 0);
	con_transparency = Cvar_Get("con_transparency", "0.5", CVAR_ARCHIVE);

	Cmd_AddCommand("toggleconsole", Con_ToggleConsole_f);
	Cmd_AddCommand("togglechat", Con_ToggleChat_f);
	Cmd_AddCommand("messagemode", Con_MessageMode_f);
	Cmd_AddCommand("messagemode2", Con_MessageMode2_f);
	Cmd_AddCommand("clear", Con_Clear_f);
	Cmd_AddCommand("condump", Con_Dump_f);
	con.initialized = true;
}


/*
 * =============== 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) * con.linewidth]
	    ,' ', con.linewidth);
}

/*
 * ================ Con_Print
 *
 * Handles cursor positioning, line wrapping, etc All console printing must go
 * through this in order to be logged to disk If no console is visible, the
 * text will appear at the top of the game window ================
 */
void
Con_Print(char *txt)
{
	int		y;
	int		c         , l;
	static int	cr;
	int		mask;

	if (!con.initialized)
		return;

	if (txt[0] == 1 || txt[0] == 2) {
		mask = 128;	/* go to colored text */
		txt++;
	} else
		mask = 0;


	while ((c = *txt)) {
		/* count word length */
		for (l = 0; l < con.linewidth; l++)
			if (txt[l] <= ' ')
				break;

		/* word wrap */
		if (l != con.linewidth && (con.x + l > con.linewidth))
			con.x = 0;

		txt++;

		if (cr) {
			con.current--;
			cr = false;
		}
		if (!con.x) {
			Con_Linefeed();
			/* mark time for transparent overlay */
			if (con.current >= 0)
				con.times[con.current % NUM_CON_TIMES] = cls.realtime;
		}
		switch (c) {
		case '\n':
			con.x = 0;
			break;

		case '\r':
			con.x = 0;
			cr = 1;
			break;

		default:	/* display character and advance */
			y = con.current % con.totallines;
			con.text[y * con.linewidth + con.x] = c | mask | con.ormask;
			con.x++;
			if (con.x >= con.linewidth)
				con.x = 0;
			break;
		}

	}
}


/*
 * ============== Con_CenteredPrint ==============
 */
void
Con_CenteredPrint(char *text)
{
	int		l;
	char		buffer[1024];

	l = strlen(text);
	l = (con.linewidth - l) / 2;
	if (l < 0)
		l = 0;
	memset(buffer, ' ', l);
	Q_strncpyz(buffer + l, text, sizeof(buffer));
	strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1);
	Con_Print(buffer);
}

/*
 *
 * ============================================================================
 *
 * DRAWING
 *
 * ======================================================================
 */


/*
 * ================
 * Con_DrawInput
 *
 * The input line scrolls horizontally if typing goes beyond the right edge.
 * ================
 */
void
Con_DrawInput(void)
{
	int		y;
	int		i;
	char           *text;
	
	if (cls.key_dest == key_menu)
		return;
	if (cls.key_dest != key_console && cls.state == ca_active)
		return;		/* don't draw anything (always draw if not active) */
	text = key_lines[edit_line];

	/* add the cursor frame */
	text[key_linepos] = 10 + ((int)(cls.realtime >> 8) & 1);

	/* fill out remainder with spaces */
	for (i = key_linepos + 1; i < con.linewidth; i++)
		text[i] = ' ';

	/* prestep if horizontally scrolling */
	if (key_linepos >= con.linewidth)
		text += 1 + key_linepos - con.linewidth;

	/* draw it */
	y = con.vislines - 16;

	for (i = 0; i < con.linewidth; i++)
		re.DrawChar((i + 1) << 3, con.vislines - 22, text[i], 256);

	/* remove cursor */
	key_lines[edit_line][key_linepos] = 0;
}


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

	v = 0;
	for (i = con.current - NUM_CON_TIMES + 1; i <= con.current; i++) {
		if (i < 0)
			continue;
		time = con.times[i % NUM_CON_TIMES];
		if (time == 0)
			continue;
		time = cls.realtime - time;
		if (time > con_notifytime->value * 1000)
			continue;
		text = con.text + (i % con.totallines) * con.linewidth;

		for (x = 0; x < con.linewidth; x++)
			if (con_notify->value)
				re.DrawChar((x + 1) << 3, v, text[x], 256);
				v += 8;
	}


	if (cls.key_dest == key_message) {
		if (chat_team) {
			DrawString(8, v, "say_team:");
			skip = 11;
		} else {
			DrawString(8, v, "say:");
			skip = 5;
		}

		s = chat_buffer;
		if (chat_bufferlen > (viddef.width >> 3) - (skip + 1))
			s += chat_bufferlen - ((viddef.width >> 3) - (skip + 1));
		x = 0;
		while (s[x]) {
			re.DrawChar((x + skip) << 3, v, s[x], 256);
			x++;
		}
		re.DrawChar((x + skip) << 3, v, 10 + ((cls.realtime >> 8) & 1), 256);
		v += 8;
	}
	if (v) {
		SCR_AddDirtyPoint(0, 0);
		SCR_AddDirtyPoint(viddef.width - 1, v);
	}
}

/*
 * ================ Con_DrawConsole
 *
 * Draws the console with the solid background ================
 */
void
Con_DrawConsole(float frac, qboolean in_game)
{
	int		i, j, x, y, n;
	int		rows;
	char           *text;
	int		row;
	int		lines;
	char		version[64];
	char		dlbar[1024];
	float		alpha;
	char           *conback_image;
	int		length;

	struct tm      *ntime;
	char		stime[32];
	time_t		l_time;

	lines = viddef.height * frac;
	if (lines <= 0)
		return;

	if (lines > viddef.height)
		lines = viddef.height;

	if (!in_game)
		alpha = 1;
	else
		alpha = con_transparency->value;

	/* draw the background */

	conback_image = cl_conback_image->string;

	re.DrawStretchPic(0, -viddef.height + lines, viddef.width, viddef.height, conback_image, alpha);
	SCR_AddDirtyPoint(0, 0);
	SCR_AddDirtyPoint(viddef.width - 1, lines - 1);

	Com_sprintf(version, sizeof(version), "%s", QUDOS_VERSION);
	length = strlen(QUDOS_VERSION);
	for (x = 0; x < length; x++)
		re.DrawChar(viddef.width - 4 -length * 8 + x * 8, lines - 12, 128 + QUDOS_VERSION[x], 256);

	time(&l_time);
	ntime = localtime(&l_time);
	strftime(stime, sizeof(stime), "%a, %d %b %Y - %H:%M:%S", ntime);
	for (x = 0; x < 50; ++x)
		re.DrawChar(viddef.width - 215 + x * 8, 0, 128 + stime[x], 256);

	/* draw the text */
	con.vislines = lines;
	rows = (lines - 22) >> 3;	/* rows of text to draw */

	y = lines - 30;

	/* draw from the bottom up */
	if (con.display != con.current) {
		/* draw arrows to show the buffer is backscrolled */
		for (x = 0; x < con.linewidth; x += 4)
			re.DrawChar((x + 1) << 3, y, '^', 256);
		y -= 8;
		rows--;
	}
	row = con.display;
	for (i = 0; i < rows; i++, y -= 8, row--) {
		if (row < 0)
			break;
		if (con.current - row >= con.totallines)
			break;	/* past scrollback wrap point */

		text = con.text + (row % con.totallines) * con.linewidth;

		for (x = 0; x < con.linewidth; x++)
			re.DrawChar((x + 1) << 3, y, text[x], 256);
	}

	/* ZOID */
	/* draw the download bar */
	/* figure out width */
	if (cls.download) {
		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;
			Q_strncpyz(dlbar, text, i);
			strncat(dlbar, "...", sizeof(dlbar) - strlen(dlbar) - 1);
		} else
			Q_strncpyz(dlbar, text, sizeof(dlbar));
		strncat(dlbar, ": ", sizeof(dlbar) - strlen(dlbar) - 1);
		i = strlen(dlbar);
		dlbar[i++] = '\x80';
		/* where's the dot go? */
		if (cls.downloadpercent == 0)
			n = 0;
		else
			n = y * cls.downloadpercent / 100;

		for (j = 0; j < y; j++)
			if (j == n)
				dlbar[i++] = '\x83';
			else
				dlbar[i++] = '\x81';
		dlbar[i++] = '\x82';
		dlbar[i] = 0;

		Com_sprintf(dlbar + strlen(dlbar), sizeof(dlbar) - strlen(dlbar), " %02d%%", cls.downloadpercent);

		/* draw it */
		y = con.vislines - 12;
		for (i = 0; i < strlen(dlbar); i++)
			re.DrawChar((i + 1) << 3, y, dlbar[i], 256);
	}
	/* ZOID */

	/* draw the input prompt, user text, and cursor if desired */
	Con_DrawInput();
}
