/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, 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 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.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//																			//
//	pfConsole Functions														//
//																			//
//////////////////////////////////////////////////////////////////////////////

#include "pfConsole.h"
#include "pfConsoleEngine.h"
#include "../plPipeline/plDebugText.h"
#include "../plInputCore/plInputDevice.h"
#include "../plInputCore/plInputInterface.h"
#include "../plInputCore/plInputInterfaceMgr.h"
#include "../pnInputCore/plKeyMap.h"
#include "../pnInputCore/plKeyDef.h"
#include "../plMessage/plInputEventMsg.h"
#include "../plMessage/plConsoleMsg.h"
#include "../plMessage/plInputIfaceMgrMsg.h"
#include "../pnKeyedObject/plFixedKey.h"
#include "hsTimer.h"
#include "plgDispatch.h"
#include "plPipeline.h"
#include "hsConfig.h"

#include "../pfPython/cyPythonInterface.h"
#include "../plNetClient/plNetClientMgr.h"

#ifndef PLASMA_EXTERNAL_RELEASE
#include "../pfGameMgr/pfGameMgr.h"
#endif // PLASMA_EXTERNAL_RELEASE


//// Static Class Stuff //////////////////////////////////////////////////////

pfConsole	*pfConsole::fTheConsole = nil;
UInt32		pfConsole::fConsoleTextColor = 0xff00ff00;
plPipeline	*pfConsole::fPipeline = nil;


//////////////////////////////////////////////////////////////////////////////
//// pfConsoleInputInterface - Input interface layer for the console /////////
//////////////////////////////////////////////////////////////////////////////

class pfConsoleInputInterface : public plInputInterface
{
	protected:

		pfConsole	*fConsole;


		virtual hsBool	IHandleCtrlCmd( plCtrlCmd *cmd )
		{
			if( cmd->fControlCode == B_SET_CONSOLE_MODE )
			{
				if( cmd->fControlActivated )
				{
					// Activate/deactivate
					switch( fConsole->fMode )
					{
						case pfConsole::kModeHidden:
							fConsole->ISetMode( pfConsole::kModeSingleLine );
							break;
						case pfConsole::kModeSingleLine:
							fConsole->ISetMode( pfConsole::kModeFull );
							break;
						case pfConsole::kModeFull:
							fConsole->ISetMode( pfConsole::kModeHidden );
							break;
					}
				}
				return true;
			}
			return false;
		}

	public:

		pfConsoleInputInterface( pfConsole *console ) 
		{
			fConsole = console; 
			SetEnabled( true );			// Always enabled

			// Add our control codes to our control map. Do NOT add the key bindings yet.
			// Note: HERE is where you specify the actions for each command, i.e. net propagate and so forth.
			// This part basically declares us master of the bindings for these commands.
			
			// IF YOU ARE LOOKING TO CHANGE THE DEFAULT KEY BINDINGS, DO NOT LOOK HERE. GO TO
			// RestoreDefaultKeyMappings()!!!!

#ifndef PLASMA_EXTERNAL_RELEASE
			fControlMap->AddCode( B_SET_CONSOLE_MODE, kControlFlagNormal | kControlFlagNoRepeat );
#endif

			// IF YOU ARE LOOKING TO CHANGE THE DEFAULT KEY BINDINGS, DO NOT LOOK HERE. GO TO
			// RestoreDefaultKeyMappings()!!!!
		}

		virtual UInt32	GetPriorityLevel( void ) const			{ return kConsolePriority; }
		virtual UInt32	GetCurrentCursorID( void ) const		{ return kCursorHidden; }
		virtual hsBool	HasInterestingCursorID( void ) const	{ return false; }

		virtual hsBool	InterpretInputEvent( plInputEventMsg *pMsg )
		{
			plKeyEventMsg	*keyMsg = plKeyEventMsg::ConvertNoRef( pMsg );

			// HACK for now to let runlock work always (until we can think of a more generic and good way of doing this)
			if( keyMsg != nil && keyMsg->GetKeyCode() != KEY_CAPSLOCK )
			{
				if( fConsole->fMode )
				{
					fConsole->IHandleKey( keyMsg );
					return true;
				}
			}

			return false;
		}

		virtual void	RefreshKeyMap( void )
		{
		}

		virtual void	RestoreDefaultKeyMappings( void )
		{
			if( fControlMap != nil )
			{
				fControlMap->UnmapAllBindings();
#ifndef PLASMA_EXTERNAL_RELEASE
				fControlMap->BindKey( KEY_TILDE, B_SET_CONSOLE_MODE );
#endif
			}
		}
};

//// Constructor & Destructor ////////////////////////////////////////////////

pfConsole::pfConsole()
{
	fNumDisplayLines = 32;
	fDisplayBuffer = nil;
	fTheConsole = this;
	fFXEnabled = true;
}

