//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// CTextConsoleWin32.cpp: Win32 implementation of the TextConsole class.
//
//////////////////////////////////////////////////////////////////////

#include "TextConsoleWin32.h"
#include "tier0/dbg.h"
#include "utlvector.h"

// Could possibly switch all this code over to using readline. This:
//   http://mingweditline.sourceforge.net/?Description
// readline() / add_history(char *)

#ifdef _WIN32

BOOL WINAPI ConsoleHandlerRoutine( DWORD CtrlType )
{
	NOTE_UNUSED( CtrlType );
	/* TODO ?
	if ( CtrlType != CTRL_C_EVENT && CtrlType != CTRL_BREAK_EVENT )
		m_System->Stop();	 // don't quit on break or ctrl+c
	*/

	return TRUE;
}

// GetConsoleHwnd() helper function from MSDN Knowledge Base Article Q124103
// needed, because HWND GetConsoleWindow(VOID) is not avaliable under Win95/98/ME

HWND GetConsoleHwnd(void)
{
	typedef HWND (WINAPI *PFNGETCONSOLEWINDOW)( VOID );
	static PFNGETCONSOLEWINDOW s_pfnGetConsoleWindow = (PFNGETCONSOLEWINDOW) GetProcAddress( GetModuleHandle("kernel32"), "GetConsoleWindow" );
	if ( s_pfnGetConsoleWindow )
		return s_pfnGetConsoleWindow();

	HWND hwndFound;         // This is what is returned to the caller.
	char pszNewWindowTitle[1024]; // Contains fabricated WindowTitle
	char pszOldWindowTitle[1024]; // Contains original WindowTitle

	// Fetch current window title.
	GetConsoleTitle( pszOldWindowTitle, 1024 );

	// Format a "unique" NewWindowTitle.
	wsprintf( pszNewWindowTitle,"%d/%d", GetTickCount(), GetCurrentProcessId() );

	// Change current window title.
	SetConsoleTitle(pszNewWindowTitle);

	// Ensure window title has been updated.
	Sleep(40);

	// Look for NewWindowTitle.
	hwndFound = FindWindow( NULL, pszNewWindowTitle );

	// Restore original window title.

	SetConsoleTitle( pszOldWindowTitle );

	return hwndFound;
} 

CTextConsoleWin32::CTextConsoleWin32()
{
	hinput	= NULL;
	houtput = NULL;
	Attrib = 0;
	statusline[0] = '\0';
}

bool CTextConsoleWin32::Init()
{
	(void) AllocConsole();
	SetTitle( "SOURCE DEDICATED SERVER" );

	hinput = GetStdHandle ( STD_INPUT_HANDLE );
	houtput = GetStdHandle ( STD_OUTPUT_HANDLE );
	
	if ( !SetConsoleCtrlHandler( &ConsoleHandlerRoutine, TRUE) )
	{
		Print( "WARNING! TextConsole::Init: Could not attach console hook.\n" );
	}

	Attrib = FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY ;

	SetWindowPos( GetConsoleHwnd(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW );

	memset( m_szConsoleText, 0, sizeof( m_szConsoleText ) );
	m_nConsoleTextLen	= 0;
	m_nCursorPosition	= 0;

	memset( m_szSavedConsoleText, 0, sizeof( m_szSavedConsoleText ) );
	m_nSavedConsoleTextLen = 0;

	memset( m_aszLineBuffer, 0, sizeof( m_aszLineBuffer ) );
	m_nTotalLines	= 0;
	m_nInputLine	= 0;
	m_nBrowseLine	= 0;

	// these are log messages, not related to console
	Msg( "\n" );
	Msg( "Console initialized.\n" );

	return CTextConsole::Init();
}

void CTextConsoleWin32::ShutDown( void )
{
	FreeConsole();
}

void CTextConsoleWin32::SetVisible( bool visible )
{
	ShowWindow ( GetConsoleHwnd(), visible ? SW_SHOW : SW_HIDE );
	m_ConsoleVisible = visible;
}

char * CTextConsoleWin32::GetLine( int index, char *buf, int buflen )
{
	while ( 1 )
	{
		INPUT_RECORD	recs[ 1024 ];
		unsigned long	numread;
		unsigned long	numevents;

		if ( !GetNumberOfConsoleInputEvents( hinput, &numevents ) )
		{
			Error("CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents");
			return NULL;
		}

		if ( numevents <= 0 )
			break;

		if ( !ReadConsoleInput( hinput, recs, ARRAYSIZE( recs ), &numread ) )
		{
			Error("CTextConsoleWin32::GetLine: !ReadConsoleInput");
			return NULL;
		}

		if ( numread == 0 )
			return NULL;

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

			if ( pRec->Event.KeyEvent.bKeyDown )
			{
				// check for cursor keys
				if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_UP )
				{
					ReceiveUpArrow();
				}
				else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_DOWN )
				{
					ReceiveDownArrow();
				}
				else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_LEFT )
				{
					ReceiveLeftArrow();
				}
				else if ( pRec->Event.KeyEvent.wVirtualKeyCode == VK_RIGHT )
				{
					ReceiveRightArrow();
				}
				else
				{
					char	ch;
					int		nLen;

					ch = pRec->Event.KeyEvent.uChar.AsciiChar;
					switch ( ch )
					{
					case '\r':	// Enter
						nLen = ReceiveNewline();
						if ( nLen )
						{
							strncpy( buf, m_szConsoleText, buflen );
							buf[ buflen - 1 ] = 0;
							return buf;
						}
						break;

					case '\b':	// Backspace
						ReceiveBackspace();
						break;

					case '\t':	// TAB
						ReceiveTab();
						break;

					default:
						if ( ( ch >= ' ') && ( ch <= '~' ) )	// dont' accept nonprintable chars
						{
							ReceiveStandardChar( ch );
						}
						break;
					}
				}
			}
		}
	}

	return NULL;
}

