#ifdef WIN32
#include <windows.h>
#endif

#include "m_alloc.h"
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

#include "version.h"
#include "dstrings.h"
#include "g_game.h"
#include "c_console.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "hu_stuff.h"
#include "i_system.h"
#include "m_swap.h"
#include "v_palette.h"
#include "v_video.h"
#include "v_text.h"
#include "w_wad.h"
#include "z_zone.h"
#include "r_main.h"
#include "r_draw.h"
#include "st_stuff.h"
#include "s_sound.h"
#include "s_sndseq.h"
#include "doomstat.h"
#include "time.h"

#include "gi.h"
#include "sv_main.h" // changed - added

static void C_TabComplete (void);
static BOOL TabbedLast;		// Last key pressed was tab


static DCanvas *conback;

extern int KeyRepeatDelay;

extern int		gametic;
extern BOOL		automapactive;	// in AM_map.c
extern BOOL		advancedemo;

int			ConRows, ConCols, PhysRows;
char		*Lines, *Last = NULL;
BOOL		vidactive = false, gotconback = false;
BOOL		cursoron = false;
int			SkipRows, ConBottom, ConScroll, RowAdjust;
int			CursorTicker, ScrollState = 0;
constate_e	ConsoleState = c_up;
char		VersionString[8];

event_t		RepeatEvent;		// always type ev_keydown
int			RepeatCountdown;
BOOL		KeysShifted;

static bool midprinting;


#define SCROLLUP 1
#define SCROLLDN 2
#define SCROLLNO 0


static unsigned int TickerAt, TickerMax;
static const char *TickerLabel;

struct History
{
	struct History *Older;
	struct History *Newer;
	char String[1];
};

// CmdLine[0]  = # of chars on command line
// CmdLine[1]  = cursor position
// CmdLine[2+] = command line (max 255 chars + NULL)
// CmdLine[259]= offset from beginning of cmdline to display
static byte CmdLine[260];

static byte printxormask;

#define MAXHISTSIZE 50
static struct History *HistHead = NULL, *HistTail = NULL, *HistPos = NULL;
static int HistSize;

#define NUMNOTIFIES 4

CVAR (con_notifytime, "3", CVAR_ARCHIVE)
CVAR (con_scaletext, "0", CVAR_ARCHIVE)		// Scale notify text at high resolutions?

static struct NotifyText
{
	int timeout;
	int printlevel;
	byte text[256];
} NotifyStrings[NUMNOTIFIES];

#define PRINTLEVELS 5
int PrintColors[PRINTLEVELS+1] = { CR_RED, CR_GOLD, CR_GRAY, CR_GREEN, CR_GREEN, CR_GOLD };

static void setmsgcolor (int index, const char *color);

FILE *Logfile = NULL;


BOOL C_HandleKey (event_t *ev, byte *buffer, int len);


cvar_t msglevel ("msg", "0", CVAR_ARCHIVE);

BEGIN_CUSTOM_CVAR (msg0color, "6", CVAR_ARCHIVE)
{
	setmsgcolor (0, var.string);
}
END_CUSTOM_CVAR (msg0color)

BEGIN_CUSTOM_CVAR (msg1color, "5", CVAR_ARCHIVE)
{
	setmsgcolor (1, var.string);
}
END_CUSTOM_CVAR (msg1color)

BEGIN_CUSTOM_CVAR (msg2color, "2", CVAR_ARCHIVE)
{
	setmsgcolor (2, var.string);
}
END_CUSTOM_CVAR (msg2color)

BEGIN_CUSTOM_CVAR (msg3color, "3", CVAR_ARCHIVE)
{
	setmsgcolor (3, var.string);
}
END_CUSTOM_CVAR (msg3color)

BEGIN_CUSTOM_CVAR (msg4color, "3", CVAR_ARCHIVE)
{
	setmsgcolor (4, var.string);
}
END_CUSTOM_CVAR (msg4color)

BEGIN_CUSTOM_CVAR (msgmidcolor, "5", CVAR_ARCHIVE)
{
	setmsgcolor (PRINTLEVELS, var.string);
}
END_CUSTOM_CVAR (msgmidcolor)

static void maybedrawnow (void)
{
}

