//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//===========================================================================//

#include "vgui_controls/consoledialog.h"

#include "vgui/IInput.h"
#include "vgui/IScheme.h"
#include "vgui/IVGui.h"
#include "vgui/ISurface.h"
#include "vgui/ILocalize.h"
#include "KeyValues.h"

#include "vgui_controls/Button.h"
#include "vgui/KeyCode.h"
#include "vgui_controls/Menu.h"
#include "vgui_controls/TextEntry.h"
#include "vgui_controls/RichText.h"
#include "tier1/convar.h"
#include "tier1/convar_serverbounded.h"
#include "icvar.h"
#include "filesystem.h"

#include <stdlib.h>

#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

using namespace vgui;


//-----------------------------------------------------------------------------
// Used by the autocompletion system
//-----------------------------------------------------------------------------
class CNonFocusableMenu : public Menu
{
	DECLARE_CLASS_SIMPLE( CNonFocusableMenu, Menu );

public:
	CNonFocusableMenu( Panel *parent, const char *panelName )
		: BaseClass( parent, panelName ),
		m_pFocus( 0 )
	{
	}

	void SetFocusPanel( Panel *panel )
	{
		m_pFocus = panel;
	}

	VPANEL GetCurrentKeyFocus()
	{
		if ( !m_pFocus )
			return GetVPanel();

		return m_pFocus->GetVPanel();
	}

private:
	Panel		*m_pFocus;
};


//-----------------------------------------------------------------------------
// Purpose: forwards tab key presses up from the text entry so we can do autocomplete
//-----------------------------------------------------------------------------
class TabCatchingTextEntry : public TextEntry
{
public:
	TabCatchingTextEntry(Panel *parent, const char *name, VPANEL comp) : TextEntry(parent, name), m_pCompletionList( comp )
	{
		SetAllowNonAsciiCharacters( true );
		SetDragEnabled( true );
	}

	virtual void OnKeyCodeTyped(KeyCode code)
	{
		if (code == KEY_TAB)
		{
			GetParent()->OnKeyCodeTyped(code);
		}
		else if ( code == KEY_ENTER )
		{
			// submit is the default button whose click event will have been called already
		}
		else
		{
			TextEntry::OnKeyCodeTyped(code);
		}
	}

	virtual void OnKillFocus()
	{
		if ( input()->GetFocus() != m_pCompletionList ) // if its not the completion window trying to steal our focus
		{
			PostMessage(GetParent(), new KeyValues("CloseCompletionList"));
		}
	}

private:
	VPANEL m_pCompletionList;
};



// Things the user typed in and hit submit/return with
CHistoryItem::CHistoryItem( void )
{
	m_text = NULL;
	m_extraText = NULL;
	m_bHasExtra = false;
}

CHistoryItem::CHistoryItem( const char *text, const char *extra )
{
	Assert( text );
	m_text = NULL;
	m_extraText = NULL;
	m_bHasExtra = false;
	SetText( text , extra );
}

CHistoryItem::CHistoryItem( const CHistoryItem& src )
{
	m_text = NULL;
	m_extraText = NULL;
	m_bHasExtra = false;
	SetText( src.GetText(), src.GetExtra() );
}

CHistoryItem::~CHistoryItem( void )
{
	delete[] m_text;
	delete[] m_extraText;
	m_text = NULL;
}

const char *CHistoryItem::GetText() const
{
	if ( m_text )
	{
		return m_text;
	}
	else
	{
		return "";
	}
}

const char *CHistoryItem::GetExtra() const
{
	if ( m_extraText )
	{
		return m_extraText;
	}
	else
	{
		return NULL;
	}
}

void CHistoryItem::SetText( const char *text, const char *extra )
{
	delete[] m_text;
	int len = strlen( text ) + 1;

	m_text = new char[ len ];
	Q_memset( m_text, 0x0, len );
	Q_strncpy( m_text, text, len );

	if ( extra )
	{
		m_bHasExtra = true;
		delete[] m_extraText;
		int elen = strlen( extra ) + 1;
		m_extraText = new char[ elen ];
		Q_memset( m_extraText, 0x0, elen);
		Q_strncpy( m_extraText, extra, elen );
	}
	else
	{
		m_bHasExtra = false;
	}
}