void CTextConsoleWin32::Print( char * pszMsg )
{
	if ( m_nConsoleTextLen )
	{
		int nLen;

		nLen = m_nConsoleTextLen;

		while ( nLen-- )
		{
			PrintRaw( "\b \b" );
		}
	}

	PrintRaw( pszMsg );

	if ( m_nConsoleTextLen )
	{
		PrintRaw( m_szConsoleText, m_nConsoleTextLen );
	}

	UpdateStatus();
}

void CTextConsoleWin32::PrintRaw( const char * pszMsg, int nChars )
{
	unsigned long dummy;

	if ( houtput == NULL )
	{
		houtput = GetStdHandle ( STD_OUTPUT_HANDLE );
		if ( houtput == NULL )
			return;
	}
	
	if ( nChars <= 0 )
	{
		nChars = strlen( pszMsg );
		if ( nChars <= 0 )
			return;
	}

	// filter out ASCII BEL characters because windows actually plays a
	// bell sound, which can be used to lag the server in a DOS attack.
	char * pTempBuf = NULL;
	for ( int i = 0; i < nChars; ++i )
	{
		if ( pszMsg[i] == 0x07 /*BEL*/ )
		{
			if ( !pTempBuf )
			{
				pTempBuf = ( char * ) malloc( nChars );
				memcpy( pTempBuf, pszMsg, nChars );
			}
			pTempBuf[i] = '.';
		}
	}
	
	WriteFile( houtput, pTempBuf ? pTempBuf : pszMsg, nChars, &dummy, NULL );

	free( pTempBuf ); // usually NULL
}

int CTextConsoleWin32::GetWidth( void )
{
	CONSOLE_SCREEN_BUFFER_INFO	csbi;
	int							nWidth;

	nWidth = 0;

	if ( GetConsoleScreenBufferInfo( houtput, &csbi ) )
	{
		nWidth = csbi.dwSize.X;
	}

	if ( nWidth <= 1 )
		nWidth = 80;

	return nWidth;
} 

void CTextConsoleWin32::SetStatusLine( char * pszStatus )
{
	strncpy( statusline, pszStatus, 80 );
	statusline[ 79 ] = '\0';
	UpdateStatus();
}

void CTextConsoleWin32::UpdateStatus( void )
{
	COORD	coord;
	DWORD	dwWritten = 0;
	WORD	wAttrib[ 80 ];
	
	for ( int i = 0; i < 80; i++ )
	{
		wAttrib[i] = Attrib; //FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY ;
	}

	coord.X = coord.Y = 0;

	WriteConsoleOutputAttribute( houtput, wAttrib, 80, coord, &dwWritten );
	WriteConsoleOutputCharacter( houtput, statusline, 80, coord, &dwWritten );	
}