pfConsole::~pfConsole()
{
	if( fInputInterface != nil )
	{
		plInputIfaceMgrMsg *msg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kRemoveInterface );
		msg->SetIFace( fInputInterface );
		plgDispatch::MsgSend( msg );

		hsRefCnt_SafeUnRef( fInputInterface );
		fInputInterface = nil;
	}

	if( fDisplayBuffer != nil )
	{
		delete [] fDisplayBuffer;
		fDisplayBuffer = nil;
	}

	fTheConsole = nil;

	plgDispatch::Dispatch()->UnRegisterForExactType( plConsoleMsg::Index(), GetKey() );
	plgDispatch::Dispatch()->UnRegisterForExactType( plControlEventMsg::Index(), GetKey() );

#ifndef PLASMA_EXTERNAL_RELEASE	
	pfGameMgr::GetInstance()->RemoveReceiver(GetKey());
#endif // PLASMA_EXTERNAL_RELEASE
}

pfConsole * pfConsole::GetInstance () {

	return fTheConsole;
}

//// Init ////////////////////////////////////////////////////////////////////

void	pfConsole::Init( pfConsoleEngine *engine )
{
	fDisplayBuffer = TRACKED_NEW char[ fNumDisplayLines * kMaxCharsWide ];
	memset( fDisplayBuffer, 0, fNumDisplayLines * kMaxCharsWide );

	memset( fWorkingLine, 0, sizeof( fWorkingLine ) );
	fWorkingCursor = 0;

	memset( fHistory, 0, sizeof( fHistory ) );
	fHistoryCursor = fHistoryRecallCursor = 0;

	fEffectCounter = 0;
	fMode = 0;
	fMsgTimeoutTimer = 0;
	fHelpMode = false;
	fPythonMode = false;
	fPythonFirstTime = true;
	fPythonMultiLines = 0;
	fHelpTimer = 0;
	fCursorTicks = 0;
	memset( fLastHelpMsg, 0, sizeof( fLastHelpMsg ) );
	fEngine = engine;

	fInputInterface = TRACKED_NEW pfConsoleInputInterface( this );
	plInputIfaceMgrMsg *msg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kAddInterface );
	msg->SetIFace( fInputInterface );
	plgDispatch::MsgSend( msg );

	// Register for keyboard event messages
	plgDispatch::Dispatch()->RegisterForExactType( plConsoleMsg::Index(), GetKey() );
	plgDispatch::Dispatch()->RegisterForExactType( plControlEventMsg::Index(), GetKey() );

#ifndef PLASMA_EXTERNAL_RELEASE	
	pfGameMgr::GetInstance()->AddReceiver(GetKey());
#endif // PLASMA_EXTERNAL_RELEASE
}

//// ISetMode ////////////////////////////////////////////////////////////////

void	pfConsole::ISetMode( UInt8 mode )
{
	fMode = mode; 
	fEffectCounter = ( fFXEnabled ? kEffectDivisions : 0 ); 
	fMsgTimeoutTimer = 0; 
	fInputInterface->RefreshKeyMap();
}

//// MsgReceive //////////////////////////////////////////////////////////////

hsBool	pfConsole::MsgReceive( plMessage *msg )
{
	plControlEventMsg *ctrlMsg = plControlEventMsg::ConvertNoRef( msg );
	if( ctrlMsg != nil )
	{
		if( ctrlMsg->ControlActivated() && ctrlMsg->GetControlCode() == B_CONTROL_CONSOLE_COMMAND && plNetClientMgr::GetInstance()->GetFlagsBit(plNetClientMgr::kPlayingGame))
		{
			fEngine->RunCommand( ctrlMsg->GetCmdString(), IAddLineCallback );
			return true;
		}
		return false;
	}

	plConsoleMsg *cmd = plConsoleMsg::ConvertNoRef( msg );
	if( cmd != nil && cmd->GetString() != nil )
	{
		if( cmd->GetCmd() == plConsoleMsg::kExecuteFile )
		{
			if( !fEngine->ExecuteFile( (char *)cmd->GetString() ) )
			{
				// Change the following line once we have a better way of reporting
				// errors in the parsing
				static char	str[ 256 ];
				static char	msg[ 1024 ];

				sprintf( str, "Error parsing %s", cmd->GetString() );
				sprintf( msg, "%s:\n\nCommand: '%s'\n%s", fEngine->GetErrorMsg(), fEngine->GetLastErrorLine(),
#ifdef HS_DEBUGGING
						"" );

				hsAssert( false, msg );
#else
						"\nPress OK to continue parsing files." );

				hsMessageBox( msg, str, hsMessageBoxNormal );
#endif
			}
		}
		else if( cmd->GetCmd() == plConsoleMsg::kAddLine )
			IAddParagraph( (char *)cmd->GetString() );
		else if( cmd->GetCmd() == plConsoleMsg::kExecuteLine )
		{
			if( !fEngine->RunCommand( (char *)cmd->GetString(), IAddLineCallback ) )
			{
				// Change the following line once we have a better way of reporting
				// errors in the parsing
				static char	msg[ 1024 ];

				sprintf( msg, "%s:\n\nCommand: '%s'\n", fEngine->GetErrorMsg(), fEngine->GetLastErrorLine() );
				IAddLineCallback( msg ); 
			}
		}
			
		return true;
	}


	//========================================================================
	// pfGameMgrMsg