//-----------------------------------------------------------------------------
//
// Console page completion item starts here
//
//-----------------------------------------------------------------------------
CConsolePanel::CompletionItem::CompletionItem( void )
{
	m_bIsCommand = true;
	m_pCommand = NULL;
	m_pText = NULL;
}

CConsolePanel::CompletionItem::CompletionItem( const CompletionItem& src )
{
	m_bIsCommand = src.m_bIsCommand;
	m_pCommand = src.m_pCommand;
	if ( src.m_pText )
	{
		m_pText = new CHistoryItem( (const CHistoryItem& )src.m_pText );
	}
	else
	{
		m_pText = NULL;
	}
}

CConsolePanel::CompletionItem& CConsolePanel::CompletionItem::operator =( const CompletionItem& src )
{
	if ( this == &src )
		return *this;

	m_bIsCommand = src.m_bIsCommand;
	m_pCommand = src.m_pCommand;
	if ( src.m_pText )
	{
		m_pText = new CHistoryItem( (const CHistoryItem& )*src.m_pText );
	}
	else
	{
		m_pText = NULL;
	}

	return *this;
}

CConsolePanel::CompletionItem::~CompletionItem( void )
{
	if ( m_pText )
	{
		delete m_pText;
		m_pText = NULL;
	}
}

const char *CConsolePanel::CompletionItem::GetName() const
{
	if ( m_bIsCommand )
		return m_pCommand->GetName();
	return m_pCommand ? m_pCommand->GetName() : GetCommand();
}

const char *CConsolePanel::CompletionItem::GetItemText( void )
{
	static char text[256];
	text[0] = 0;
	if ( m_pText )
	{
		if ( m_pText->HasExtra() )
		{
			Q_snprintf( text, sizeof( text ), "%s %s", m_pText->GetText(), m_pText->GetExtra() );
		}
		else
		{
			Q_strncpy( text, m_pText->GetText(), sizeof( text ) );
		}
	}
	return text;
}	

const char *CConsolePanel::CompletionItem::GetCommand( void ) const
{
	static char text[256];
	text[0] = 0;
	if ( m_pText )
	{
		Q_strncpy( text, m_pText->GetText(), sizeof( text ) );
	}
	return text;
}


//-----------------------------------------------------------------------------
//
// Console page starts here
//
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Purpose: Constructor, destuctor
//-----------------------------------------------------------------------------
CConsolePanel::CConsolePanel( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) : 
	BaseClass( pParent, pName ), m_bStatusVersion( bStatusVersion )
{
	SetKeyBoardInputEnabled( true );

	if ( !m_bStatusVersion )
	{
		SetMinimumSize(100,100);
	}

	// create controls
	m_pHistory = new RichText(this, "ConsoleHistory");
	m_pHistory->SetAllowKeyBindingChainToParent( false );
	SETUP_PANEL( m_pHistory );
	m_pHistory->SetVerticalScrollbar( !m_bStatusVersion );
	if ( m_bStatusVersion )
	{
		m_pHistory->SetDrawOffsets( 3, 3 );
	}
	m_pHistory->GotoTextEnd();
	
	m_pSubmit = new Button(this, "ConsoleSubmit", "#Console_Submit");
	m_pSubmit->SetCommand("submit");
	m_pSubmit->SetVisible( !m_bStatusVersion );

	CNonFocusableMenu *pCompletionList = new CNonFocusableMenu( this, "CompletionList" );
	m_pCompletionList = pCompletionList;
	m_pCompletionList->SetVisible(false);

	m_pEntry = new TabCatchingTextEntry(this, "ConsoleEntry", m_pCompletionList->GetVPanel() );
	m_pEntry->AddActionSignalTarget(this);
	m_pEntry->SendNewLine(true);
	pCompletionList->SetFocusPanel( m_pEntry );

	// need to set up default colors, since ApplySchemeSettings won't be called until later
	m_PrintColor = Color(216, 222, 211, 255);
	m_DPrintColor = Color(196, 181, 80, 255);

	m_pEntry->SetTabPosition(1);

	m_bAutoCompleteMode = false;
	m_szPartialText[0] = 0;
	m_szPreviousPartialText[0]=0;

	// Add to global console list
	g_pCVar->InstallConsoleDisplayFunc( this );
}


//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CConsolePanel::~CConsolePanel()
{
	ClearCompletionList();
	m_CommandHistory.Purge();
	g_pCVar->RemoveConsoleDisplayFunc( this );
}