void CTextConsoleWin32::SetTitle( char * pszTitle )
{
	SetConsoleTitle( pszTitle );
}

void CTextConsoleWin32::SetColor(WORD attrib) 
{
	Attrib = attrib;
}	

int CTextConsoleWin32::ReceiveNewline( void )
{
	int nLen = 0;

	PrintRaw( "\n" );

	if ( m_nConsoleTextLen )
	{
		nLen = m_nConsoleTextLen;

		m_szConsoleText[ m_nConsoleTextLen ] = 0;
		m_nConsoleTextLen = 0;
		m_nCursorPosition = 0;

		// cache line in buffer, but only if it's not a duplicate of the previous line
		if ( ( m_nInputLine == 0 ) || ( strcmp( m_aszLineBuffer[ m_nInputLine - 1 ], m_szConsoleText ) ) )
		{
			strncpy( m_aszLineBuffer[ m_nInputLine ], m_szConsoleText, MAX_CONSOLE_TEXTLEN );
			
			m_nInputLine++;

			if ( m_nInputLine > m_nTotalLines )
				m_nTotalLines = m_nInputLine;

			if ( m_nInputLine >= MAX_BUFFER_LINES )
				m_nInputLine = 0;

		}

		m_nBrowseLine = m_nInputLine;
	}

	return nLen;
}


void CTextConsoleWin32::ReceiveBackspace( void )
{
	int nCount;

	if ( m_nCursorPosition == 0 )
	{
		return;
	}

	m_nConsoleTextLen--;
	m_nCursorPosition--;

	PrintRaw( "\b" );

	for ( nCount = m_nCursorPosition; nCount < m_nConsoleTextLen; nCount++ )
	{
		m_szConsoleText[ nCount ] = m_szConsoleText[ nCount + 1 ];
		PrintRaw( m_szConsoleText + nCount, 1 );
	}

	PrintRaw( " " );

	nCount = m_nConsoleTextLen;
	while ( nCount >= m_nCursorPosition )
	{
		PrintRaw( "\b" );
		nCount--;
	}

	m_nBrowseLine = m_nInputLine;
}


void CTextConsoleWin32::ReceiveTab( void )
{
	CUtlVector<char *> matches;

	m_szConsoleText[ m_nConsoleTextLen ] = 0;

	if ( matches.Count() == 0 )
	{
		return;
	}

	if ( matches.Count() == 1 )
	{
		char * pszCmdName;
		char * pszRest;

		pszCmdName	= matches[0];
		pszRest		= pszCmdName + strlen( m_szConsoleText );

		if ( pszRest )
		{
			PrintRaw( pszRest );
			strcat( m_szConsoleText, pszRest );
			m_nConsoleTextLen += strlen( pszRest );

			PrintRaw( " " );
			strcat( m_szConsoleText, " " );
			m_nConsoleTextLen++;
		}
	}
	else
	{
		int		nLongestCmd;
		int		nTotalColumns;
		int		nCurrentColumn;
		char *	pszCurrentCmd;
		int i = 0;

		nLongestCmd = 0;

		pszCurrentCmd = matches[0];
		while ( pszCurrentCmd )
		{
			if ( (int)strlen( pszCurrentCmd) > nLongestCmd )
			{
				nLongestCmd = strlen( pszCurrentCmd);
			}

			i++;
			pszCurrentCmd = (char *)matches[i];
		}

		nTotalColumns	= ( GetWidth() - 1 ) / ( nLongestCmd + 1 );
		nCurrentColumn	= 0;

		PrintRaw( "\n" );

		// Would be nice if these were sorted, but not that big a deal
		pszCurrentCmd = matches[0];
		i = 0;
		while ( pszCurrentCmd )
		{
			char szFormatCmd[ 256 ];

			nCurrentColumn++;

			if ( nCurrentColumn > nTotalColumns )
			{
				PrintRaw( "\n" );
				nCurrentColumn = 1;
			}

			Q_snprintf( szFormatCmd, sizeof(szFormatCmd), "%-*s ", nLongestCmd, pszCurrentCmd );
			PrintRaw( szFormatCmd );

			i++;
			pszCurrentCmd = matches[i];
		}

		PrintRaw( "\n" );
		PrintRaw( m_szConsoleText );
		// TODO: Tack on 'common' chars in all the matches, i.e. if I typed 'con' and all the
		//       matches begin with 'connect_' then print the matches but also complete the
		//       command up to that point at least.
	}

	m_nCursorPosition	= m_nConsoleTextLen;
	m_nBrowseLine		= m_nInputLine;
}