#ifndef PLASMA_EXTERNAL_RELEASE
	if (pfGameMgrMsg * gameMgrMsg = pfGameMgrMsg::ConvertNoRef(msg)) {

		switch (gameMgrMsg->netMsg->messageId) {

			//================================================================
			// InviteReceived			
			case kSrv2Cli_GameMgr_InviteReceived: {
				const Srv2Cli_GameMgr_InviteReceived & gmMsg = *(const Srv2Cli_GameMgr_InviteReceived *)gameMgrMsg->netMsg;
				const char * inviterName = plNetClientMgr::GetInstance()->GetPlayerNameById(gmMsg.inviterId);
				AddLineF("[GameMgr] Invite received: %S, %u. Inviter: %s", pfGameMgr::GetInstance()->GetGameNameByTypeId(gmMsg.gameTypeId), gmMsg.newGameId, inviterName ? inviterName : "<Unknown>");
			}
			return true;

			//================================================================
			// InviteRevoked			
			case kSrv2Cli_GameMgr_InviteRevoked: {
				const Srv2Cli_GameMgr_InviteRevoked & gmMsg = *(const Srv2Cli_GameMgr_InviteRevoked *)gameMgrMsg->netMsg;
				const char * inviterName = plNetClientMgr::GetInstance()->GetPlayerNameById(gmMsg.inviterId);
				AddLineF("[GameMgr] Invite revoked: %S, %u. Inviter: %s", pfGameMgr::GetInstance()->GetGameNameByTypeId(gmMsg.gameTypeId), gmMsg.newGameId, inviterName ? inviterName : "<Unknown>");
			}
			return true;
			
			DEFAULT_FATAL(gameMgrMsg->netMsg->messageId);
		};
	}
#endif // PLASMA_EXTERNAL_RELEASE
	//========================================================================
	
	//========================================================================
	// pfGameCliMsg
#ifndef PLASMA_EXTERNAL_RELEASE
	if (pfGameCliMsg * gameCliMsg = pfGameCliMsg::ConvertNoRef(msg)) {
	
		pfGameCli * cli = gameCliMsg->gameCli;
	
		//====================================================================
		// Handle pfGameCli msgs
		switch (gameCliMsg->netMsg->messageId) {
			//================================================================
			// PlayerJoined
			case kSrv2Cli_Game_PlayerJoined: {
				const Srv2Cli_Game_PlayerJoined & netMsg = *(const Srv2Cli_Game_PlayerJoined *)gameCliMsg->netMsg;
				AddLineF(
					"[Game %s:%u] Player joined: %s",
					cli->GetName(),
					cli->GetGameId(),
					netMsg.playerId
						? plNetClientMgr::GetInstance()->GetPlayerNameById(netMsg.playerId)
						: "Computer"
				);
			}
			return true;

			//================================================================
			// PlayerLeft
			case kSrv2Cli_Game_PlayerLeft: {
				const Srv2Cli_Game_PlayerLeft & netMsg = *(const Srv2Cli_Game_PlayerLeft *)gameCliMsg->netMsg;
				AddLineF(
					"[Game %s:%u] Player left: %s",
					cli->GetName(),
					cli->GetGameId(),
					netMsg.playerId
						? plNetClientMgr::GetInstance()->GetPlayerNameById(netMsg.playerId)
						: "Computer"
				);
			}
			return true;
			
			//================================================================
			// InviteFailed
			case kSrv2Cli_Game_InviteFailed: {
				const Srv2Cli_Game_InviteFailed & netMsg = *(const Srv2Cli_Game_InviteFailed *)gameCliMsg->netMsg;
				AddLineF(
					"[Game %s:%u] Invite failed for playerId %u, error %u",
					cli->GetName(),
					cli->GetGameId(),
					netMsg.inviteeId,
					netMsg.error
				);
			}
			return true;

			//================================================================
			// OwnerChange
			case kSrv2Cli_Game_OwnerChange: {
				const Srv2Cli_Game_OwnerChange & netMsg = *(const Srv2Cli_Game_OwnerChange *)gameCliMsg->netMsg;
				AddLineF(
					"[Game %s:%u] Owner changed to playerId %u",
					cli->GetName(),
					cli->GetGameId(),
					netMsg.ownerId
				);
			}
			return true;
		}

		//====================================================================
		// Handle Tic-Tac-Toe msgs
		if (gameCliMsg->gameCli->GetGameTypeId() == kGameTypeId_TicTacToe) {
		
			pfGmTicTacToe * ttt = pfGmTicTacToe::ConvertNoRef(cli);
			ASSERT(ttt);
			
			switch (gameCliMsg->netMsg->messageId) {
				//============================================================
				// GameStarted
				case kSrv2Cli_TTT_GameStarted: {
					const Srv2Cli_TTT_GameStarted & netMsg = *(const Srv2Cli_TTT_GameStarted *)gameCliMsg->netMsg;
					if (netMsg.yourTurn)
						AddLineF(
							"[Game %s:%u] Game started. You are X's. You go first.",
							cli->GetName(),
							cli->GetGameId()
						);
					else
						AddLineF(
							"[Game %s:%u] Game started. You are O's. Other player goes first.",
							cli->GetName(),
							cli->GetGameId()
						);
					ttt->ShowBoard();
				}
				return true;

				//============================================================
				// GameOver
				case kSrv2Cli_TTT_GameOver: {
					const Srv2Cli_TTT_GameOver & netMsg = *(const Srv2Cli_TTT_GameOver *)gameCliMsg->netMsg;
					switch (netMsg.result) {
						case kTTTGameResultWinner:
							if (netMsg.winnerId == NetCommGetPlayer()->playerInt)
								AddLineF(
									"[Game %s:%u] Game over. You won!",
									cli->GetName(),
									cli->GetGameId()
								);
							else
								AddLineF(
									"[Game %s:%u] Game over. You lost.",
									cli->GetName(),
									cli->GetGameId()
								);
						break;
						
						case kTTTGameResultTied:
							AddLineF(
								"[Game %s:%u] Game over. You tied.",
								cli->GetName(),
								cli->GetGameId()
							);
						break;
						
						case kTTTGameResultGave:
							AddLineF(
								"[Game %s:%u] Game over. You win by default.",
								cli->GetName(),
								cli->GetGameId()
							);
						break;
							
						default:
							AddLineF(
								"[Game %s:%u] Game over. Server-side error %u.",
								cli->GetName(),
								cli->GetGameId(),
								netMsg.result
							);
						break;
					}
				}
				return true;
				
				//============================================================
				// MoveMade
				case kSrv2Cli_TTT_MoveMade: {
					const Srv2Cli_TTT_MoveMade & netMsg = *(const Srv2Cli_TTT_MoveMade *)gameCliMsg->netMsg;
					const char * playerName
						= netMsg.playerId
							? plNetClientMgr::GetInstance()->GetPlayerNameById(netMsg.playerId)
							: "Computer";
					AddLineF(
						"[Game %s:%u] %s moved:",
						cli->GetName(),
						cli->GetGameId(),
						playerName
					);
					ttt->ShowBoard();
				}
				return true;
				
				DEFAULT_FATAL(gameCliMsg->netMsg->messageId);
			}
		}
		else {
			FATAL("Unknown game type");
		}
		
		return true;
	}