void C_InitConsole (int width, int height, BOOL ingame)
{
	int row;
	char *zap;
	char *old;
	int cols, rows;

	if ( (vidactive = ingame) )
	{
		if (!gotconback)
		{
			BOOL stylize = false;
			BOOL isRaw = false;
			patch_t *bg;
			int num;

			num = W_CheckNumForName ("CONBACK");
			if (num == -1)
			{
				stylize = true;
				num = W_GetNumForName (gameinfo.titlePage);
				isRaw = gameinfo.flags & GI_PAGESARERAW;
			}

			bg = (patch_t *)W_CacheLumpNum (num, PU_CACHE);

			if (isRaw)
				conback = new DCanvas (320, 200, 8);
			else
				conback = new DCanvas (SHORT(bg->width), SHORT(bg->height), 8);

			conback->Lock ();

			if (isRaw)
				conback->DrawBlock (0, 0, 320, 200, (byte *)bg);
			else
				conback->DrawPatch (bg, 0, 0);

			if (stylize)
			{
				byte *fadetable = (byte *)W_CacheLumpName ("COLORMAP", PU_CACHE), f, *v, *i;
				int x, y;

				for (y = 0; y < conback->height; y++)
				{
					i = conback->buffer + conback->pitch * y;
					if (y < 8 || y > 191)
					{
						if (y < 8)
							f = y;
						else
							f = 199 - y;
						v = fadetable + (30 - f) * 256;
						for (x = 0; x < conback->width; x++)
						{
							*i = v[*i];
							i++;
						}
					}
					else
					{
						for (x = 0; x < conback->width; x++)
						{
							if (x <= 8)
								v = fadetable + (30 - x) * 256;
							else if (x > 312)
								v = fadetable + (x - 289) * 256;
							*i = v[*i];
							i++;
						}
					}
				}
			}

			VersionString[0] = 0x11;
			size_t i;
			for (i = 0; i < strlen(DOTVERSIONSTR); i++)
				VersionString[i+1] = ((DOTVERSIONSTR[i]>='0'&&DOTVERSIONSTR[i]<='9')||DOTVERSIONSTR[i]=='.')?DOTVERSIONSTR[i]-30:DOTVERSIONSTR[i] ^ 0x80;
			VersionString[i+1] = 0;

			conback->Unlock ();

			gotconback = true;
		}
	}

	cols = ConCols;
	rows = ConRows;

	ConCols = width / 8 - 2;
	PhysRows = height / 8;
	ConRows = PhysRows * 10;

	old = Lines;
	Lines = (char *)Malloc (ConRows * (ConCols + 2) + 1);

	for (row = 0, zap = Lines; row < ConRows; row++, zap += ConCols + 2)
	{
		zap[0] = 0;
		zap[1] = 0;
	}

	Last = Lines + (ConRows - 1) * (ConCols + 2);

	if (old)
	{
		char string[256];
		gamestate_t oldstate = gamestate;	// Don't print during reformatting
		FILE *templog = Logfile;		// Don't log our reformatting pass

		gamestate = GS_FORCEWIPE;
		Logfile = NULL;

		for (row = 0, zap = old; row < rows; row++, zap += cols + 2)
		{
			memcpy (string, &zap[2], zap[1]);
			if (!zap[0])
			{
				string[zap[1]] = '\n';
				string[zap[1]+1] = 0;
			}
			else
			{
				string[zap[1]] = 0;
			}
			Printf (PRINT_HIGH, "%s", string);
		}
		free (old);
		C_FlushDisplay ();

		Logfile = templog;
		gamestate = oldstate;
	}

	if (ingame && gamestate == GS_STARTUP)
		C_FullConsole ();
}

static void setmsgcolor (int index, const char *color)
{
	int i;

	i = atoi (color);
	if (i < 0 || i >= NUM_TEXT_COLORS)
		i = 0;
	PrintColors[index] = i;
}

extern int DisplayWidth;

void C_AddNotifyString (int printlevel, const char *source)
{
}

CVAR (sv_log, "1", CVAR_SERVERINFO)

/* Provide our own Printf() that is sensitive of the
 * console status (in or out of game)
 */
int AddError(char *String) // added
{
	FILE *fp = fopen("log.txt", "aw");

	if(fp)
	{
		fprintf(fp, "%s\n", (char *)String, strlen(String));
		fclose(fp);
	}

	return(1);
}

#ifdef WIN32

extern HWND hWnd; // changed - added

