/*
sys_con.c - win32 dedicated and developer console
Copyright (C) 2007 Uncle Mike

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 3 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.
*/

#include "common.h"
#include "xash3d_mathlib.h"

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

WIN32 CONSOLE

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

// console defines
#define COMMAND_HISTORY	64	// system console keep more commands than game console

typedef struct
{
	string		title;
	string		previousTitle;
	UINT		previousCodePage;
	UINT		previousOutputCodePage;
	HWND		hWnd;
	HANDLE		hInput;
	HANDLE		hOutput;
	int			inputLine;
	int			browseLine;
	int			cursorPosition;
	int			totalLines;
	int			savedConsoleTextLen;
	int			consoleTextLen;
	string		consoleText;
	string		savedConsoleText;
	string		statusLine;
	string		lineBuffer[COMMAND_HISTORY];
	qboolean	inputEnabled;
	qboolean	consoleVisible;
	qboolean	attached;

	// log stuff
	qboolean	log_active;
	char		log_path[MAX_SYSPATH];
} WinConData;

static WinConData	s_wcd;
static WORD g_color_table[8] =
{
FOREGROUND_INTENSITY,									// black
FOREGROUND_RED,											// red
FOREGROUND_GREEN,										// green
FOREGROUND_RED | FOREGROUND_GREEN,						// yellow
FOREGROUND_BLUE | FOREGROUND_INTENSITY,					// blue
FOREGROUND_GREEN | FOREGROUND_BLUE,						// cyan
FOREGROUND_RED | FOREGROUND_BLUE,						// magenta
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,	// default color (white)
};

static BOOL WINAPI Wcon_HandleConsole(DWORD CtrlType)
{
	return TRUE;
}

static void Wcon_PrintInternal( const char *msg, int length )
{
	char *pTemp;
	DWORD cbWritten;
	const char *pMsgString;
	static char tmpBuf[2048];
	static char szOutput[2048];

	Q_strncpy( szOutput, msg, length ? (length + 1) : ( sizeof( szOutput ) - 1 ));
	if( length )
		szOutput[length + 1] = '\0';
	else
		szOutput[sizeof( szOutput ) - 1] = '\0';

	pTemp = tmpBuf;
	pMsgString = szOutput;
	while( pMsgString && *pMsgString )
	{
		if( IsColorString( pMsgString ))
		{
			if (( pTemp - tmpBuf ) > 0 )
			{
				// dump accumulated text before change color
				*pTemp = 0; // terminate string
				WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 );
				pTemp = tmpBuf;
			}

			// set new color
			SetConsoleTextAttribute( s_wcd.hOutput, g_color_table[ColorIndex(*(pMsgString + 1))] );
			pMsgString += 2; // skip color info
		}
		else if(( pTemp - tmpBuf ) < sizeof( tmpBuf ) - 1 )
		{
			*pTemp++ = *pMsgString++;
		}
		else
		{
			// temp buffer is full, dump it now
			*pTemp = 0; // terminate string
			WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 );
			pTemp = tmpBuf;
		}
	}

	// check for last portion
	if (( pTemp - tmpBuf ) > 0 )
	{
		// dump accumulated text
		*pTemp = 0; // terminate string
		WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 );
		pTemp = tmpBuf;
	}

	// restore white color
	SetConsoleTextAttribute( s_wcd.hOutput, g_color_table[7] );
}

void Wcon_ShowConsole( qboolean show )
{
	if( !s_wcd.hWnd || show == s_wcd.consoleVisible || s_wcd.attached )
		return;

	s_wcd.consoleVisible = show;
	if( show )
		ShowWindow( s_wcd.hWnd, SW_SHOW );
	else
		ShowWindow( s_wcd.hWnd, SW_HIDE );
}

void Wcon_DisableInput( void )
{
	if( host.type != HOST_DEDICATED )
		return;

	s_wcd.inputEnabled = false;
}

static void Wcon_SetInputText( const char *inputText )
{
	if( host.type != HOST_DEDICATED )
		return;

	while( s_wcd.consoleTextLen-- )
	{
		Wcon_PrintInternal( "\b \b", 0 );
	}
	Wcon_PrintInternal( inputText, 0 );
	Q_strncpy( s_wcd.consoleText, inputText, sizeof(s_wcd.consoleText) - 1 );
	s_wcd.consoleTextLen = Q_strlen( inputText );
	s_wcd.cursorPosition = s_wcd.consoleTextLen;
	s_wcd.browseLine = s_wcd.inputLine;
}