#endif // PLASMA_EXTERNAL_RELEASE
	//========================================================================
	
	return hsKeyedObject::MsgReceive(msg);
}

//// IHandleKey //////////////////////////////////////////////////////////////

void	pfConsole::IHandleKey( plKeyEventMsg *msg )
{
	char			*c, key;
	int				i,eol;
	static hsBool	findAgain = false;
	static UInt32	findCounter = 0;


	if( !msg->GetKeyDown() )
		return;

	if( msg->GetKeyCode() == KEY_ESCAPE )
	{
		fWorkingLine[ fWorkingCursor = 0 ] = 0;
		findAgain = false;
		findCounter = 0;
		fHelpMode = false;
		fPythonMode = false;
		fPythonMultiLines = 0;
		IUpdateTooltip();
	}
	else if( msg->GetKeyCode() == KEY_TAB )
	{
		if ( fPythonMode )
		{
			// if we are in Python mode, then just add two spaces, tab over, sorta
			if ( strlen(fWorkingLine) < kWorkingLineSize+2 )
			{
				strcat(&fWorkingLine[fWorkingCursor], "  ");
				fWorkingCursor += 2;
			}
		}
		else
		{
			static char		lastSearch[ kMaxCharsWide ];
			char			search[ kMaxCharsWide ];

			if( !findAgain && findCounter == 0 )
				strcpy( lastSearch, fWorkingLine );
			strcpy( search, lastSearch );

			if( findCounter > 0 )
			{
				// Not found the normal way; try using an unrestricted search
				if( fEngine->FindNestedPartialCmd( search, findCounter, true ) )
				{
					strcpy( fWorkingLine, search );
					findCounter++;
				}
				else
				{
					/// Try starting over...?
					findCounter = 0;
					if( fEngine->FindNestedPartialCmd( search, findCounter, true ) )
					{ 
						strcpy( fWorkingLine, search );
						findCounter++;
					}
				}
			}
			else if( fEngine->FindPartialCmd( search, findAgain, true ) )
			{
				strcpy( fWorkingLine, search );
				findAgain = true;
			}
			else if( findAgain )
			{
				/// Try starting over
				strcpy( search, lastSearch );
				if( fEngine->FindPartialCmd( search, findAgain = false, true ) )
				{
					strcpy( fWorkingLine, search );
					findAgain = true;
				}
			}

			else
			{
				// Not found the normal way; start an unrestricted search
				if( fEngine->FindNestedPartialCmd( search, findCounter, true ) )
				{
					strcpy( fWorkingLine, search );
					findCounter++;
				}
			}

			fWorkingCursor = strlen( fWorkingLine );
			IUpdateTooltip();
		}
	}
	else if( msg->GetKeyCode() == KEY_UP )
	{
		i = ( fHistoryRecallCursor > 0 ) ? fHistoryRecallCursor - 1 : kNumHistoryItems - 1;
		if( fHistory[ i ][ 0 ] != 0 )
		{
			fHistoryRecallCursor = i;
			strcpy( fWorkingLine, fHistory[ fHistoryRecallCursor ] );
			findAgain = false;
			findCounter = 0;
			fWorkingCursor = strlen( fWorkingLine );
			IUpdateTooltip();
		}
	}
	else if( msg->GetKeyCode() == KEY_DOWN )
	{
		if( fHistoryRecallCursor != fHistoryCursor )
		{
			i = ( fHistoryRecallCursor < kNumHistoryItems - 1 ) ? fHistoryRecallCursor + 1 : 0;
			if( i != fHistoryCursor )
			{
				fHistoryRecallCursor = i;
				strcpy( fWorkingLine, fHistory[ fHistoryRecallCursor ] );
			}
			else
			{
				memset( fWorkingLine, 0, sizeof( fWorkingLine ) );
				fHistoryRecallCursor = fHistoryCursor;
			}
			findAgain = false;
			findCounter = 0;
			fWorkingCursor = strlen( fWorkingLine );
			IUpdateTooltip();
		}
	}
	else if( msg->GetKeyCode() == KEY_LEFT )
	{
		if( fWorkingCursor > 0 )
			fWorkingCursor--;
	}
	else if( msg->GetKeyCode() == KEY_RIGHT )
	{
		if( fWorkingCursor < strlen( fWorkingLine ) )
			fWorkingCursor++;
	}
	else if( msg->GetKeyCode() == KEY_BACKSPACE )
	{
		if( fWorkingCursor > 0 )
		{
			fWorkingCursor--;

			c = &fWorkingLine[ fWorkingCursor ];
			do
			{
				*c = *( c + 1 );
				c++;
			} while( *c != 0 );

			findAgain = false;
			findCounter = 0;
		}
		else if( fHelpMode )
			fHelpMode = false;

		IUpdateTooltip();
	}
	else if( msg->GetKeyCode() == KEY_DELETE )
	{
		c = &fWorkingLine[ fWorkingCursor ];
		do
		{
			*c = *( c + 1 );
			c++;
		} while( *c != 0 );

		findAgain = false;
		findCounter = 0;
		IUpdateTooltip();
	}
	else if( msg->GetKeyCode() == KEY_ENTER )
	{
		// leave leading space for Python multi lines (need the indents!)
		if ( fPythonMultiLines == 0 )
		{
			// Clean up working line by removing any leading whitespace
			for( c = fWorkingLine; *c == ' ' || *c == '\t'; c++ );
			for( i = 0; *c != 0; i++, c++ )
				fWorkingLine[ i ] = *c;
			fWorkingLine[ i ] = 0;
			eol = i;
		}

		if( fWorkingLine[ 0 ] == 0 && !fHelpMode && !fPythonMode )
		{
			// Blank line--just print a blank line to the console and skip
			IAddLine( "" );
			return;
		}

		// only save history line if there is something there
		if( fWorkingLine[ 0 ] != 0 )
		{
			// Save to history
			strcpy( fHistory[ fHistoryCursor ], fWorkingLine );
			fHistoryCursor = ( fHistoryCursor < kNumHistoryItems - 1 ) ? fHistoryCursor + 1 : 0;
			fHistoryRecallCursor = fHistoryCursor;
		}

		// EXECUTE!!! (warning: DESTROYS fWorkingLine)
		if( fHelpMode )
		{
			if( fWorkingLine[ 0 ] == 0 )
				IPrintSomeHelp();
			else if( stricmp( fWorkingLine, "commands" ) == 0 )
				fEngine->PrintCmdHelp( "", IAddLineCallback );
			else if( !fEngine->PrintCmdHelp( fWorkingLine, IAddLineCallback ) )
			{
				c = (char *)fEngine->GetErrorMsg();
				AddLine( c );
			}

			fHelpMode = false;
		}
		// are we in Python mode?
		else if ( fPythonMode )
		{
			// are we in Python multi-line mode?
			if ( fPythonMultiLines > 0 )
			{
				// if there was a line then bump num lines
				if ( fWorkingLine[0] != 0 )
				{
					char displine[300];
					sprintf(displine,"... %s",fWorkingLine);
					AddLine( displine );
					fPythonMultiLines++;
				}

				// is it time to evaluate all the multi lines that are saved?
				if ( fWorkingLine[0] == 0 || fPythonMultiLines >= kNumHistoryItems )
				{
					if ( fPythonMultiLines >= kNumHistoryItems )
						AddLine("Python Multi-line buffer full!");
					// get the lines and stuff them in our buffer
					char biglines[kNumHistoryItems*(kMaxCharsWide+1)];
					biglines[0] = 0;
					for ( i=fPythonMultiLines; i>0 ; i--)
					{
						// reach back in the history and find this line and paste it in here
						int recall = fHistoryCursor - i;
						if ( recall < 0 )
							recall += kNumHistoryItems;
						strcat(biglines,fHistory[ recall ]);
						strcat(biglines,"\n");
					}
					// now evaluate this mess they made
					PyObject* mymod = PythonInterface::FindModule("__main__");
					PythonInterface::RunStringInteractive(biglines,mymod);
					std::string output;
					// get the messages
					PythonInterface::getOutputAndReset(&output);
					AddLine( output.c_str() );
					// all done doing multi lines...
					fPythonMultiLines = 0;
				}
			}
			// else we are just doing single lines
			else
			{
				// was there actually anything in the input buffer?
				if ( fWorkingLine[0] != 0 )
				{
					char displine[300];
					sprintf(displine,">>> %s",fWorkingLine);
					AddLine( displine );
					// check to see if this is going to be a multi line mode ( a ':' at the end)
					if ( fWorkingLine[eol-1] == ':' )
					{
						fPythonMultiLines = 1;
					}
					else
					// else if not the start of a multi-line then execute it
					{
						PyObject* mymod = PythonInterface::FindModule("__main__");
						PythonInterface::RunStringInteractive(fWorkingLine,mymod);
						std::string output;
						// get the messages
						PythonInterface::getOutputAndReset(&output);
						AddLine( output.c_str() );
					}
				}
				else
					AddLine( ">>> " );
			}
			// find the end of the line
			for( c = fWorkingLine, eol = 0; *c != 0; eol++, c++ );
		}
		else
		{
			if( !fEngine->RunCommand( fWorkingLine, IAddLineCallback ) )
			{
				c = (char *)fEngine->GetErrorMsg();
				if( c[ 0 ] != 0 )
					AddLine( c );
			}
		}

		// Clear
		fWorkingLine[ fWorkingCursor = 0 ] = 0;
		findAgain = false;
		findCounter = 0;
		IUpdateTooltip();
	}
	else if( msg->GetKeyCode() == KEY_END )
	{
		fWorkingCursor = strlen( fWorkingLine );
	}
	else if( msg->GetKeyCode() == KEY_HOME )
	{
		fWorkingCursor = 0;
	}
	else 
	{
		key = plKeyboardDevice::KeyEventToChar( msg );
		// do they want to go into help mode?
		if( !fPythonMode && key == '?' && fWorkingCursor == 0 )
		{
			/// Go into help mode
			fHelpMode = true;
		}
		// do they want to go into Python mode?
		else if( !fHelpMode && key == '\\' && fWorkingCursor == 0 )
		{
			// toggle Python mode
			fPythonMode = fPythonMode ? false:true;
			if ( fPythonMode )
			{
				if ( fPythonFirstTime )
				{
					IAddLine( "" );		// add a blank line
					PyObject* mymod = PythonInterface::FindModule("__main__");
					PythonInterface::RunStringInteractive("import sys;print 'Python',sys.version",mymod);
					std::string output;
					// get the messages
					PythonInterface::getOutputAndReset(&output);
					AddLine( output.c_str() );
					fPythonFirstTime = false;		// do this only once!
				}
			}
		}
		// or are they just typing in a working line
		else if( fWorkingCursor < kMaxCharsWide - 2 && key != 0 )
		{
			for( i = strlen( fWorkingLine ) + 1; i > fWorkingCursor; i-- )
				fWorkingLine[ i ] = fWorkingLine[ i - 1 ];

			fWorkingLine[ fWorkingCursor++ ] = key;

			findAgain = false;
			findCounter = 0;
			IUpdateTooltip();
		}
	}
}

