//-----------------------------------------------------------------------------------------------
// Console.cpp
//
#include "GypsyPrecompiledHeader.hpp"
#include "Engine/Core/PlatformDefines.hpp"
#if defined( GYPSY_ENABLE_CONSOLE )

//-----------------------------------------------------------------------------------------------
#include "Engine/Console/Console.hpp"
#include "Engine/Core/Application.hpp"
#include "Engine/Core/NamedProperties.hpp"
#include "Engine/Core/Events.hpp"
#include "Engine/Renderer/Renderer.hpp"
#include <stdarg.h>


//-----------------------------------------------------------------------------------------------
// HORRIBLE TEMP HACKS HERE
#include "Engine/Renderer/Renderer_OpenGL3_Win32.hpp"


//-----------------------------------------------------------------------------------------------
const float COMMAND_INPUT_LINE_SCALE = 1.3f;
const float LINE_HEIGHTS_FOR_COMMAND_PROMPT_PADDING = 0.1f;
const float LINE_HEIGHTS_FOR_COMMAND_PROMPT = (2.f * COMMAND_INPUT_LINE_SCALE) + (3.f * LINE_HEIGHTS_FOR_COMMAND_PROMPT_PADDING);
const Rgba COMMAND_PROMPT_INPUT_COLOR( 255, 200, 0, 255 );
//const Rgba COMMAND_PROMPT_MARKER_COLOR( 180, 144, 0, 255 );
const Rgba COMMAND_PROMPT_MARKER_COLOR( 255, 200, 0, 255 );
const Rgba TEXT_SHADOW_COLOR( 0, 0, 0, 255 );
const Vector2 TEXT_SHADOW_OFFSET( 2.f, -2.f );
const float CONSOLE_TEXT_EDGE_THRESHOLD = 0.1f;
const float CONSOLE_TEXT_SOFTNESS = 1.0;

//-----------------------------------------------------------------------------------------------
EXTERN Console* theConsole = nullptr;


//-----------------------------------------------------------------------------------------------
STATIC void Console::Create()
{
	theConsole = new Console();
}


//-----------------------------------------------------------------------------------------------
Console::Console()
	: m_displayMode( CONSOLE_DISPLAY_MODE_HIDDEN )
	, m_defaultFont( nullptr )
	, m_mostRecentTextLineIndex( -1 )
	, m_numLinesScrolledBack( 0 )
	, m_consoleLogCapacity( 10000 )
	, m_linesOnScreenVertically( 39.4f ) // Having a partial line at the top of the screen helps reinforce that "there's more up there".  When scrolling back, try to have fractional lines on top and bottom.
	, m_commandInputCursorPos( 0 )
	, m_timeBlinkStarted( 0.0 )
	, m_secondsToHoldMinimalTextLine( 3.f )
	, m_secondsToFadeMinimalTextLine( 1.f )
{
}