static void Wcon_Clear_f( void )
{
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	SMALL_RECT scrollRect;
	COORD scrollTarget;
	CHAR_INFO fill;

	if( host.type != HOST_DEDICATED )
		return;

	if( !GetConsoleScreenBufferInfo( s_wcd.hOutput, &csbi ))
	{
		return;
	}

	scrollRect.Left = 0;
	scrollRect.Top = 0;
	scrollRect.Right = csbi.dwSize.X;
	scrollRect.Bottom = csbi.dwSize.Y;
	scrollTarget.X = 0;
	scrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y);
	fill.Char.UnicodeChar = TEXT(' ');
	fill.Attributes = csbi.wAttributes;
	ScrollConsoleScreenBuffer( s_wcd.hOutput, &scrollRect, NULL, scrollTarget, &fill );

	csbi.dwCursorPosition.X = 0;
	csbi.dwCursorPosition.Y = 0;
	SetConsoleCursorPosition( s_wcd.hOutput, csbi.dwCursorPosition );

	s_wcd.consoleText[0] = '\0';
	s_wcd.consoleTextLen = 0;
	s_wcd.cursorPosition = 0;
	s_wcd.inputLine = 0;
	s_wcd.browseLine = 0;
	s_wcd.totalLines = 0;
	Wcon_PrintInternal( "\n", 0 );
}

static void Wcon_EventUpArrow()
{
	int nLastCommandInHistory = s_wcd.inputLine + 1;
	if( nLastCommandInHistory > s_wcd.totalLines )
		nLastCommandInHistory = 0;

	if( s_wcd.browseLine == nLastCommandInHistory )
		return;

	if( s_wcd.browseLine == s_wcd.inputLine )
	{
		if( s_wcd.consoleTextLen > 0 )
		{
			Q_strncpy( s_wcd.savedConsoleText, s_wcd.consoleText, s_wcd.consoleTextLen );
		}
		s_wcd.savedConsoleTextLen = s_wcd.consoleTextLen;
	}

	s_wcd.browseLine--;
	if( s_wcd.browseLine < 0 )
	{
		s_wcd.browseLine = s_wcd.totalLines - 1;
	}

	while( s_wcd.consoleTextLen-- )
	{
		Wcon_PrintInternal( "\b \b", 0 );
	}

	Wcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 );
	Q_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText ));
	s_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] );
	s_wcd.cursorPosition = s_wcd.consoleTextLen;
}

static void Wcon_EventDownArrow()
{
	if( s_wcd.browseLine == s_wcd.inputLine )
		return;

	if( ++s_wcd.browseLine > s_wcd.totalLines )
		s_wcd.browseLine = 0;

	while( s_wcd.consoleTextLen-- )
	{
		Wcon_PrintInternal( "\b \b", 0 );
	}

	if( s_wcd.browseLine == s_wcd.inputLine )
	{
		if( s_wcd.savedConsoleTextLen > 0 )
		{
			Q_strncpy( s_wcd.consoleText, s_wcd.savedConsoleText, s_wcd.savedConsoleTextLen );
			Wcon_PrintInternal( s_wcd.consoleText, s_wcd.savedConsoleTextLen );
		}
		s_wcd.consoleTextLen = s_wcd.savedConsoleTextLen;
	}
	else
	{
		Wcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 );
		Q_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText ));
		s_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] );
	}
	s_wcd.cursorPosition = s_wcd.consoleTextLen;
}

static void Wcon_EventLeftArrow()
{
	if( s_wcd.cursorPosition == 0 )
		return;

	Wcon_PrintInternal( "\b", 0 );
	s_wcd.cursorPosition--;
}

static void Wcon_EventRightArrow()
{
	if( s_wcd.cursorPosition == s_wcd.consoleTextLen )
		return;

	Wcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, 1 );
	s_wcd.cursorPosition++;
}

static int Wcon_EventNewline()
{
	int nLen;

	nLen = 0;
	Wcon_PrintInternal( "\n", 0 );
	if( s_wcd.consoleTextLen )
	{
		nLen = s_wcd.consoleTextLen;

		s_wcd.consoleText[s_wcd.consoleTextLen] = '\0';
		s_wcd.consoleTextLen = 0;
		s_wcd.cursorPosition = 0;

		if (( s_wcd.inputLine == 0 ) || ( Q_strcmp( s_wcd.lineBuffer[s_wcd.inputLine - 1], s_wcd.consoleText )))
		{
			Q_strncpy( s_wcd.lineBuffer[s_wcd.inputLine], s_wcd.consoleText, sizeof( s_wcd.consoleText ));
			s_wcd.inputLine++;

			if( s_wcd.inputLine > s_wcd.totalLines )
				s_wcd.totalLines = s_wcd.inputLine;

			if( s_wcd.inputLine >= COMMAND_HISTORY )
				s_wcd.inputLine = 0;
		}
		s_wcd.browseLine = s_wcd.inputLine;
	}
	return nLen;
}