//// IAddLineCallback ////////////////////////////////////////////////////////

void	pfConsole::IAddLineCallback( const char *string )
{
	fTheConsole->IAddParagraph( string, 0 );
}

//// IAddLine ////////////////////////////////////////////////////////////////

void	pfConsole::IAddLine( const char *string, short leftMargin )
{
	int			i;
	char		*ptr;


	/// Advance upward
	for( i = 0, ptr = fDisplayBuffer; i < fNumDisplayLines - 1; i++ )
	{
		memcpy( ptr, ptr + kMaxCharsWide, kMaxCharsWide );
		ptr += kMaxCharsWide;
	}

	if( string[ 0 ] == '\t' )
	{
		leftMargin += 4;
		string++;
	}

	memset( ptr, 0, kMaxCharsWide );
	memset( ptr, ' ', leftMargin );
	strncpy( ptr + leftMargin, string, kMaxCharsWide - leftMargin - 1 );
	if( fMode == 0 )
	{
		/// Console is invisible, so show this line for a bit of time
		fMsgTimeoutTimer = kMsgHintTimeout;
	}
}

//// IAddParagraph ///////////////////////////////////////////////////////////

void	pfConsole::IAddParagraph( const char *s, short margin )
{
	char		*ptr, *ptr2, *ptr3, *string=(char*)s;


	// Special character: if \i is in front of the string, indent it
	while( strncmp( string, "\\i", 2 ) == 0 )
	{
		margin += 3;
		string += 2;
	}

	for( ptr = string; ptr != nil && *ptr != 0; )
	{
		// Go as far as possible
		if( strlen( ptr ) < kMaxCharsWide - margin - margin - 1 )
			ptr2 = ptr + strlen( ptr );
		else
		{
			// Back up until we hit a sep
			ptr2 = ptr + kMaxCharsWide - margin - margin - 1;
			for( ; ptr2 > string && *ptr2 != ' ' && *ptr2 != '\t' && *ptr2 != '\n'; ptr2-- );
		}

		// Check for carriage return
		ptr3 = strchr( ptr, '\n' );
		if( ptr3 == ptr )
		{
			IAddLine( "", margin );
			ptr++;
			continue;
		}
		if( ptr3 != nil && ptr3 < ptr2 )
			ptr2 = ptr3;

		// Add this part
		if( ptr2 == ptr || *ptr2 == 0 )
		{
			IAddLine( ptr, margin );
			break;
		}

		*ptr2 = 0;
		IAddLine( ptr, margin );
		ptr = ptr2 + 1;
	}
}