int PrintString (int printlevel, char *outline)
{
	static bool last_string_terminated = true;
	char szBuffer[9000]; // thanks to Luigi Auriemma for finding an overflow bug here

	// If no string, return
	if(!strlen((char *)outline) || outline[0] == '\n')return(0);

	// Get timestamp
	time_t ti = time(NULL);
	tm *lt = localtime((time_t *)&ti);
	// Output
	if(last_string_terminated == true)
		sprintf (szBuffer, "[%.2d:%.2d:%.2d] %s", lt->tm_hour, lt->tm_min, lt->tm_sec, (char *)outline);
	else
		sprintf (szBuffer, "%s", (char *)outline);

	// changed - win32
	if(printlevel != PRINT_LOG)
		{
		// If just file logging - don't bother dumping on screen
		// Send to our resident rcon players
		client_t *cl;
	    for (int i=0; i<MAXPLAYERS; i++)
			{
			cl = &clients[i];
			if (cl->cls < cs_spawned)
				continue;        
			if (!cl->allow_rcon)
				continue;        
			MSG_WriteByte (&cl->reliablebuf, svc_print);
			MSG_WriteByte (&cl->reliablebuf, PRINT_MEDIUM);
			MSG_WriteString (&cl->reliablebuf, szBuffer);
			}
		// remove '/n'
		if(szBuffer[strlen(szBuffer) - 1] == '\n')szBuffer[strlen(szBuffer) - 1] = false;
		// Now display to console
#ifdef _CONSOLE
		printf("%s\r\n", szBuffer);
#else
		unsigned int x = SendMessage(hWnd, LB_INSERTSTRING, false, (LPARAM)(LPCSTR)szBuffer);
		PostMessage(hWnd, LB_SETCURSEL, x, false);
#endif
		}
	// remove '/n'
	if(szBuffer[strlen(szBuffer) - 1] == '\n')szBuffer[strlen(szBuffer) - 1] = false;
	// append to log
	if(sv_log.value)
	{
		AddError(szBuffer);
	}

return strlen (szBuffer);
}

#else

int PrintString (int printlevel, char *outline)
{
	static bool last_string_terminated = true;
	char szBuffer[9000];  // thanks to Luigi Auriemma for finding an overflow bug here

	// If no string, return
	if(!strlen((char *)outline) || outline[0] == '\n')return(0);

	// Get timestamp
	time_t ti = time(NULL);
	tm *lt = localtime((time_t *)&ti);
	// Output
	if(last_string_terminated == true)
		sprintf (szBuffer, "[%.2d:%.2d:%.2d] %s", lt->tm_hour, lt->tm_min, lt->tm_sec, (char *)outline);
	else
		sprintf (szBuffer, "%s", (char *)outline);

	// changed
	if(printlevel != PRINT_LOG)
		{
		// If just file logging - don't bother dumping on screen
		// Send to our resident rcon players
		client_t *cl;
	    for (int i=0; i<MAXPLAYERS; i++)
			{
			cl = &clients[i];
			if (cl->cls < cs_spawned)
				continue;        
			if (!cl->allow_rcon)
				continue;        
			MSG_WriteByte (&cl->reliablebuf, svc_print);
			MSG_WriteByte (&cl->reliablebuf, PRINT_MEDIUM);
			MSG_WriteString (&cl->reliablebuf, szBuffer);
			}
		// remove '/n'
		if(szBuffer[strlen(szBuffer) - 1] == '\n')szBuffer[strlen(szBuffer) - 1] = false;
		// Now display to console
		//unsigned int x = SendMessage(hWnd, LB_INSERTSTRING, false, (LPARAM)(LPCSTR)szBuffer);
		//PostMessage(hWnd, LB_SETCURSEL, x, false);
		printf("%s\r\n", szBuffer);
		}
	// remove '/n'
	if(szBuffer[strlen(szBuffer) - 1] == '\n')szBuffer[strlen(szBuffer) - 1] = false;
	// append to log
	if(sv_log.value)
	{
		AddError(szBuffer);
	}

return strlen (szBuffer);
}

#endif

extern BOOL gameisdead;

int VPrintf (int printlevel, const char *format, va_list parms)
{
	char outline[8192];

	if (gameisdead)
		return 0;

	vsprintf (outline, format, parms);
	return PrintString (printlevel, outline);
}

int STACK_ARGS Printf (int printlevel, const char *format, ...)
{
	va_list argptr;
	int count;
	va_start (argptr, format);
	count = VPrintf (printlevel, format, argptr);
	va_end (argptr);

	return count;
}

int STACK_ARGS Printf_Bold (const char *format, ...)
{
	va_list argptr;
	int count;

	printxormask = 0x80;
	va_start (argptr, format);
	count = VPrintf (PRINT_HIGH, format, argptr);
	va_end (argptr);

	return count;
}