static void Wcon_EventBackspace()
{
	int nCount;

	if( s_wcd.cursorPosition < 1 )
	{
		return;
	}

	s_wcd.consoleTextLen--;
	s_wcd.cursorPosition--;

	Wcon_PrintInternal( "\b", 0 );

	for( nCount = s_wcd.cursorPosition; nCount < s_wcd.consoleTextLen; ++nCount )
	{
		s_wcd.consoleText[nCount] = s_wcd.consoleText[nCount + 1];
		Wcon_PrintInternal( s_wcd.consoleText + nCount, 1 );
	}

	Wcon_PrintInternal( " ", 0 );

	nCount = s_wcd.consoleTextLen;
	while( nCount >= s_wcd.cursorPosition )
	{
		Wcon_PrintInternal( "\b", 0 );
		nCount--;
	}

	s_wcd.browseLine = s_wcd.inputLine;
}

static void Wcon_EventTab()
{
	s_wcd.consoleText[s_wcd.consoleTextLen] = '\0';
	Cmd_AutoComplete( s_wcd.consoleText );
	Wcon_SetInputText( s_wcd.consoleText );
}

static void Wcon_EventCharacter(char c)
{
	int nCount;

	if( s_wcd.consoleTextLen >= ( sizeof( s_wcd.consoleText ) - 2 ))
	{
		return;
	}

	nCount = s_wcd.consoleTextLen;
	while( nCount > s_wcd.cursorPosition )
	{
		s_wcd.consoleText[nCount] = s_wcd.consoleText[nCount - 1];
		nCount--;
	}

	s_wcd.consoleText[s_wcd.cursorPosition] = c;
	Wcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, s_wcd.consoleTextLen - s_wcd.cursorPosition + 1 );
	s_wcd.consoleTextLen++;
	s_wcd.cursorPosition++;

	nCount = s_wcd.consoleTextLen;
	while( nCount > s_wcd.cursorPosition )
	{
		Wcon_PrintInternal( "\b", 0 );
		nCount--;
	}

	s_wcd.browseLine = s_wcd.inputLine;
}

static void Wcon_UpdateStatusLine()
{
	COORD coord;
	WORD wAttrib;
	DWORD dwWritten;

	coord.X = 0;
	coord.Y = 0;
	wAttrib = g_color_table[5] | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY;

	FillConsoleOutputCharacter( s_wcd.hOutput, ' ', 80, coord, &dwWritten );
	FillConsoleOutputAttribute( s_wcd.hOutput, wAttrib, 80, coord, &dwWritten );
	WriteConsoleOutputCharacter( s_wcd.hOutput, s_wcd.statusLine, Q_strlen(s_wcd.statusLine), coord, &dwWritten );
}

static char *Wcon_KeyEvent( int key, WCHAR character )
{
	int nLen;
	char inputBuffer[1024];

	switch( key )
	{
	case VK_UP:
		Wcon_EventUpArrow();
		return NULL;
	case VK_DOWN:
		Wcon_EventDownArrow();
		return NULL;
	case VK_LEFT:
		Wcon_EventLeftArrow();
		return NULL;
	case VK_RIGHT:
		Wcon_EventRightArrow();
		return NULL;
	}

	switch( character )
	{
		case '\r':	// Enter
			nLen = Wcon_EventNewline();
			if (nLen)
			{
				return s_wcd.consoleText;
			}
			break;
		case '\b':	// Backspace
			Wcon_EventBackspace();
			break;
		case '\t':	// TAB
			Wcon_EventTab();
			break;
		default:
			// TODO implement converting wide chars to UTF-8 and properly handling it
			if (( character >= ' ' ) && ( character <= '~' ))
			{
				Wcon_EventCharacter(character);
			}
			break;
	}

	return NULL;
}

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

WIN32 IO

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

/*
================
Con_WinPrint

print into window console
================
*/
void Wcon_WinPrint( const char *pMsg )
{
	int nLen;
	if( s_wcd.consoleTextLen )
	{
		nLen = s_wcd.consoleTextLen;
		while (nLen--)
		{
			Wcon_PrintInternal( "\b \b", 0 );
		}
	}

	Wcon_PrintInternal( pMsg, 0 );

	if( s_wcd.consoleTextLen )
	{
		Wcon_PrintInternal( s_wcd.consoleText, s_wcd.consoleTextLen );
	}

	if( !s_wcd.attached ) 
		Wcon_UpdateStatusLine();
}