//// IClear //////////////////////////////////////////////////////////////////

void	pfConsole::IClear( void )
{
	memset( fDisplayBuffer, 0, kMaxCharsWide * fNumDisplayLines );
}

//// Draw ////////////////////////////////////////////////////////////////////

void	pfConsole::Draw( plPipeline *p )
{
	int			i, yOff, y, x, eOffset, height;
	char		*line;
	char		tmp[ kMaxCharsWide ];
	hsBool		showTooltip = false;
	float		thisTime;	// For making the console FX speed konstant regardless of framerate
	const float	kEffectDuration = 0.5f;


	plDebugText&	drawText = plDebugText::Instance();
	
	thisTime = (float)hsTimer::PrecTicksToSecs( hsTimer::GetPrecTickCount() );

	if( fMode == kModeHidden && fEffectCounter == 0 )
	{
		if( fMsgTimeoutTimer > 0 )
		{
			/// Message hint--draw the last line of the console for a bit
			drawText.DrawString( 10, 4, fDisplayBuffer + kMaxCharsWide * ( fNumDisplayLines - 1 ), fConsoleTextColor );
			fMsgTimeoutTimer--;
		}
		fLastTime = thisTime;
		return;
	}

	drawText.SetDrawOnTopMode( true );

	yOff = drawText.GetFontHeight() + 2;
	if( fMode == kModeSingleLine )
		height = yOff * 3 + 14;
	else
		height = yOff * ( fNumDisplayLines + 2 ) + 14;

	if( fHelpTimer == 0 && !fHelpMode && fLastHelpMsg[ 0 ] != 0 )
		showTooltip = true;
	
	if( fEffectCounter > 0 )
	{
		int	numElapsed = (int)( (float)kEffectDivisions * ( ( thisTime - fLastTime ) / (float)kEffectDuration ) );
		if( numElapsed > fEffectCounter )
			numElapsed = fEffectCounter;
		else if( numElapsed < 0 )
			numElapsed = 0;

		if( fMode == kModeSingleLine )
			eOffset = fEffectCounter * height / kEffectDivisions;
		else if( fMode == kModeFull )
			eOffset = fEffectCounter * ( height - yOff * 3 - 14 ) / kEffectDivisions;
		else
			eOffset = ( kEffectDivisions - fEffectCounter ) * ( height - yOff * 3 - 14 ) / kEffectDivisions;
		fEffectCounter -= numElapsed;
	}
	else 
		eOffset = 0;
	fLastTime = thisTime;

	if( fMode == kModeSingleLine )
	{
		// Bgnd (TEMP ONLY)
		x = kMaxCharsWide * drawText.CalcStringWidth( "W" ) + 4;
		y = height - eOffset;
		drawText.DrawRect( 4, 0, x, y, /*color*/0, 0, 0, 127 );

		/// Actual text
		if( fEffectCounter == 0 )
			drawText.DrawString( 10, 4, "Plasma 2.0 Console", 255, 255, 255, 255 );

		if( !showTooltip )
			drawText.DrawString( 10, 4 + yOff - eOffset, fDisplayBuffer + kMaxCharsWide * ( fNumDisplayLines - 1 ), fConsoleTextColor );

		y = 4 + yOff + yOff - eOffset;
	}
	else
	{
		// Bgnd (TEMP ONLY)
		x = kMaxCharsWide * drawText.CalcStringWidth( "W" ) + 4;
		y = yOff * ( fNumDisplayLines + 2 ) + 14 - eOffset;
		drawText.DrawRect( 4, 0, x, y, /*color*/0, 0, 0, 127 );

		/// Actual text
		drawText.DrawString( 10, 4, "Plasma 2.0 Console", 255, 255, 255, 255 );

		static int	countDown = 3000;
		if( fHelpTimer > 0 || fEffectCounter > 0 || fMode != kModeFull )
			countDown = 3000;
		else if( countDown > -720 )
			countDown--;

		// Resource data is encrypted so testers can't peer in to the EXE, plz don't decrypt
		static bool rezLoaded = false;
		static char tmpSrc[ kMaxCharsWide ];
		if( !rezLoaded )
		{
			memset( tmp, 0, sizeof( tmp ) );
			memset( tmpSrc, 0, sizeof( tmpSrc ) );
			// Our concession to windows
#ifdef HS_BUILD_FOR_WIN32
			#include "../../Apps/plClient/res/resource.h"
			HRSRC rsrc = FindResource( nil, MAKEINTRESOURCE( IDR_CNSL1 ), "CNSL" );
			if( rsrc != nil )
			{
				HGLOBAL hdl = LoadResource( nil, rsrc );
				if( hdl != nil )
				{
					UInt8 *ptr = (UInt8 *)LockResource( hdl );
					if( ptr != nil )
					{
						for( i = 0; i < SizeofResource( nil, rsrc ); i++ )
							tmpSrc[ i ] = ptr[ i ] + 26;
						UnlockResource( hdl );
					}
				}
			}
			rezLoaded = true;
#else
			// Need to define for other platforms?
#endif
		}
		memcpy( tmp, tmpSrc, sizeof( tmp ) );

		if( countDown <= 0 )
		{
			y = 4 + yOff - eOffset;
			if( countDown <= -480 )
			{
				tmp[ ( (-countDown - 480)>> 4 ) + 1 ] = 0;
				drawText.DrawString( 10, y, tmp, fConsoleTextColor );
			}
			y += yOff * ( fNumDisplayLines - ( showTooltip ? 1 : 0 ) );
		}
		else
		{
			for( i = 0, y = 4 + yOff - eOffset, line = fDisplayBuffer; i < fNumDisplayLines - ( showTooltip ? 1 : 0 ); i++ )
			{
				drawText.DrawString( 10, y, line, fConsoleTextColor );
				y += yOff;
				line += kMaxCharsWide;
			}
		}

		if( showTooltip )
			y += yOff;
	}

//	strcpy( tmp, fHelpMode ? "Get Help On:" : "]" );
	if ( fHelpMode )
		strcpy( tmp, "Get Help On:");
	else if (fPythonMode )
		if ( fPythonMultiLines == 0 )
			strcpy( tmp, ">>>");
		else
			strcpy( tmp, "...");
	else
		strcpy( tmp, "]" );

	drawText.DrawString( 10, y, tmp, 255, 255, 255, 255 );
	i = 10 + drawText.CalcStringWidth( tmp ) + 4;
	drawText.DrawString( i, y, fWorkingLine, fConsoleTextColor );

	if( fCursorTicks >= 0 )
	{
		strcpy( tmp, fWorkingLine );
		tmp[ fWorkingCursor ] = 0;
		x = drawText.CalcStringWidth( tmp );
		drawText.DrawString( i + x, y + 2, "_", 255, 255, 255 );
	}
	fCursorTicks--;
	if( fCursorTicks < -kCursorBlinkRate )
		fCursorTicks = kCursorBlinkRate;

	if( showTooltip )
		drawText.DrawString( i, y - yOff, fLastHelpMsg, 255, 255, 0 );
	else
		fHelpTimer--;

	drawText.SetDrawOnTopMode( false );
}