//-----------------------------------------------------------------------------
// Updates the completion list
//-----------------------------------------------------------------------------
void CConsolePanel::OnThink()
{
	BaseClass::OnThink();

	if ( !IsVisible() )
		return;

	if ( !m_pCompletionList->IsVisible() )
		return;

	UpdateCompletionListPosition();
}


//-----------------------------------------------------------------------------
// Purpose: Clears the console
//-----------------------------------------------------------------------------
void CConsolePanel::Clear()
{
	m_pHistory->SetText("");
	m_pHistory->GotoTextEnd();
}


//-----------------------------------------------------------------------------
// Purpose: color text print
//-----------------------------------------------------------------------------
void CConsolePanel::ColorPrint( const Color& clr, const char *msg )
{
	if ( m_bStatusVersion )
	{
		Clear();
	}

	m_pHistory->InsertColorChange( clr );
	m_pHistory->InsertString( msg );
}


//-----------------------------------------------------------------------------
// Purpose: normal text print
//-----------------------------------------------------------------------------
void CConsolePanel::Print(const char *msg)
{
	ColorPrint( m_PrintColor, msg );
}


//-----------------------------------------------------------------------------
// Purpose: debug text print
//-----------------------------------------------------------------------------
void CConsolePanel::DPrint( const char *msg )
{
	ColorPrint( m_DPrintColor, msg );
}


void CConsolePanel::ClearCompletionList()
{
	int c = m_CompletionList.Count();
	int i;
	for ( i = c - 1; i >= 0; i-- )
	{
		delete m_CompletionList[ i ];
	}
	m_CompletionList.Purge();
}


static ConCommand *FindAutoCompleteCommmandFromPartial( const char *partial )
{
	char command[ 256 ];
	Q_strncpy( command, partial, sizeof( command ) );

	char *space = Q_strstr( command, " " );
	if ( space )
	{
		*space = 0;
	}

	ConCommand *cmd = g_pCVar->FindCommand( command );
	if ( !cmd )
		return NULL;

	if ( !cmd->CanAutoComplete() )
		return NULL;

	return cmd;
}


//-----------------------------------------------------------------------------
// Purpose: rebuilds the list of possible completions from the current entered text
//-----------------------------------------------------------------------------
void CConsolePanel::RebuildCompletionList(const char *text)
{
	ClearCompletionList();

	// we need the length of the text for the partial string compares
	int len = Q_strlen(text);
	if ( len < 1 )
	{
		// Fill the completion list with history instead
		for ( int i = 0 ; i < m_CommandHistory.Count(); i++ )
		{
			CHistoryItem *item = &m_CommandHistory[ i ];
			CompletionItem *comp = new CompletionItem();
			m_CompletionList.AddToTail( comp );
			comp->m_bIsCommand = false;
			comp->m_pCommand = NULL;
			comp->m_pText = new CHistoryItem( *item );
		}
		return;
	}

	bool bNormalBuild = true;

	// if there is a space in the text, and the command isn't of the type to know how to autocomplet, then command completion is over
	const char *space = strstr( text, " " );
	if ( space )
	{
		ConCommand *pCommand = FindAutoCompleteCommmandFromPartial( text );
		if ( !pCommand )
			return;

		bNormalBuild = false;

		CUtlVector< CUtlString > commands;
		int count = pCommand->AutoCompleteSuggest( text, commands );
		Assert( count <= COMMAND_COMPLETION_MAXITEMS );
		int i;

		for ( i = 0; i < count; i++ )
		{
			// match found, add to list
			CompletionItem *item = new CompletionItem();
			m_CompletionList.AddToTail( item );
			item->m_bIsCommand = false;
			item->m_pCommand = NULL;
			item->m_pText = new CHistoryItem( commands[ i ].String() );
		}
	}
				 
	if ( bNormalBuild )
	{
		// look through the command list for all matches
		ConCommandBase const *cmd = (ConCommandBase const *)cvar->GetCommands();
		while (cmd)
		{
			if ( cmd->IsFlagSet( FCVAR_DEVELOPMENTONLY ) || cmd->IsFlagSet( FCVAR_HIDDEN ) )
			{
				cmd = cmd->GetNext();
				continue;
			}

			if ( !strnicmp(text, cmd->GetName(), len))
			{
				// match found, add to list
				CompletionItem *item = new CompletionItem();
				m_CompletionList.AddToTail( item );
				item->m_pCommand = (ConCommandBase *)cmd;
				const char *tst = cmd->GetName();
				if ( !cmd->IsCommand() )
				{
					item->m_bIsCommand = false;
					ConVar *var = ( ConVar * )cmd;
					ConVar_ServerBounded *pBounded = dynamic_cast<ConVar_ServerBounded*>( var );
					if ( pBounded || var->IsFlagSet( FCVAR_NEVER_AS_STRING ) )
					{
						char strValue[512];
						
						int intVal = pBounded ? pBounded->GetInt() : var->GetInt();
						float floatVal = pBounded ? pBounded->GetFloat() : var->GetFloat();
						
						if ( floatVal == intVal )
							Q_snprintf( strValue, sizeof( strValue ), "%d", intVal );
						else
							Q_snprintf( strValue, sizeof( strValue ), "%f", floatVal );

						item->m_pText = new CHistoryItem( var->GetName(), strValue );
					}
					else
					{
						item->m_pText = new CHistoryItem( var->GetName(), var->GetString() );
					}
				}
				else
				{
					item->m_bIsCommand = true;
					item->m_pText = new CHistoryItem( tst );
				}
			}

			cmd = cmd->GetNext();
		}

		// Now sort the list by command name
		if ( m_CompletionList.Count() >= 2 )
		{
			for ( int i = 0 ; i < m_CompletionList.Count(); i++ )
			{
				for ( int j = i + 1; j < m_CompletionList.Count(); j++ )
				{
					const CompletionItem *i1, *i2;
					i1 = m_CompletionList[ i ];
					i2 = m_CompletionList[ j ];

					if ( Q_stricmp( i1->GetName(), i2->GetName() ) > 0 )
					{
						CompletionItem *temp = m_CompletionList[ i ];
						m_CompletionList[ i ] = m_CompletionList[ j ];
						m_CompletionList[ j ] = temp;
					}
				}
			}
		}
	}

}