/*
================
Con_CreateConsole

create win32 console
================
*/
void Wcon_CreateConsole( void )
{
	if( Sys_CheckParm( "-log" ))
		s_wcd.log_active = true;

	if( host.type == HOST_NORMAL )
	{
		Q_strncpy( s_wcd.title, XASH_ENGINE_NAME " " XASH_VERSION, sizeof( s_wcd.title ));
		Q_strncpy( s_wcd.log_path, "engine.log", sizeof( s_wcd.log_path ));
	}
	else // dedicated console
	{
		Q_strncpy( s_wcd.title, XASH_DEDICATED_SERVER_NAME " " XASH_VERSION, sizeof( s_wcd.title ));
		Q_strncpy( s_wcd.log_path, "dedicated.log", sizeof( s_wcd.log_path ));
		s_wcd.log_active = true; // always make log
	}

	s_wcd.attached = ( AttachConsole( ATTACH_PARENT_PROCESS ) != 0 );
	if( s_wcd.attached ) {
		GetConsoleTitle( &s_wcd.previousTitle, sizeof( s_wcd.previousTitle ));
		s_wcd.previousCodePage = GetConsoleCP();
		s_wcd.previousOutputCodePage = GetConsoleOutputCP();
	}
	else {
		AllocConsole();
	}

	SetConsoleTitle( s_wcd.title );
	SetConsoleCP( CP_UTF8 );
	SetConsoleOutputCP( CP_UTF8 );

	s_wcd.hWnd = GetConsoleWindow();
	s_wcd.hInput = GetStdHandle( STD_INPUT_HANDLE );
	s_wcd.hOutput = GetStdHandle( STD_OUTPUT_HANDLE );
	s_wcd.inputEnabled = true;
	
	if( !SetConsoleCtrlHandler( &Wcon_HandleConsole, TRUE ))
	{
		Con_Reportf( S_ERROR "Couldn't attach console handler function\n" );
		return;
	}

	if( !s_wcd.attached )
	{ 
		SetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW );

		// show console if needed
		if( host.con_showalways )
		{
			// make console visible
			ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT );
			UpdateWindow( s_wcd.hWnd );
			SetForegroundWindow( s_wcd.hWnd );
			SetFocus( s_wcd.hWnd );
			s_wcd.consoleVisible = true;
		}
		else
		{
			s_wcd.consoleVisible = false;
			ShowWindow( s_wcd.hWnd, SW_HIDE );
		}
	}
}

/*
================
Con_InitConsoleCommands

register console commands (dedicated only)
================
*/
void Wcon_InitConsoleCommands( void )
{
	if( host.type != HOST_DEDICATED )
		return;

	Cmd_AddCommand( "clear", Wcon_Clear_f, "clear console history" );
}

/*
================
Con_DestroyConsole

destroy win32 console
================
*/
void Wcon_DestroyConsole( void )
{
	// last text message into console or log
	Con_Reportf( "Sys_FreeLibrary: Unloading xash.dll\n" );

	Sys_CloseLog();

	if( !s_wcd.attached )
	{ 
		if( s_wcd.hWnd )
		{
			ShowWindow( s_wcd.hWnd, SW_HIDE );
			s_wcd.hWnd = 0;
		}
	}
	else
	{
		// reverts title & code page for console window that was before starting Xash3D
		SetConsoleCP( s_wcd.previousCodePage );
		SetConsoleOutputCP( s_wcd.previousOutputCodePage );
		SetConsoleTitle( &s_wcd.previousTitle );
		Con_Printf( "Press Enter to continue...\n" );
	}

	FreeConsole();

	// place it here in case Sys_Crash working properly
	if( host.hMutex )
		CloseHandle( host.hMutex );
}

/*
================
Con_Input

returned input text
================
*/
char *Wcon_Input( void )
{
	DWORD i;
	DWORD eventsCount;
	static INPUT_RECORD events[1024];
	
	if( !s_wcd.inputEnabled )
		return NULL;

	while( true )
	{
		if( !GetNumberOfConsoleInputEvents( s_wcd.hInput, &eventsCount ))
		{
			return NULL;
		}

		if( eventsCount <= 0 )
			break;

		if( !ReadConsoleInputW( s_wcd.hInput, events, ARRAYSIZE( events ), &eventsCount ))
		{
			return NULL;
		}

		if( eventsCount == 0 )
			return NULL;

		for( i = 0; i < eventsCount; i++ )
		{
			INPUT_RECORD *pRec = &events[i];
			if( pRec->EventType != KEY_EVENT )
				continue;

			if( pRec->Event.KeyEvent.bKeyDown )
				return Wcon_KeyEvent( pRec->Event.KeyEvent.wVirtualKeyCode, pRec->Event.KeyEvent.uChar.UnicodeChar );
		}
	}
	return NULL;
}

/*
================
Platform_SetStatus

set server status string in console
================
*/
void Platform_SetStatus( const char *pStatus )
{
	if( s_wcd.attached )
		return;

	Q_strncpy( s_wcd.statusLine, pStatus, sizeof( s_wcd.statusLine ) - 1 );
	s_wcd.statusLine[sizeof( s_wcd.statusLine ) - 2] = '\0';
	Wcon_UpdateStatusLine();
}