void CTextConsoleWin32::ReceiveStandardChar( const char ch )
{
	int nCount;

	// If the line buffer is maxed out, ignore this char
	if ( m_nConsoleTextLen >= ( sizeof( m_szConsoleText ) - 2 ) )
	{
		return;
	}

	nCount = m_nConsoleTextLen;
	while ( nCount > m_nCursorPosition )
	{
		m_szConsoleText[ nCount ] = m_szConsoleText[ nCount - 1 ];
		nCount--;
	}

	m_szConsoleText[ m_nCursorPosition ] = ch;

	PrintRaw( m_szConsoleText + m_nCursorPosition, m_nConsoleTextLen - m_nCursorPosition + 1 );

	m_nConsoleTextLen++;
	m_nCursorPosition++;

	nCount = m_nConsoleTextLen;
	while ( nCount > m_nCursorPosition )
	{
		PrintRaw( "\b" );
		nCount--;
	}

	m_nBrowseLine = m_nInputLine;
}


void CTextConsoleWin32::ReceiveUpArrow( void )
{
	int nLastCommandInHistory;

	nLastCommandInHistory = m_nInputLine + 1;
	if ( nLastCommandInHistory > m_nTotalLines )
	{
		nLastCommandInHistory = 0;
	}

	if ( m_nBrowseLine == nLastCommandInHistory )
	{
		return;
	}

	if ( m_nBrowseLine == m_nInputLine )
	{
		if ( m_nConsoleTextLen > 0 )
		{
			// Save off current text
			strncpy( m_szSavedConsoleText, m_szConsoleText, m_nConsoleTextLen );
			// No terminator, it's a raw buffer we always know the length of
		}

		m_nSavedConsoleTextLen = m_nConsoleTextLen;
	}

	m_nBrowseLine--;
	if ( m_nBrowseLine < 0 )
	{
		m_nBrowseLine = m_nTotalLines - 1;
	}

	while ( m_nConsoleTextLen-- )	// delete old line
	{
		PrintRaw( "\b \b" );
	}

	// copy buffered line
	PrintRaw( m_aszLineBuffer[ m_nBrowseLine ] );

	strncpy( m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN );
	m_nConsoleTextLen = strlen( m_aszLineBuffer[ m_nBrowseLine ] );

	m_nCursorPosition = m_nConsoleTextLen;
}


void CTextConsoleWin32::ReceiveDownArrow( void )
{
	if ( m_nBrowseLine == m_nInputLine )
	{
		return;
	}

	m_nBrowseLine++;
	if ( m_nBrowseLine > m_nTotalLines )
	{
		m_nBrowseLine = 0;
	}

	while ( m_nConsoleTextLen-- )	// delete old line
	{
		PrintRaw( "\b \b" );
	}

	if ( m_nBrowseLine == m_nInputLine )
	{
		if ( m_nSavedConsoleTextLen > 0 )
		{
			// Restore current text
			strncpy( m_szConsoleText, m_szSavedConsoleText, m_nSavedConsoleTextLen );
			// No terminator, it's a raw buffer we always know the length of

			PrintRaw( m_szConsoleText, m_nSavedConsoleTextLen );
		}

		m_nConsoleTextLen = m_nSavedConsoleTextLen;
	}
	else
	{
		// copy buffered line
		PrintRaw( m_aszLineBuffer[ m_nBrowseLine ] );

		strncpy( m_szConsoleText, m_aszLineBuffer[ m_nBrowseLine ], MAX_CONSOLE_TEXTLEN );

		m_nConsoleTextLen = strlen( m_aszLineBuffer[ m_nBrowseLine ] );
	}

	m_nCursorPosition = m_nConsoleTextLen;
}


void CTextConsoleWin32::ReceiveLeftArrow( void )
{
	if ( m_nCursorPosition == 0 )
	{
		return;
	}

	PrintRaw( "\b" );
	m_nCursorPosition--;
}


void CTextConsoleWin32::ReceiveRightArrow( void )
{
	if ( m_nCursorPosition == m_nConsoleTextLen )
	{
		return;
	}

	PrintRaw( m_szConsoleText + m_nCursorPosition, 1 );
	m_nCursorPosition++;
}

#endif // _WIN32