int STACK_ARGS DPrintf (const char *format, ...)
{
	va_list argptr;
	int count;

	if (developer.value)
	{
		va_start (argptr, format);
		count = VPrintf (PRINT_HIGH, format, argptr);
		va_end (argptr);
		return count;
	}
	else
	{
		return 0;
	}
}

void C_FlushDisplay (void)
{
	int i;

	for (i = 0; i < NUMNOTIFIES; i++)
		NotifyStrings[i].timeout = 0;
}

void C_AdjustBottom (void)
{
}

void C_NewModeAdjust (void)
{
}

void C_Ticker (void)
{
	static int lasttic = 0;

	if (lasttic == 0)
		lasttic = gametic - 1;

	if (ConsoleState != c_up)
	{
		// Handle repeating keys
		switch (ScrollState)
		{
			case SCROLLUP:
				RowAdjust++;
				break;

			case SCROLLDN:
				if (RowAdjust)
					RowAdjust--;
				break;

			default:
				break;
		}

		if (ConsoleState == c_falling)
		{
			ConBottom += (gametic - lasttic) * (screen->height*2/25);
			if (ConBottom >= screen->height / 2)
			{
				ConBottom = screen->height / 2;
				ConsoleState = c_down;
			}
		} else if (ConsoleState == c_rising)
		{
			ConBottom -= (gametic - lasttic) * (screen->height*2/25);
			if (ConBottom <= 0)
			{
				ConsoleState = c_up;
				ConBottom = 0;
			}
		}

		if (SkipRows + RowAdjust + (ConBottom/8) + 1 > ConRows)
		{
			RowAdjust = ConRows - SkipRows - ConBottom;
		}
	}

	if (--CursorTicker <= 0)
	{
		cursoron ^= 1;
		CursorTicker = C_BLINKRATE;
	}

	lasttic = gametic;
}

static void C_DrawNotifyText (void)
{
}

void C_InitTicker (const char *label, unsigned int max)
{
	TickerMax = max;
	TickerLabel = label;
	TickerAt = 0;
	maybedrawnow ();
}

void C_SetTicker (unsigned int at)
{
	TickerAt = at > TickerMax ? TickerMax : at;
	maybedrawnow ();
}

void C_DrawConsole (void)
{
}

void C_FullConsole (void)
{
	if (demoplayback)
		G_CheckDemoStatus ();
	advancedemo = false;
	ConsoleState = c_down;
	HistPos = NULL;
	TabbedLast = false;
	if (gamestate != GS_STARTUP)
	{
		gamestate = GS_FULLCONSOLE;
		level.music[0] = '\0';
		S_Start ();
	} else
		C_AdjustBottom ();
}

void C_ToggleConsole (void)
{
	if (gamestate == GS_DEMOSCREEN || demoplayback)
	{
		gameaction = ga_fullconsole;
	}
	else if (!chatmodeon && (ConsoleState == c_up || ConsoleState == c_rising))
	{
		ConsoleState = c_falling;
		HistPos = NULL;
		TabbedLast = false;
//		I_PauseMouse ();
	}
	else if (gamestate != GS_FULLCONSOLE && gamestate != GS_STARTUP)
	{
		ConsoleState = c_rising;
//		C_FlushDisplay ();
//		I_ResumeMouse ();
	}
}

void C_HideConsole (void)
{
	if (gamestate != GS_FULLCONSOLE && gamestate != GS_STARTUP)
	{
		ConsoleState = c_up;
		ConBottom = 0;
		HistPos = NULL;
//		if (!menuactive)
//			I_ResumeMouse ();
	}
}

static void makestartposgood (void)
{
	int n;
	int pos = CmdLine[259];
	int curs = CmdLine[1];
	int len = CmdLine[0];

	n = pos;

	if (pos >= len)
	{ // Start of visible line is beyond end of line
		n = curs - ConCols + 2;
	}
	if ((curs - pos) >= ConCols - 2)
	{ // The cursor is beyond the visible part of the line
		n = curs - ConCols + 2;
	}
	if (pos > curs)
	{ // The cursor is in front of the visible part of the line
		n = curs;
	}
	if (n < 0)
		n = 0;
	CmdLine[259] = n;
}

BOOL C_HandleKey (event_t *ev, byte *buffer, int len)
{
	return true;
}

BOOL C_Responder (event_t *ev)
{
	return false;
}