//-----------------------------------------------------------------------------
// Purpose: auto completes current text
//-----------------------------------------------------------------------------
void CConsolePanel::OnAutoComplete(bool reverse)
{
	if (!m_bAutoCompleteMode)
	{
		// we're not in auto-complete mode, Start
		m_iNextCompletion = 0;
		m_bAutoCompleteMode = true;
	}

	// if we're in reverse, move back to before the current
	if (reverse)
	{
		m_iNextCompletion -= 2;
		if (m_iNextCompletion < 0)
		{
			// loop around in reverse
			m_iNextCompletion = m_CompletionList.Size() - 1;
		}
	}

	// get the next completion
	if (!m_CompletionList.IsValidIndex(m_iNextCompletion))
	{
		// loop completion list
		m_iNextCompletion = 0;
	}

	// make sure everything is still valid
	if (!m_CompletionList.IsValidIndex(m_iNextCompletion))
		return;

	// match found, set text
	char completedText[256];
	CompletionItem *item = m_CompletionList[m_iNextCompletion];
	Assert( item );

	if ( !item->m_bIsCommand && item->m_pCommand )
	{
		Q_strncpy(completedText, item->GetCommand(), sizeof(completedText) - 2 );
	}
	else
	{
		Q_strncpy(completedText, item->GetItemText(), sizeof(completedText) - 2 );
	}

	if ( !Q_strstr( completedText, " " ) )
	{
		Q_strncat(completedText, " ", sizeof(completedText), COPY_ALL_CHARACTERS );
	}

	m_pEntry->SetText(completedText);
	m_pEntry->GotoTextEnd();
	m_pEntry->SelectNone();

	m_iNextCompletion++;
}