//-----------------------------------------------------------------------------------------------
Console::~Console()
{
	theConsole = nullptr;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::StartUp()
{
	m_logTextLines.resize( m_consoleLogCapacity );
	m_mostRecentTextLineIndex = 0;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::EndFrame()
{
	BeginRendering();

	if( m_displayMode == CONSOLE_DISPLAY_MODE_FULL )
	{
		RenderFull();
	}
	else if( m_displayMode == CONSOLE_DISPLAY_MODE_MINIMAL )
	{
		RenderMinimal();
	}

	EndRendering();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL bool Console::ProcessSystemEventPreGame( const SystemEvent& systemEvent )
{
	static int pcIndex = GetProfileCategoryIndexForName( "Input.Msg.PreGame.Console", Rgba( 255, 255, 0 ) );
	ScopedProfileSection profileSection( pcIndex );

	if( m_displayMode == CONSOLE_DISPLAY_MODE_HIDDEN )
		return false;
	
	if( systemEvent.m_eventType == EVENT_TYPE_BUTTON_PRESSED || systemEvent.m_eventType == EVENT_TYPE_BUTTON_REPEATED )
	{
		switch( systemEvent.m_keyButtonCode )
		{
			case BUTTON_KEYBOARD_LEFTARROW:		MoveCursorToLeft();					break;
			case BUTTON_KEYBOARD_RIGHTARROW:	MoveCursorToRight();				break;
			case BUTTON_KEYBOARD_HOME:			OnHomeInConsole();					break;
			case BUTTON_KEYBOARD_END:			OnEndInConsole();					break;
			case BUTTON_KEYBOARD_PGUP:			ScrollConsoleLog( 10 );				break;
			case BUTTON_KEYBOARD_PGDN:			ScrollConsoleLog( -10 );			break;
			case BUTTON_KEYBOARD_BACKSPACE:		DeleteCharacterToLeftOfCursor();	break;
			case BUTTON_KEYBOARD_DELETE:		DeleteCharacterToRightOfCursor();	break;
			case BUTTON_KEYBOARD_ENTER:			OnEnterInConsole();					break;
			case BUTTON_KEYBOARD_ESCAPE:		OnEscapeInConsole();				break;
			case BUTTON_KEYBOARD_TILDE:			OnTildeInConsole();					break;
		}

		// Consume ALL button presses when console is open
		// ToDo: Really?  Are there any exceptions to this rule?  Is this too draconian?
		return true;
	}
	else if( systemEvent.m_eventType == EVENT_TYPE_BUTTON_RELEASED )
	{
		// Consume ALL button releases (really?) when console is open...
		// ToDo: are there any exceptions to this rule?  is this too draconian?
		return true;
	}
	else if( systemEvent.m_eventType == EVENT_TYPE_TEXT_CHARACTER_TYPED )
	{
		if( systemEvent.m_utf32TextCharacter == '`' ) // Disallow (and consume) the unshifted tilde (grave) key character (typically used to open the console)
		{			
			return true;
		}

		// ToDo: unicode / locale support
		if( systemEvent.m_utf32TextCharacter < 256 )
		{
			unsigned char latinChar = static_cast< unsigned char >( systemEvent.m_utf32TextCharacter );
			if( IsTypeableLatinCharacter( latinChar ) )
				TypeLatinCharacter( latinChar );
		}
		return true; // Consume ALL characters (e.g. WM_CHAR in Windows) typed when console is open
	}
	else if( systemEvent.m_eventType == EVENT_TYPE_MOUSE_WHEEL_MOVED )
	{
		int numMouseWheelPositions = systemEvent.m_miscData;
		bool isCtrlKeyDown = theApplication->IsControlDown();
		if( isCtrlKeyDown )
			numMouseWheelPositions *= 10;

		ScrollConsoleLog( numMouseWheelPositions );
	}

	return false; // Not a key we care about; don't consume it.
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::SetDefaultFont( BitmapFont* defaultFont )
{
	m_defaultFont = defaultFont;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::SetConsoleCapacity( int maxTextLines )
{
	if( m_logTextLines.empty() )
	{
		m_consoleLogCapacity = maxTextLines;
		m_logTextLines.resize( m_consoleLogCapacity );
	}
	else
	{
		FATAL_ERROR( "Console::SetConsoleCapacity() not yet implemented for an already-existing ." );
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::RestartCursorTiming()
{
	m_timeBlinkStarted = GetCurrentTimeSeconds();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::ScrollConsoleLogForwardToCurrentPosition()
{
	ScrollConsoleLog( -m_consoleLogCapacity );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::ScrollConsoleLogBackToBeginningOfContent()
{
	m_numLinesScrolledBack = 0;
	int leastRecentTextLineIndex = m_mostRecentTextLineIndex + 1;
	for( int numLinesToAdvance = 0; numLinesToAdvance < m_consoleLogCapacity-1; ++ numLinesToAdvance )
	{
		int indexToCheck = (leastRecentTextLineIndex + numLinesToAdvance) % m_consoleLogCapacity;
		ConsoleTextLine& textLine = m_logTextLines[ indexToCheck ];
		if( !textLine.m_text.empty() )
		{
			int displacementBackwardFromMostRecentIndex = m_mostRecentTextLineIndex - indexToCheck;
			if( displacementBackwardFromMostRecentIndex < 0 )
				displacementBackwardFromMostRecentIndex += m_consoleLogCapacity;

			int numLinesComfortablyOnscreen = static_cast< int >( m_linesOnScreenVertically ) - 1;
			ScrollConsoleLog( displacementBackwardFromMostRecentIndex - numLinesComfortablyOnscreen );
			return;
		}
	}
}


//-----------------------------------------------------------------------------------------------
// Pass a negative number to scroll forward
//
VIRTUAL void Console::ScrollConsoleLog( int numLinesToScrollBack )
{
	m_numLinesScrolledBack += numLinesToScrollBack;
	m_numLinesScrolledBack = Clamp( m_numLinesScrolledBack, 0, m_consoleLogCapacity );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::MoveCursorToLeft()
{
	m_commandInputCursorPos = Clamp( m_commandInputCursorPos-1, 0, (int) m_commandInputContents.size() );
	RestartCursorTiming();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::MoveCursorToRight()
{
	m_commandInputCursorPos = Clamp( m_commandInputCursorPos+1, 0, (int) m_commandInputContents.size() );
	RestartCursorTiming();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::MoveCursorToBeginningOfLine()
{
	m_commandInputCursorPos = 0;
	RestartCursorTiming();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::MoveCursorToEndOfLine()
{
	m_commandInputCursorPos = (int) m_commandInputContents.size();
	RestartCursorTiming();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::DeleteCharacterToLeftOfCursor()
{
	if( m_commandInputCursorPos > 0 )
	{
		-- m_commandInputCursorPos;
		m_commandInputContents.erase( m_commandInputCursorPos, 1 );
	}

	RestartCursorTiming();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::DeleteCharacterToRightOfCursor()
{
	if( m_commandInputCursorPos < (int) m_commandInputContents.size() )
	{
		m_commandInputContents.erase( m_commandInputCursorPos, 1 );
	}

	RestartCursorTiming();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::OnHomeInConsole()
{
	if( theApplication->IsControlDown() )
	{
		ScrollConsoleLogBackToBeginningOfContent();
	}
	else
	{
		MoveCursorToBeginningOfLine();
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::OnEndInConsole()
{
	if( theApplication->IsControlDown() )
	{
		ScrollConsoleLogForwardToCurrentPosition();
	}
	else
	{
		MoveCursorToEndOfLine();
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::OnEnterInConsole()
{
	if( m_commandInputContents.empty() )
		SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_HIDDEN );
	else
	{
		m_numLinesScrolledBack = 0; // When the user enters something manually, snap the console log back to "current"
		ExecuteAndEmptyCommandLine();
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::OnEscapeInConsole()
{
	if( m_commandInputContents.empty() )
		SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_HIDDEN );
	else
		ClearCommandLine();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::OnTildeInConsole()
{
	if( !theApplication->IsShiftDown() )
	{
		if( m_displayMode == CONSOLE_DISPLAY_MODE_FULL )
		{
			SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_HIDDEN );
		}
		else
		{
			SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_FULL );
		}
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL bool Console::IsTypeableUnicodeCharacter( unsigned int unicodeCharacterUTF32 )
{
	UNUSED( unicodeCharacterUTF32 );
	// ToDo: use isPrint( char, locale ) to determine?
	FATAL_ERROR( "Console::IsTypeableUnicodeCharacter() not yet implemented!" );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL bool Console::IsTypeableLatinCharacter( unsigned char latinChar )
{
	if( isprint( latinChar ) )
		return true;

	return false;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::TypeUnicodeCharacter( unsigned int unicodeCharacterUTF32 )
{
	unsigned char latinChar = static_cast< unsigned char >( unicodeCharacterUTF32 );
	TypeLatinCharacter( latinChar );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::TypeLatinCharacter( unsigned char latinChar )
{
	m_commandInputContents.insert( m_commandInputCursorPos, 1, latinChar );
	++ m_commandInputCursorPos;
	RestartCursorTiming();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::ExecuteAndEmptyCommandLine()
{
	std::string commandStringToExecute = m_commandInputContents;
	ClearCommandLine();
	ExecuteCommandString( commandStringToExecute );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::ExecuteCommandString( const std::string& commandStringToExecute )
{
	std::string printLine = ">" + commandStringToExecute;
	PrintLine( COMMAND_PROMPT_INPUT_COLOR, printLine.c_str() );

	std::string commandName;
	std::string commandArgs;
	std::vector< std::string > commandArgList;
	ParseCommandsAndArgs( commandStringToExecute, commandName, commandArgs, commandArgList );

	NamedProperties params;
	params.Set( "Args", commandArgs );
	params.Set( "ArgList", commandArgList );
	int numRecipients = FireEvent( commandName, params );
	if( numRecipients == 0 )
	{
		ConsolePrintf( Rgba::RED, "ERROR: Unknown command \"%s\".  Type Help for a list of commands.\n", commandName.c_str() );
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::BeginRendering()
{
	theRenderer->PushMatrix();
	const float screenAspect = theApplication->GetPhysicalWindowClientAreaAspectRatio();
	const Vector2 consoleVirtualScreenSize( 1600.f, 1600.f / screenAspect );
	theRenderer->SetScreenVirtualOrtho( consoleVirtualScreenSize, false );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::EndRendering()
{
	theRenderer->PopMatrix();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::RenderFull()
{
	const float numPhysicalRowHeightsOnScreenVertically = m_linesOnScreenVertically + LINE_HEIGHTS_FOR_COMMAND_PROMPT;
	const float screenAspect = theApplication->GetPhysicalWindowClientAreaAspectRatio();
	const Vector2 consoleVirtualScreenSize( 1600.f, 1600.f / screenAspect );
	const float lineHeight = consoleVirtualScreenSize.y / numPhysicalRowHeightsOnScreenVertically;
	const float commandPromptPadding = lineHeight * LINE_HEIGHTS_FOR_COMMAND_PROMPT_PADDING;
	const float totalCommandPromptLineHeight = LINE_HEIGHTS_FOR_COMMAND_PROMPT + LINE_HEIGHTS_FOR_COMMAND_PROMPT_PADDING;
	const int numLinesAtLeastPartiallyVisibleUnderCommandPrompt = static_cast< int >( floor( 0.5f + totalCommandPromptLineHeight ) );
	int numLinesClippedOffScreenBottom = Clamp( m_numLinesScrolledBack - numLinesAtLeastPartiallyVisibleUnderCommandPrompt, 0, m_consoleLogCapacity );
	int numLinesOfConsoleLogHistoryToRender = static_cast< int >( ceil( m_linesOnScreenVertically ) ) + numLinesAtLeastPartiallyVisibleUnderCommandPrompt;
	int lineIndexToRenderAtBottom = m_mostRecentTextLineIndex - numLinesClippedOffScreenBottom;
	lineIndexToRenderAtBottom = (lineIndexToRenderAtBottom + m_consoleLogCapacity) % m_consoleLogCapacity;
	int lineIndexToRenderAtTop = 1 + lineIndexToRenderAtBottom - numLinesOfConsoleLogHistoryToRender;
	while( lineIndexToRenderAtTop < 0 )
		lineIndexToRenderAtTop += m_consoleLogCapacity;

	lineIndexToRenderAtTop %= m_consoleLogCapacity;
	theRenderer->Draw2dAABB( Vector2::ZERO, consoleVirtualScreenSize, Rgba( 0, 0, 0, 150 ) );

	float initialLineBoundsMinsY = lineHeight * LINE_HEIGHTS_FOR_COMMAND_PROMPT;
	AABB2 lineBounds( commandPromptPadding, initialLineBoundsMinsY, consoleVirtualScreenSize.x - commandPromptPadding, initialLineBoundsMinsY + lineHeight );
	if( m_numLinesScrolledBack > 0 )
	{
		lineBounds.Translate( Vector2( 0.f, -lineHeight * static_cast< float >( m_numLinesScrolledBack ) ) );
		lineBounds.Translate( Vector2( 0.f, lineHeight * static_cast< float >( numLinesClippedOffScreenBottom ) ) );
	}

	ShaderProgram* textEffect = theRenderer->CreateOrGetShaderProgram( "TextBasic", "TextBasic" );
	theRenderer->UseShaderProgram( textEffect );
	glUniform1f( textEffect->m_uniformLocation_edgeThreshold, CONSOLE_TEXT_EDGE_THRESHOLD );
	glUniform1f( textEffect->m_uniformLocation_softness, CONSOLE_TEXT_SOFTNESS );

	float linesToDraw = static_cast< float >( numLinesOfConsoleLogHistoryToRender - 1 );
	lineBounds += Vector2( 0.f, linesToDraw * lineHeight );
	int lineIndexToRenderNext = lineIndexToRenderAtTop;
	for( int lineDrawCount = 0; lineDrawCount < numLinesOfConsoleLogHistoryToRender; ++ lineDrawCount )
	{
		ConsoleTextLine& textLine = m_logTextLines[ lineIndexToRenderNext ];
		AABB2 shadowBounds = lineBounds + TEXT_SHADOW_OFFSET;
		theRenderer->Draw2dTextStringInBox( textLine.m_text.c_str(), shadowBounds, lineHeight, TEXT_SHADOW_COLOR, ALIGNMENT_2D_CENTERLEFT, m_defaultFont );
		theRenderer->Draw2dTextStringInBox( textLine.m_text.c_str(), lineBounds, lineHeight, textLine.m_color, ALIGNMENT_2D_CENTERLEFT, m_defaultFont );
		lineBounds -= Vector2( 0.f, lineHeight );
		// FIXME: the below condition kept the console from wrapping around visibly when we were drawing bottom-to-top, but doesn't work top-to-bottom
// 		if( lineIndexToRenderNext == m_mostRecentTextLineIndex )
// 			break;

		++ lineIndexToRenderNext;
		if( lineIndexToRenderNext > m_consoleLogCapacity - 1 )
			lineIndexToRenderNext = 0;
	}

	RenderCommandPrompt();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::RenderMinimal()
{
	double timeNow = GetCurrentTimeSeconds();
	float totalSecondsToShowMinimalTextLine = m_secondsToHoldMinimalTextLine + m_secondsToFadeMinimalTextLine;
	const float numPhysicalRowHeightsOnScreenVertically = m_linesOnScreenVertically + LINE_HEIGHTS_FOR_COMMAND_PROMPT;
	const float screenAspect = theApplication->GetPhysicalWindowClientAreaAspectRatio();
	const Vector2 consoleVirtualScreenSize( 1600.f, 1600.f / screenAspect );
	const float lineHeight = consoleVirtualScreenSize.y / numPhysicalRowHeightsOnScreenVertically;
	const float commandPromptPadding = lineHeight * LINE_HEIGHTS_FOR_COMMAND_PROMPT_PADDING;
	int numLinesOfConsoleLogHistoryToRender = static_cast< int >( ceil( m_linesOnScreenVertically ) );
	int lineIndexToRenderAtBottom = m_mostRecentTextLineIndex;
	int lineIndexToRenderAtTop = 1 + lineIndexToRenderAtBottom - numLinesOfConsoleLogHistoryToRender;
	lineIndexToRenderAtTop = (lineIndexToRenderAtTop + m_consoleLogCapacity) % m_consoleLogCapacity;

	float initialLineBoundsMinsY = lineHeight * LINE_HEIGHTS_FOR_COMMAND_PROMPT;
	AABB2 lineBounds( commandPromptPadding, initialLineBoundsMinsY, consoleVirtualScreenSize.x - commandPromptPadding, initialLineBoundsMinsY + lineHeight );
	int lineIndexToRenderNext = lineIndexToRenderAtBottom;
	for( int lineDrawCount = 0; lineDrawCount < numLinesOfConsoleLogHistoryToRender; ++ lineDrawCount )
	{
		ConsoleTextLine& textLine = m_logTextLines[ lineIndexToRenderNext ];
		float textLineAgeSeconds = static_cast< float >( timeNow - textLine.m_absoluteTimePrinted );
		float textLineAlpha = RangeMapFloat( textLineAgeSeconds, m_secondsToHoldMinimalTextLine, totalSecondsToShowMinimalTextLine, 1.f, 0.f );
		textLineAlpha = Clamp( textLineAlpha, 0.f, 1.f );

		if( textLineAlpha > 0.f )
		{
			Rgba textLineDisplayColor( textLine.m_color );
			textLineDisplayColor.ScaleAlpha( textLineAlpha );
			theRenderer->Draw2dTextStringInBox( textLine.m_text.c_str(), lineBounds, lineHeight, textLineDisplayColor, ALIGNMENT_2D_CENTERLEFT, m_defaultFont );
		}

		lineBounds += Vector2( 0.f, lineHeight );
		-- lineIndexToRenderNext;
		if( lineIndexToRenderNext < 0 )
			lineIndexToRenderNext = m_consoleLogCapacity - 1;

		if( lineIndexToRenderNext == m_mostRecentTextLineIndex )
			break;
	}

	RenderCommandPrompt();
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::RenderCommandPrompt()
{
	std::string promptMarker = ">";
	BitmapFont* font = m_defaultFont;
	if( !font )
	{
		font = theRenderer->GetDefaultFont();
		if( !font )
			return;
	}

	const float numPhysicalRowHeightsOnScreenVertically = m_linesOnScreenVertically + LINE_HEIGHTS_FOR_COMMAND_PROMPT;
	const float screenAspect = theApplication->GetPhysicalWindowClientAreaAspectRatio();
	const Vector2 consoleVirtualScreenSize( 1600.f, 1600.f / screenAspect );
	const float lineHeight = consoleVirtualScreenSize.y / numPhysicalRowHeightsOnScreenVertically;
	const float commandInputLineHeight = COMMAND_INPUT_LINE_SCALE * lineHeight;
	const float commandPromptPadding = lineHeight * LINE_HEIGHTS_FOR_COMMAND_PROMPT_PADDING;
	const float commandPromptTotalHeight = lineHeight * LINE_HEIGHTS_FOR_COMMAND_PROMPT;
	const AABB2 commandPromptTotalBounds( 0.f, 0.f, consoleVirtualScreenSize.x, commandPromptTotalHeight );
	const AABB2 commandPromptInputBounds( commandPromptPadding, commandPromptPadding, consoleVirtualScreenSize.x - commandPromptPadding, commandInputLineHeight + commandPromptPadding );
	const AABB2 commandPromptHelpBounds( commandPromptPadding, commandPromptInputBounds.maxs.y + commandPromptPadding, consoleVirtualScreenSize.x - commandPromptPadding, commandPromptTotalBounds.maxs.y - commandPromptPadding );
	std::string commandInputContentsLeftOfCursor( m_commandInputContents, 0, m_commandInputCursorPos );
	const float promptWidth = font->CalcTextStringWidth( promptMarker.c_str(), commandInputLineHeight );
	const float cursorPosOffsetX = promptWidth + font->CalcTextStringWidth( commandInputContentsLeftOfCursor.c_str(), commandInputLineHeight );
	const AABB2 commandPromptBounds( commandPromptInputBounds.mins.x, commandPromptInputBounds.mins.y, commandPromptInputBounds.mins.x + promptWidth, commandPromptInputBounds.maxs.y );
	const AABB2 commandPromptInputTextBounds( commandPromptBounds.maxs.x, commandPromptInputBounds.mins.y, commandPromptInputBounds.maxs.x, commandPromptInputBounds.maxs.y );
	const Vector2 cursorTopPos( commandPromptInputBounds.mins.x + cursorPosOffsetX, commandPromptInputBounds.maxs.y );
	const Vector2 cursorBottomPos( commandPromptInputBounds.mins.x + cursorPosOffsetX, commandPromptInputBounds.mins.y );
	const float cursorLineThickness = 1.f;
	const double cursorBlinkOnSeconds = 0.5;
	const double cursorBlinkOffSeconds = 0.3;
	const double cursorBlinkPeriod = (cursorBlinkOnSeconds + cursorBlinkOffSeconds);
	const double timeNow = GetCurrentTimeSeconds();
	const double thisCursorTime = timeNow - m_timeBlinkStarted;
	const double thisCursorFraction = Clamp( thisCursorTime / cursorBlinkPeriod, 0.0, 1.0 );
	AABB2 commandPromptShadowBounds = commandPromptBounds + TEXT_SHADOW_OFFSET;
	AABB2 commandPromptInputTextShadowBounds = commandPromptInputTextBounds + TEXT_SHADOW_OFFSET;
	float pulseAlpha = 1.f - static_cast< float >( thisCursorFraction );
	pulseAlpha *= pulseAlpha;
	Rgba cursorColor( 255, 255, 255, 255 );
	cursorColor.ScaleAlpha( pulseAlpha );
	Rgba promptMarkerColor = Interpolate( COMMAND_PROMPT_MARKER_COLOR, Rgba::WHITE, pulseAlpha );
	if( thisCursorTime > cursorBlinkPeriod )
		m_timeBlinkStarted = timeNow;

	ShaderProgram* basicUnlitShader = theRenderer->CreateOrGetShaderProgram( "UnlitBasic", "UnlitBasic" );
	theRenderer->UseShaderProgram( nullptr );

	theRenderer->Draw2dAABB( commandPromptTotalBounds, Rgba( 255, 255, 255, 100 ) );
	theRenderer->Draw2dAABB( commandPromptInputBounds, Rgba( 0, 0, 0, 200 ) );
	theRenderer->Draw2dAABB( commandPromptHelpBounds, Rgba( 0, 0, 0, 200 ) );

	ShaderProgram* textEffect = theRenderer->CreateOrGetShaderProgram( "TextBasic", "TextBasic" );
	theRenderer->UseShaderProgram( textEffect );
	glUniform1f( textEffect->m_uniformLocation_edgeThreshold, CONSOLE_TEXT_EDGE_THRESHOLD );
	glUniform1f( textEffect->m_uniformLocation_softness, CONSOLE_TEXT_SOFTNESS );

	theRenderer->Draw2dTextStringInBox( promptMarker.c_str(), commandPromptShadowBounds, commandInputLineHeight, TEXT_SHADOW_COLOR, ALIGNMENT_2D_CENTERLEFT, font );
	theRenderer->Draw2dTextStringInBox( m_commandInputContents.c_str(), commandPromptInputTextShadowBounds, commandInputLineHeight, TEXT_SHADOW_COLOR, ALIGNMENT_2D_CENTERLEFT, font );

	theRenderer->Draw2dTextStringInBox( promptMarker.c_str(), commandPromptBounds, commandInputLineHeight, promptMarkerColor, ALIGNMENT_2D_CENTERLEFT, font );
	theRenderer->Draw2dTextStringInBox( m_commandInputContents.c_str(), commandPromptInputTextBounds, commandInputLineHeight, COMMAND_PROMPT_INPUT_COLOR, ALIGNMENT_2D_CENTERLEFT, font );

	theRenderer->UseShaderProgram( basicUnlitShader );
	theRenderer->BindTextureToSampler( nullptr, 0 );
	theRenderer->SetLineThickness( cursorLineThickness );
	theRenderer->Draw2dLineSegment( cursorTopPos, cursorBottomPos, cursorColor, cursorColor );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::Printf( const char* messageFormat, ... )
{
	va_list args;
	va_start( args, messageFormat );
	PrintfV( Rgba::WHITE, messageFormat, args );
	va_end( args );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::Printf( const Rgba& textColor, const char* messageFormat, ... )
{
	va_list args;
	va_start( args, messageFormat );
	PrintfV( textColor, messageFormat, args );
	va_end( args );
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::SetConsoleDisplayMode( ConsoleDisplayMode consoleDisplayMode )
{
	m_displayMode = consoleDisplayMode;
	if( m_displayMode == CONSOLE_DISPLAY_MODE_FULL )
	{
		m_numLinesScrolledBack = 0; // When the console is (re)opened in full mode, start back at the current position
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::ToggleFullConsole()
{
	if( m_displayMode == CONSOLE_DISPLAY_MODE_FULL )
	{
		SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_HIDDEN );
	}
	else
	{
		SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_FULL );
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::ToggleMinimalConsole()
{
	if( m_displayMode == CONSOLE_DISPLAY_MODE_MINIMAL )
	{
		SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_HIDDEN );
	}
	else
	{
		SetConsoleDisplayMode( CONSOLE_DISPLAY_MODE_MINIMAL );
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::ClearCommandLine()
{
	m_commandInputContents.clear();
	m_commandInputCursorPos = 0;
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::PrintfV( const Rgba& textColor, const char* messageFormat, va_list args )
{
	const int PRINTF_STACK_LOCAL_TEMP_LENGTH = 2048;
	char expandedText[ PRINTF_STACK_LOCAL_TEMP_LENGTH ];

#if defined( PLATFORM_APPLE )
	vsnprintf( expandedText, PRINTF_STACK_LOCAL_TEMP_LENGTH, messageFormat, args );
#else
	_vsnprintf_s( expandedText, PRINTF_STACK_LOCAL_TEMP_LENGTH, PRINTF_STACK_LOCAL_TEMP_LENGTH-1, messageFormat, args );
#endif

	PrintChars( textColor, expandedText );
}


//-----------------------------------------------------------------------------------------------
// Chops up text into one or more lines, delimited internally on \n (newline/line feed).
//	Line feed at end is optional, and is ignored (line termination is forced).
//
VIRTUAL void Console::PrintChars( const Rgba& textColor, const char* text )
{
	const int MAX_LINE_LENGTH = 120;
	char lineTextBuffer[ MAX_LINE_LENGTH + 1 ];
	int lineTextBufferIndex = 0;
	const char* scan = text;
	while( *scan != '\0' )
	{
		if( *scan == '\n' || lineTextBufferIndex >= MAX_LINE_LENGTH )
		{
			lineTextBuffer[ lineTextBufferIndex ] = '\0';
			PrintLine( textColor, lineTextBuffer );
			lineTextBufferIndex = 0;
			if( *scan == '\n' )
				++ scan;
		}
		else
		{
			lineTextBuffer[ lineTextBufferIndex ] = *scan;
			++ lineTextBufferIndex;
			++ scan;
		}

	}

	if( lineTextBufferIndex > 0 )
	{
		lineTextBuffer[ lineTextBufferIndex ] = '\0';
		PrintLine( textColor, lineTextBuffer );
	}
}


//-----------------------------------------------------------------------------------------------
VIRTUAL void Console::PrintLine( const Rgba& textLineColor, const char* textLine )
{
	DebuggerPrintf( "CONSOLE: %s\n", textLine );
	++ m_mostRecentTextLineIndex;
	if( m_mostRecentTextLineIndex >= (int) m_logTextLines.size() )
		m_mostRecentTextLineIndex = 0;

	ConsoleTextLine& consoleLine = m_logTextLines[ m_mostRecentTextLineIndex ];
	consoleLine.m_color = textLineColor;
	consoleLine.m_text = textLine;
	consoleLine.m_absoluteTimePrinted = GetCurrentTimeSeconds();
}



#endif // GYPSY_ENABLE_CONSOLE