BEGIN_COMMAND (echo)
{
	if (argc > 1)
	{
		char *str = BuildString (argc - 1, argv + 1);
		Printf (PRINT_HIGH, "%s\n", str);
		delete[] str;
	}
}
END_COMMAND (echo)

/* Printing in the middle of the screen */

static brokenlines_t *MidMsg = NULL;
static int MidTicker = 0, MidLines;
CVAR (con_midtime, "3", CVAR_ARCHIVE)

void C_MidPrint (char *msg)
{
	client_t *cl = &clients[parse_cl];

	MSG_WriteByte (&cl->reliablebuf, svc_print);
	MSG_WriteByte (&cl->reliablebuf, PRINT_HIGH);
	MSG_WriteUnterminatedString (&cl->reliablebuf, msg);
	MSG_WriteString (&cl->reliablebuf, "\n");
}

void C_DrawMid (void)
{
}


/****** Tab completion code ******/

static struct TabData
{
	int UseCount;
	char *Name;
} *TabCommands = NULL;
static int NumTabCommands = 0;
static int TabPos;				// Last TabCommand tabbed to
static int TabStart;			// First char in CmdLine to use for tab completion
static int TabSize;				// Size of tab string

static BOOL FindTabCommand (const char *name, int *stoppos, int len)
{
	int i, cval = 1;

	for (i = 0; i < NumTabCommands; i++)
	{
		cval = strnicmp (TabCommands[i].Name, name, len);
		if (cval >= 0)
			break;
	}

	*stoppos = i;

	return (cval == 0);
}

void C_AddTabCommand (const char *name)
{
	int pos;

	if (FindTabCommand (name, &pos, MAXINT))
	{
		TabCommands[pos].UseCount++;
	}
	else
	{
		NumTabCommands++;
		TabCommands = (TabData *)Realloc (TabCommands, sizeof(struct TabData) * NumTabCommands);
		if (pos < NumTabCommands - 1)
			memmove (TabCommands + pos + 1, TabCommands + pos,
					 (NumTabCommands - pos - 1) * sizeof(struct TabData));
		TabCommands[pos].Name = copystring (name);
		TabCommands[pos].UseCount = 1;
	}
}

void C_RemoveTabCommand (const char *name)
{
	int pos;

	if (FindTabCommand (name, &pos, MAXINT))
	{
		if (--TabCommands[pos].UseCount == 0)
		{
			NumTabCommands--;
			delete[] TabCommands[pos].Name;
			if (pos < NumTabCommands)
				memmove (TabCommands + pos, TabCommands + pos + 1,
						 (NumTabCommands - pos) * sizeof(struct TabData));
		}
	}
}

static int FindDiffPoint (const char *str1, const char *str2)
{
	int i;

	for (i = 0; tolower(str1[i]) == tolower(str2[i]); i++)
		if (str1[i] == 0 || str2[i] == 0)
			break;

	return i;
}

static void C_TabComplete (void)
{
	int i;
	int diffpoint;

	if (!TabbedLast)
	{
		// Skip any spaces at beginning of command line
		if (CmdLine[2] == ' ')
		{
			for (i = 0; i < CmdLine[0]; i++)
				if (CmdLine[2+i] != ' ')
					break;

			TabStart = i + 2;
		}
		else
		{
			TabStart = 2;
		}

		if (TabStart == CmdLine[0] + 2)
			return;		// Line was nothing but spaces

		TabSize = CmdLine[0] - TabStart + 2;

		if (!FindTabCommand ((char *)(CmdLine + TabStart), &TabPos, TabSize))
			return;		// No initial matches

		TabPos--;
		TabbedLast = true;
	}

	if (++TabPos == NumTabCommands)
	{
		TabbedLast = false;
		CmdLine[0] = CmdLine[1] = TabSize;
	}
	else
	{
		diffpoint = FindDiffPoint (TabCommands[TabPos].Name, (char *)(CmdLine + TabStart));

		if (diffpoint < TabSize)
		{
			// No more matches
			TabbedLast = false;
			CmdLine[0] = CmdLine[1] = TabSize + TabStart - 2;
		}
		else
		{		
			strcpy ((char *)(CmdLine + TabStart), TabCommands[TabPos].Name);
			CmdLine[0] = CmdLine[1] = strlen ((char *)(CmdLine + 2)) + 1;
			CmdLine[CmdLine[0] + 1] = ' ';
		}
	}

	makestartposgood ();
}