//-----------------------------------------------------------------------------
// Purpose: Called whenever the user types text
//-----------------------------------------------------------------------------
void CConsolePanel::OnTextChanged(Panel *panel)
{
	if (panel != m_pEntry)
		return;

	Q_strncpy( m_szPreviousPartialText, m_szPartialText, sizeof( m_szPreviousPartialText ) );

	// get the partial text the user type
	m_pEntry->GetText(m_szPartialText, sizeof(m_szPartialText));

	// see if they've hit the tilde key (which opens & closes the console)
	int len = Q_strlen(m_szPartialText);

	bool hitTilde = ( m_szPartialText[len - 1] == '~' || m_szPartialText[len - 1] == '`' ) ? true : false;

	bool altKeyDown = ( vgui::input()->IsKeyDown( KEY_LALT ) || vgui::input()->IsKeyDown( KEY_RALT ) ) ? true : false;
	bool ctrlKeyDown = ( vgui::input()->IsKeyDown( KEY_LCONTROL ) || vgui::input()->IsKeyDown( KEY_RCONTROL ) ) ? true : false;

	// Alt-Tilde toggles Japanese IME on/off!!!
	if ( ( len > 0 ) && hitTilde )
	{
		// Strip the last character (tilde)
		m_szPartialText[ len - 1 ] = L'\0';

		if( !altKeyDown && !ctrlKeyDown )
		{
			m_pEntry->SetText( "" );

			// close the console
			PostMessage( this, new KeyValues( "Close" ) );
			PostActionSignal( new KeyValues( "ClosedByHittingTilde" ) );
		}
		else
		{
			m_pEntry->SetText( m_szPartialText );
		}
		return;
	}

	// clear auto-complete state since the user has typed
	m_bAutoCompleteMode = false;

	RebuildCompletionList(m_szPartialText);

	// build the menu
	if ( m_CompletionList.Count() < 1 )
	{
		m_pCompletionList->SetVisible(false);
	}
	else
	{
		m_pCompletionList->SetVisible(true);
		m_pCompletionList->DeleteAllItems();
		const int MAX_MENU_ITEMS = 10;

		// add the first ten items to the list
		for (int i = 0; i < m_CompletionList.Count() && i < MAX_MENU_ITEMS; i++)
		{
			char text[256];
			text[0] = 0;
			if (i == MAX_MENU_ITEMS - 1)
			{
				Q_strncpy(text, "...", sizeof( text ) );
			}
			else
			{
				Assert( m_CompletionList[i] );
				Q_strncpy(text, m_CompletionList[i]->GetItemText(), sizeof( text ) );
			}
			KeyValues *kv = new KeyValues("CompletionCommand");
			kv->SetString("command",text);
			m_pCompletionList->AddMenuItem(text, kv, this);
		}

		UpdateCompletionListPosition();
	}
	
	RequestFocus();
	m_pEntry->RequestFocus();

}

//-----------------------------------------------------------------------------
// Purpose: generic vgui command handler
//-----------------------------------------------------------------------------
void CConsolePanel::OnCommand(const char *command)
{
	if ( !Q_stricmp( command, "Submit" ) )
	{
		// submit the entry as a console commmand
		char szCommand[256];
		m_pEntry->GetText(szCommand, sizeof(szCommand));
		PostActionSignal( new KeyValues( "CommandSubmitted", "command", szCommand ) );

		// add to the history
		Print("] ");
		Print(szCommand);
		Print("\n");

		// clear the field
		m_pEntry->SetText("");

		// clear the completion state
		OnTextChanged(m_pEntry);

		// always go the end of the buffer when the user has typed something
		m_pHistory->GotoTextEnd();

		// Add the command to the history
		char *extra = strchr(szCommand, ' ');
		if ( extra )
		{
			*extra = '\0';
			extra++;
		}

		if ( Q_strlen( szCommand ) > 0 )
		{
			AddToHistory( szCommand, extra );
		}
		m_pCompletionList->SetVisible(false);
	}
	else
	{
		BaseClass::OnCommand(command);
	}
}


//-----------------------------------------------------------------------------
// Focus related methods
//-----------------------------------------------------------------------------
bool CConsolePanel::TextEntryHasFocus() const
{
	return ( input()->GetFocus() == m_pEntry->GetVPanel() );
}

void CConsolePanel::TextEntryRequestFocus()
{
	m_pEntry->RequestFocus();
}