//// IUpdateTooltip //////////////////////////////////////////////////////////

void	pfConsole::IUpdateTooltip( void )
{
	char	tmpStr[ kWorkingLineSize ];
	char	*c;


	strcpy( tmpStr, fWorkingLine );
	c = (char *)fEngine->GetCmdSignature( tmpStr );
	if( c == nil || strcmp( c, fLastHelpMsg ) != 0 )
	{
		/// Different--update timer to wait
		fHelpTimer = kHelpDelay;
		strncpy( fLastHelpMsg, c ? c : "", kMaxCharsWide - 2 );
	}
}

//// IPrintSomeHelp //////////////////////////////////////////////////////////

void	pfConsole::IPrintSomeHelp( void )
{
	char	msg1[] = "The console contains commands arranged under groups and subgroups. \
To use a command, you type the group name plus the command, such as 'Console.Clear' or \
'Console Clear'.";
	
	char	msg2[] = "To get help on a command or group, type '?' followed by the command or \
group name. Typing '?' and just hitting enter will bring up this message. Typing '?' and \
then 'commands' will bring up a list of all base groups and commands.";

	char	msg3[] = "You can also have the console auto-complete a command by pressing tab. \
This will search for a group or command that starts with what you have typed. If there is more \
than one match, pressing tab repeatedly will cycle through all the matches.";


	AddLine( "" );
	AddLine( "How to use the console:" );
	IAddParagraph( msg1, 2 );
	AddLine( "" );
	IAddParagraph( msg2, 2 );
	AddLine( "" );
	IAddParagraph( msg3, 2 );
	AddLine( "" );
}


//============================================================================
void pfConsole::AddLineF(const char * fmt, ...) {
	char str[1024];
	va_list args;
	va_start(args, fmt);
	_vsnprintf(str, arrsize(str), fmt, args);
	va_end(args);
	AddLine(str);
}

//============================================================================
void pfConsole::RunCommandAsync (const char cmd[]) {

	plConsoleMsg * consoleMsg = NEWZERO(plConsoleMsg);
	consoleMsg->SetCmd(plConsoleMsg::kExecuteLine);
	consoleMsg->SetString(cmd);
//	consoleMsg->SetBreakBeforeDispatch(true);
	consoleMsg->Send(nil, true);
}