//-----------------------------------------------------------------------------
// Purpose: swallows tab key pressed
//-----------------------------------------------------------------------------
void CConsolePanel::OnKeyCodeTyped(KeyCode code)
{
	BaseClass::OnKeyCodeTyped(code);

	// check for processing
	if ( TextEntryHasFocus() )
	{
		if (code == KEY_TAB)
		{
			bool reverse = false;
			if (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT))
			{
				reverse = true;
			}

			// attempt auto-completion
			OnAutoComplete(reverse);
			m_pEntry->RequestFocus();
		}
		else if (code == KEY_DOWN)
		{
			OnAutoComplete(false);
		//	UpdateCompletionListPosition();
		//	m_pCompletionList->SetVisible(true);

			m_pEntry->RequestFocus();
		}
		else if (code == KEY_UP)
		{
			OnAutoComplete(true);
			m_pEntry->RequestFocus();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: lays out controls
//-----------------------------------------------------------------------------
void CConsolePanel::PerformLayout()
{
	BaseClass::PerformLayout();

	// setup tab ordering
	GetFocusNavGroup().SetDefaultButton(m_pSubmit);

	IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
	m_pEntry->SetBorder(pScheme->GetBorder("DepressedButtonBorder"));
	m_pHistory->SetBorder(pScheme->GetBorder("DepressedButtonBorder"));

	// layout controls
	int wide, tall;
	GetSize(wide, tall);

	if ( !m_bStatusVersion )
	{
		const int inset = 8;
		const int entryHeight = 24;
		const int topHeight = 4;
		const int entryInset = 4;
		const int submitWide = 64;
		const int submitInset = 7; // x inset to pull the submit button away from the frame grab

		m_pHistory->SetPos(inset, inset + topHeight); 
		m_pHistory->SetSize(wide - (inset * 2), tall - (entryInset * 2 + inset * 2 + topHeight + entryHeight));
		m_pHistory->InvalidateLayout();

		int nSubmitXPos = wide - ( inset + submitWide + submitInset );
		m_pSubmit->SetPos( nSubmitXPos, tall - (entryInset * 2 + entryHeight));
		m_pSubmit->SetSize( submitWide, entryHeight);
		 
		m_pEntry->SetPos( inset, tall - (entryInset * 2 + entryHeight) );
		m_pEntry->SetSize( nSubmitXPos - entryInset - 2 * inset, entryHeight);
	}
	else
	{
		const int inset = 2;

		int entryWidth = wide / 2;
		if ( wide > 400 )
		{
			entryWidth = 200;
		}

		m_pEntry->SetBounds( inset, inset, entryWidth, tall - 2 * inset );

		m_pHistory->SetBounds( inset + entryWidth + inset, inset, ( wide - entryWidth ) - inset, tall - 2 * inset );
	}

	UpdateCompletionListPosition();
}

//-----------------------------------------------------------------------------
// Purpose: Sets the position of the completion list popup
//-----------------------------------------------------------------------------
void CConsolePanel::UpdateCompletionListPosition()
{
	int ex, ey;
	m_pEntry->GetPos(ex, ey);

	if ( !m_bStatusVersion )
	{
		// Position below text entry
		ey += m_pEntry->GetTall();
	}
	else
	{
		// Position above text entry
		int menuwide, menutall;
		m_pCompletionList->GetSize( menuwide, menutall );
		ey -= ( menutall + 4 );
	}

	LocalToScreen( ex, ey );
	m_pCompletionList->SetPos( ex, ey );

	if ( m_pCompletionList->IsVisible() )
	{
		m_pEntry->RequestFocus();
		MoveToFront();
		m_pCompletionList->MoveToFront();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Closes the completion list
//-----------------------------------------------------------------------------
void CConsolePanel::CloseCompletionList()
{
	m_pCompletionList->SetVisible(false);
}

//-----------------------------------------------------------------------------
// Purpose: sets up colors
//-----------------------------------------------------------------------------
void CConsolePanel::ApplySchemeSettings(IScheme *pScheme)
{
	BaseClass::ApplySchemeSettings(pScheme);

	m_PrintColor = GetSchemeColor("Console.TextColor", pScheme);
	m_DPrintColor = GetSchemeColor("Console.DevTextColor", pScheme);
	m_pHistory->SetFont( pScheme->GetFont( "ConsoleText", IsProportional() ) );
	m_pCompletionList->SetFont( pScheme->GetFont( "DefaultSmall", IsProportional() ) );
	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: Handles autocompletion menu input
//-----------------------------------------------------------------------------
void CConsolePanel::OnMenuItemSelected(const char *command)
{
	if ( strstr( command, "..." ) ) // stop the menu going away if you click on ...
	{
		m_pCompletionList->SetVisible( true );
	}
	else
	{
		m_pEntry->SetText(command);
		m_pEntry->GotoTextEnd();
		m_pEntry->InsertChar(' ');
		m_pEntry->GotoTextEnd();
	}
}

void CConsolePanel::Hide()
{
	OnClose();
	m_iNextCompletion = 0;
	RebuildCompletionList("");
}

void CConsolePanel::AddToHistory( const char *commandText, const char *extraText )
{
	// Newest at end, oldest at head
	while ( m_CommandHistory.Count() >= MAX_HISTORY_ITEMS )
	{
		// Remove from head until size is reasonable
		m_CommandHistory.Remove( 0 );
	}

	// strip the space off the end of the command before adding it to the history
	// If this code gets cleaned up then we should remove the redundant calls to strlen,
	// the check for whether _alloca succeeded, and should use V_strncpy instead of the
	// error prone memset/strncpy sequence.
	char *command = static_cast<char *>( _alloca( (strlen( commandText ) + 1 ) * sizeof( char ) ));
	if ( command )
	{
		memset( command, 0x0, strlen( commandText ) + 1 );
		strncpy( command, commandText, strlen( commandText ));
		// There is no actual bug here, just some sloppy/odd code.
		// src\vgui2\vgui_controls\consoledialog.cpp(974): warning C6053: The prior call to 'strncpy' might not zero-terminate string 'command'
		ANALYZE_SUPPRESS( 6053 )
		if ( command[ strlen( command ) -1 ] == ' ' )
		{
			 command[ strlen( command ) -1 ] = '\0';
		}
	}

	// strip the quotes off the extra text
	char *extra = NULL;

	if ( extraText )
	{
		extra = static_cast<char *>( malloc( (strlen( extraText ) + 1 ) * sizeof( char ) ));
		if ( extra )
		{
			memset( extra, 0x0, strlen( extraText ) + 1 );
			strncpy( extra, extraText, strlen( extraText )); // +1 to dodge the starting quote
			
			// Strip trailing spaces
			int i = strlen( extra ) - 1; 
			while ( i >= 0 &&  // Check I before referencing i == -1 into the extra array!
				extra[ i ] == ' ' )
			{
				extra[ i ] = '\0';
				i--;
			}
		}
	}

	// If it's already there, then remove since we'll add it to the end instead
	CHistoryItem *item = NULL;
	for ( int i = m_CommandHistory.Count() - 1; i >= 0; i-- )
	{
		item = &m_CommandHistory[ i ];
		if ( !item )
			continue;

		if ( stricmp( item->GetText(), command ) )
			continue;

		if ( extra || item->GetExtra() )
		{
			if ( !extra || !item->GetExtra() )
				continue;

			// stricmp so two commands with the same starting text get added
			if ( stricmp( item->GetExtra(), extra ) )	
				continue;
		}
		m_CommandHistory.Remove( i );
	}

	item = &m_CommandHistory[ m_CommandHistory.AddToTail() ];
	Assert( item );
	item->SetText( command, extra );

	m_iNextCompletion = 0;
	RebuildCompletionList( m_szPartialText );

	free( extra );
}

void CConsolePanel::GetConsoleText( char *pchText, size_t bufSize ) const
{
	wchar_t *temp = new wchar_t[ bufSize ];
	m_pHistory->GetText( 0, temp, bufSize * sizeof( wchar_t ) );
	g_pVGuiLocalize->ConvertUnicodeToANSI( temp, pchText, bufSize );
	delete[] temp;
}

//-----------------------------------------------------------------------------
// Purpose: writes out console to disk
//-----------------------------------------------------------------------------
void CConsolePanel::DumpConsoleTextToFile()
{
	const int CONDUMP_FILES_MAX_NUM = 1000;

	FileHandle_t handle;
	bool found = false;
	char szfile[ 512 ];

	// we don't want to overwrite other condump.txt files
	for ( int i = 0 ; i < CONDUMP_FILES_MAX_NUM ; ++i )
	{
		_snprintf( szfile, sizeof(szfile), "condump%03d.txt", i );
		if ( !g_pFullFileSystem->FileExists(szfile) )
		{
			found = true;
			break;
		}
	}

	if ( !found )
	{
		Print( "Can't condump! Too many existing condump output files in the gamedir!\n" );
		return;
	}

	handle = g_pFullFileSystem->Open( szfile, "wb" );
	if ( handle != FILESYSTEM_INVALID_HANDLE )
	{
		int pos = 0;
		while (1)
		{
			wchar_t buf[512];
			m_pHistory->GetText(pos, buf, sizeof(buf));
			pos += sizeof(buf) / sizeof(wchar_t);

			// don't continue if none left
			if (buf[0] == 0)
				break;

			// convert to ansi
			char ansi[512];
			g_pVGuiLocalize->ConvertUnicodeToANSI(buf, ansi, sizeof(ansi));

			// write to disk
			int len = strlen(ansi);
			for (int i = 0; i < len; i++)
			{
				// preceed newlines with a return
				if (ansi[i] == '\n')
				{
					char ret = '\r';
					g_pFullFileSystem->Write( &ret, 1, handle );
				}

				g_pFullFileSystem->Write( ansi + i, 1, handle );
			}
		}

		g_pFullFileSystem->Close( handle );

		Print( "console dumped to " );
		Print( szfile );
		Print( "\n" );
	}
	else
	{
		Print( "Unable to condump to " );
		Print( szfile );
		Print( "\n" );
	}
}


//-----------------------------------------------------------------------------
//
// Console dialog starts here
//
//-----------------------------------------------------------------------------
CConsoleDialog::CConsoleDialog( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) : 
	BaseClass( pParent, pName )
{
	// initialize dialog
	SetVisible( false );
	SetTitle( "#Console_Title", true );
	m_pConsolePanel = new CConsolePanel( this, "ConsolePage", bStatusVersion );
	m_pConsolePanel->AddActionSignalTarget( this );
}

void CConsoleDialog::OnScreenSizeChanged( int iOldWide, int iOldTall )
{
	BaseClass::OnScreenSizeChanged( iOldWide, iOldTall );

	int sx, sy;
	surface()->GetScreenSize( sx, sy );
									 
	int w, h;
	GetSize( w, h );
	if ( w > sx || h > sy  )
	{
		if ( w > sx )
		{
			w = sx;
		}
		if ( h > sy )
		{
			h = sy;
		}

		// Try and lower the size to match the screen bounds
		SetSize( w, h );
	}
}


//-----------------------------------------------------------------------------
// Purpose: brings dialog to the fore
//-----------------------------------------------------------------------------
void CConsoleDialog::PerformLayout()
{
	BaseClass::PerformLayout();

	int x, y, w, h;
	GetClientArea( x, y, w, h );
	m_pConsolePanel->SetBounds( x, y, w, h );
}


//-----------------------------------------------------------------------------
// Purpose: brings dialog to the fore
//-----------------------------------------------------------------------------
void CConsoleDialog::Activate()
{
	BaseClass::Activate();
	m_pConsolePanel->m_pEntry->RequestFocus();
}


//-----------------------------------------------------------------------------
// Hides the dialog
//-----------------------------------------------------------------------------
void CConsoleDialog::Hide()
{
	OnClose();
	m_pConsolePanel->Hide();
}


//-----------------------------------------------------------------------------
// Close just hides the dialog
//-----------------------------------------------------------------------------
void CConsoleDialog::Close()
{
	Hide();
}


//-----------------------------------------------------------------------------
// Submits commands
//-----------------------------------------------------------------------------
void CConsoleDialog::OnCommandSubmitted( const char *pCommand )
{
	PostActionSignal( new KeyValues( "CommandSubmitted", "command", pCommand ) );
}


//-----------------------------------------------------------------------------
// Chain to the page
//-----------------------------------------------------------------------------
void CConsoleDialog::Print( const char *pMessage )
{
	m_pConsolePanel->Print( pMessage );
}

void CConsoleDialog::DPrint( const char *pMessage )
{
	m_pConsolePanel->DPrint( pMessage );
}

void CConsoleDialog::ColorPrint( const Color& clr, const char *msg )
{
	m_pConsolePanel->ColorPrint( clr, msg );
}

void CConsoleDialog::Clear()
{
	m_pConsolePanel->Clear( );
}

void CConsoleDialog::DumpConsoleTextToFile()
{
	m_pConsolePanel->DumpConsoleTextToFile( );
}


void CConsoleDialog::OnKeyCodePressed( vgui::KeyCode code )
{
	if ( code == KEY_XBUTTON_B )
	{
		Hide();
	}
	else
	{
		BaseClass::OnKeyCodePressed(code);
	}
}