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

#include "BasePanel.h"
#include "NewGameDialog.h"
#include "EngineInterface.h"
#include "vgui_controls/Button.h"
#include "vgui_controls/CheckButton.h"
#include "KeyValues.h"
#include "vgui/ISurface.h"
#include "vgui/IInput.h"
#include "vgui/ILocalize.h"
#include <vgui/ISystem.h>
#include "vgui_controls/RadioButton.h"
#include "vgui_controls/ComboBox.h"
#include "vgui_controls/ImagePanel.h"
#include "vgui_controls/Frame.h"
#include "vgui_controls/ControllerMap.h"
#include "filesystem.h"
#include "ModInfo.h"
#include "tier1/convar.h"
#include "GameUI_Interface.h"
#include "tier0/icommandline.h"
#include "vgui_controls/AnimationController.h"
#include "CommentaryExplanationDialog.h"
#include "vgui_controls/BitmapImagePanel.h"
#include "BonusMapsDatabase.h"

#include <stdio.h>

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

using namespace vgui;

static float	g_ScrollSpeedSlow;
static float	g_ScrollSpeedFast;

// sort function used in displaying chapter list
struct chapter_t
{
	char filename[32];
};
static int __cdecl ChapterSortFunc(const void *elem1, const void *elem2)
{
	chapter_t *c1 = (chapter_t *)elem1;
	chapter_t *c2 = (chapter_t *)elem2;

	// compare chapter number first
	static int chapterlen = strlen("chapter");
	if (atoi(c1->filename + chapterlen) > atoi(c2->filename + chapterlen))
		return 1;
	else if (atoi(c1->filename + chapterlen) < atoi(c2->filename + chapterlen))
		return -1;

	// compare length second (longer string show up later in the list, eg. chapter9 before chapter9a)
	if (strlen(c1->filename) > strlen(c2->filename))
		return 1;
	else if (strlen(c1->filename) < strlen(c2->filename))
		return -1;

	// compare strings third
	return strcmp(c1->filename, c2->filename);
}

//-----------------------------------------------------------------------------
// Purpose: invisible panel used for selecting a chapter panel
//-----------------------------------------------------------------------------
class CSelectionOverlayPanel : public vgui::Panel
{
	DECLARE_CLASS_SIMPLE( CSelectionOverlayPanel, Panel );
	int m_iChapterIndex;
	CNewGameDialog *m_pSelectionTarget;
public:
	CSelectionOverlayPanel( Panel *parent, CNewGameDialog *selectionTarget, int chapterIndex ) : BaseClass( parent, NULL )
	{
		m_iChapterIndex = chapterIndex;
		m_pSelectionTarget = selectionTarget;
		SetPaintEnabled(false);
		SetPaintBackgroundEnabled(false);
	}

	virtual void OnMousePressed( vgui::MouseCode code )
	{
		if (GetParent()->IsEnabled())
		{
			m_pSelectionTarget->SetSelectedChapterIndex( m_iChapterIndex );
		}
	}

	virtual void OnMouseDoublePressed( vgui::MouseCode code )
	{
		// call the panel
		OnMousePressed( code );
		if (GetParent()->IsEnabled())
		{
			PostMessage( m_pSelectionTarget, new KeyValues("Command", "command", "play") );
		}
	}
};

//-----------------------------------------------------------------------------
// Purpose: selectable item with screenshot for an individual chapter in the dialog
//-----------------------------------------------------------------------------
class CGameChapterPanel : public vgui::EditablePanel
{
	DECLARE_CLASS_SIMPLE( CGameChapterPanel, vgui::EditablePanel );

	ImagePanel *m_pLevelPicBorder;
	ImagePanel *m_pLevelPic;
	ImagePanel *m_pCommentaryIcon;
	Label *m_pChapterLabel;
	Label *m_pChapterNameLabel;

	Color m_TextColor;
	Color m_DisabledColor;
	Color m_SelectedColor;
	Color m_FillColor;

	char m_szConfigFile[_MAX_PATH];
	char m_szChapter[32];

	bool m_bTeaserChapter;
	bool m_bHasBonus;
	bool m_bCommentaryMode;

	bool m_bIsSelected;

public:
	CGameChapterPanel( CNewGameDialog *parent, const char *name, const char *chapterName, int chapterIndex, const char *chapterNumber, const char *chapterConfigFile, bool bCommentary ) : BaseClass( parent, name )
	{
		Q_strncpy( m_szConfigFile, chapterConfigFile, sizeof(m_szConfigFile) );
		Q_strncpy( m_szChapter, chapterNumber, sizeof(m_szChapter) );

		m_pLevelPicBorder = SETUP_PANEL( new ImagePanel( this, "LevelPicBorder" ) );
		m_pLevelPic = SETUP_PANEL( new ImagePanel( this, "LevelPic" ) );
		m_pCommentaryIcon = NULL;
		m_bCommentaryMode = bCommentary;
		m_bIsSelected = false;

		wchar_t text[32];
		wchar_t num[32];
		wchar_t *chapter = g_pVGuiLocalize->Find("#GameUI_Chapter");
		g_pVGuiLocalize->ConvertANSIToUnicode( chapterNumber, num, sizeof(num) );
		_snwprintf( text, ARRAYSIZE(text), L"%ls %ls", chapter ? chapter : L"CHAPTER", num );

		if ( ModInfo().IsSinglePlayerOnly() )
		{
			m_pChapterLabel = new Label( this, "ChapterLabel", text );
			m_pChapterNameLabel = new Label( this, "ChapterNameLabel", chapterName );
		}
		else
		{
			m_pChapterLabel = new Label( this, "ChapterLabel", chapterName );
			m_pChapterNameLabel = new Label( this, "ChapterNameLabel", "#GameUI_LoadCommentary" );
		}

		SetPaintBackgroundEnabled( false );

		KeyValues *pKeys = NULL;
		if ( GameUI().IsConsoleUI() )
		{
			pKeys = BasePanel()->GetConsoleControlSettings()->FindKey( "NewGameChapterPanel.res" );
		}
		LoadControlSettings( "Resource/NewGameChapterPanel.res", NULL, pKeys );

		// the image has the same name as the config file
		char szMaterial[MAX_PATH];
		Q_snprintf(szMaterial, sizeof(szMaterial), "chapters/%s", chapterConfigFile);
		char* ext = strstr(szMaterial, ".");
		if (ext)
		{
			*ext = 0;
		}
		m_pLevelPic->SetImage(szMaterial);

		int px, py;
		m_pLevelPicBorder->GetPos( px, py );
		SetSize( m_pLevelPicBorder->GetWide(), py + m_pLevelPicBorder->GetTall() );

		// create a selection panel the size of the page
		CSelectionOverlayPanel *overlay = new CSelectionOverlayPanel( this, parent, chapterIndex );
		overlay->SetBounds(0, 0, GetWide(), GetTall());
		overlay->MoveToFront();

		// HACK: Detect new episode teasers by the "Coming Soon" text
		wchar_t w_szStrTemp[256];
		m_pChapterNameLabel->GetText( w_szStrTemp, sizeof(w_szStrTemp)  );
		m_bTeaserChapter = !wcscmp(w_szStrTemp, L"Coming Soon");
		m_bHasBonus = false;
	}

	virtual void ApplySchemeSettings( IScheme *pScheme )
	{
		m_TextColor = pScheme->GetColor( "NewGame.TextColor", Color(255, 255, 255, 255) );
		m_FillColor = pScheme->GetColor( "NewGame.FillColor", Color(255, 255, 255, 255) );
		m_DisabledColor = pScheme->GetColor( "NewGame.DisabledColor", Color(255, 255, 255, 255) );
		m_SelectedColor = pScheme->GetColor( "NewGame.SelectionColor", Color(255, 255, 255, 255) );

		BaseClass::ApplySchemeSettings( pScheme );

		// Hide chapter numbers for new episode teasers
		if ( m_bTeaserChapter )
		{
			m_pChapterLabel->SetVisible( false );
		}
		if ( GameUI().IsConsoleUI() )
		{
			m_pChapterNameLabel->SetVisible( false );
		}

		m_pCommentaryIcon = dynamic_cast<ImagePanel*>( FindChildByName( "CommentaryIcon" ) );
		if ( m_pCommentaryIcon )
			m_pCommentaryIcon->SetVisible( m_bCommentaryMode );
	}

	bool IsSelected( void ) const { return m_bIsSelected; }

	void SetSelected( bool state )
	{
		m_bIsSelected = state;

		// update the text/border colors
		if ( !IsEnabled() )
		{
			m_pChapterLabel->SetFgColor( m_DisabledColor );
			m_pChapterNameLabel->SetFgColor( Color(0, 0, 0, 0) );
			m_pLevelPicBorder->SetFillColor( m_DisabledColor );
			m_pLevelPic->SetAlpha( GameUI().IsConsoleUI() ? 64 : 128 );
			return;
		}

		if ( state )
		{
			if ( !GameUI().IsConsoleUI() )
			{
				m_pChapterLabel->SetFgColor( m_SelectedColor );
				m_pChapterNameLabel->SetFgColor( m_SelectedColor );
			}
			m_pLevelPicBorder->SetFillColor( m_SelectedColor );
		}
		else
		{
			m_pChapterLabel->SetFgColor( m_TextColor );
			m_pChapterNameLabel->SetFgColor( m_TextColor );
			m_pLevelPicBorder->SetFillColor( m_FillColor );
		}
		m_pLevelPic->SetAlpha( 255 );
	}

	const char *GetConfigFile()
	{
		return m_szConfigFile;
	}

	const char *GetChapter()
	{
		return m_szChapter;
	}

	bool IsTeaserChapter()
	{
		return m_bTeaserChapter;
	}

	bool HasBonus()
	{
		return m_bHasBonus;
	}

	void SetCommentaryMode( bool bCommentaryMode )
	{
		m_bCommentaryMode = bCommentaryMode;
		if ( m_pCommentaryIcon )
			m_pCommentaryIcon->SetVisible( m_bCommentaryMode );
	}
};

const char *COM_GetModDirectory()
{
	static char modDir[MAX_PATH];
	if ( Q_strlen( modDir ) == 0 )
	{
		const char *gamedir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) );
		Q_strncpy( modDir, gamedir, sizeof(modDir) );
		if ( strchr( modDir, '/' ) || strchr( modDir, '\\' ) )
		{
			Q_StripLastDir( modDir, sizeof(modDir) );
			int dirlen = Q_strlen( modDir );
			Q_strncpy( modDir, gamedir + dirlen, sizeof(modDir) - dirlen );
		}
	}

	return modDir;
}

//-----------------------------------------------------------------------------
// Purpose: new game chapter selection
//-----------------------------------------------------------------------------
CNewGameDialog::CNewGameDialog(vgui::Panel *parent, bool bCommentaryMode) : BaseClass(parent, "NewGameDialog")
{
	SetDeleteSelfOnClose(true);
	SetBounds(0, 0, 372, 160);
	SetSizeable( false );
	m_iSelectedChapter = -1;
	m_ActiveTitleIdx = 0;

	m_bCommentaryMode = bCommentaryMode;
	m_bMapStarting = false;
	m_bScrolling = false;
	m_ScrollCt = 0;
	m_ScrollSpeed = 0.f;
	m_ButtonPressed = SCROLL_NONE;
	m_ScrollDirection = SCROLL_NONE;
	m_pCommentaryLabel = NULL;

	m_iBonusSelection = 0;
	m_bScrollToFirstBonusMap = false;

	SetTitle("#GameUI_NewGame", true);

	m_pNextButton = new Button( this, "Next", "#gameui_next" );
	m_pPrevButton = new Button( this, "Prev", "#gameui_prev" );
	m_pPlayButton = new CNewGamePlayButton( this, "Play", "#GameUI_Play" );
	m_pPlayButton->SetCommand( "Play" );

	vgui::Button *cancel = new vgui::Button( this, "Cancel", "#GameUI_Cancel" );
	cancel->SetCommand( "Close" );

	m_pCenterBg = SETUP_PANEL( new Panel( this, "CenterBG" ) );
	m_pCenterBg->SetVisible( false );

	if ( GameUI().IsConsoleUI() )
	{
		m_pNextButton->SetVisible( false );
		m_pPrevButton->SetVisible( false );
		m_pPlayButton->SetVisible( false );
		cancel->SetVisible( false );

		m_pCenterBg->SetPaintBackgroundType( 2 );
		m_pCenterBg->SetVisible( true );

		m_pChapterTitleLabels[0] = SETUP_PANEL( new Label( this, "ChapterTitleLabel", "" ) );
		m_pChapterTitleLabels[0]->SetVisible( true );
		m_pChapterTitleLabels[0]->SetFgColor( Color( 255, 255, 255, 255 ) );

		m_pChapterTitleLabels[1] = SETUP_PANEL( new Label( this, "ChapterTitleLabel2", "" ) );
		m_pChapterTitleLabels[1]->SetVisible( true );
		m_pChapterTitleLabels[1]->SetAlpha( 0 );
		m_pChapterTitleLabels[1]->SetFgColor( Color( 255, 255, 255, 255 ) );

		m_pBonusSelection = SETUP_PANEL( new Label( this, "BonusSelectionLabel", "#GameUI_BonusMapsStandard" ) );
		m_pBonusSelectionBorder = SETUP_PANEL( new ImagePanel( this, "BonusSelectionBorder" ) );

		m_pFooter = new CFooterPanel( parent, "NewGameFooter" );
		m_pFooter->AddNewButtonLabel( "#GameUI_Play", "#GameUI_Icons_A_BUTTON" );
		m_pFooter->AddNewButtonLabel( "#GameUI_Close", "#GameUI_Icons_B_BUTTON" );
	}
	else
	{
		m_pFooter = NULL;
	}

	// parse out the chapters off disk
	static const int MAX_CHAPTERS = 32;
	chapter_t chapters[MAX_CHAPTERS];

	char szFullFileName[MAX_PATH];
	int chapterIndex = 0;

	if ( IsPC() || !IsX360() )
	{
		FileFindHandle_t findHandle = FILESYSTEM_INVALID_FIND_HANDLE;
		const char *fileName = "cfg/chapter*.cfg";
		fileName = g_pFullFileSystem->FindFirst( fileName, &findHandle );
		while ( fileName && chapterIndex < MAX_CHAPTERS )
		{
			if ( fileName[0] )
			{
				// Only load chapter configs from the current mod's cfg dir
				// or else chapters appear that we don't want!
				Q_snprintf( szFullFileName, sizeof(szFullFileName), "cfg/%s", fileName );
				FileHandle_t f = g_pFullFileSystem->Open( szFullFileName, "rb", "MOD" );
				if ( f )
				{	
					// don't load chapter files that are empty, used in the demo
					if ( g_pFullFileSystem->Size(f) > 0	)
					{
						Q_strncpy(chapters[chapterIndex].filename, fileName, sizeof(chapters[chapterIndex].filename));
						++chapterIndex;
					}
					g_pFullFileSystem->Close( f );
				}
			}
			fileName = g_pFullFileSystem->FindNext(findHandle);
		}
	}
	else if ( IsX360() )
	{
		int ChapterStringIndex = 0;
		bool bExists = true;
		while ( bExists && chapterIndex < MAX_CHAPTERS )
		{
			Q_snprintf( szFullFileName, sizeof( szFullFileName ), "cfg/chapter%d.cfg", ChapterStringIndex+1 );

			FileHandle_t f = g_pFullFileSystem->Open( szFullFileName, "rb", "MOD" );
			if ( f )
			{		
				Q_strncpy(chapters[chapterIndex].filename, szFullFileName + 4, sizeof(chapters[chapterIndex].filename));
				++chapterIndex;
				++ChapterStringIndex;
				g_pFullFileSystem->Close( f );
			}
			else
			{
				bExists = false;
			}	
			//Hack to account for xbox360 missing chapter9a
			if ( ChapterStringIndex == 10 )
			{				
				Q_snprintf( szFullFileName, sizeof( szFullFileName ), "cfg/chapter9a.cfg" );
				FileHandle_t fChap = g_pFullFileSystem->Open( szFullFileName, "rb", "MOD" );
				if ( fChap )
				{		
					Q_strncpy(chapters[chapterIndex].filename, szFullFileName + 4, sizeof(chapters[chapterIndex].filename));
					++chapterIndex;
					g_pFullFileSystem->Close( fChap );
				}		
			}

		}
		
	}

	bool bBonusesUnlocked = false;

	if ( GameUI().IsConsoleUI() )
	{
		if ( !m_bCommentaryMode )
		{
			// Scan to see if the bonus maps have been unlocked
			bBonusesUnlocked = BonusMapsDatabase()->BonusesUnlocked();
		}
	}

	// sort the chapters
	qsort(chapters, chapterIndex, sizeof(chapter_t), &ChapterSortFunc);

	// work out which chapters are unlocked
	ConVarRef var( "sv_unlockedchapters" );

	if ( bBonusesUnlocked )
	{
		// Bonuses are unlocked so we need to unlock all the chapters too
		var.SetValue( 15 );
	}

	const char *unlockedChapter = var.IsValid() ? var.GetString() : "1";
	int iUnlockedChapter = atoi(unlockedChapter);

	// add chapters to combobox
	for (int i = 0; i < chapterIndex; i++)
	{
		const char *fileName = chapters[i].filename;
		char chapterID[32] = { 0 };
		sscanf(fileName, "chapter%s", chapterID);
		// strip the extension
		char *ext = V_stristr(chapterID, ".cfg");
		if (ext)
		{
			*ext = 0;
		}

		const char *pGameDir = COM_GetModDirectory();

		char chapterName[64];
		Q_snprintf(chapterName, sizeof(chapterName), "#%s_Chapter%s_Title", pGameDir, chapterID);

		Q_snprintf( szFullFileName, sizeof( szFullFileName ), "%s", fileName );
		CGameChapterPanel *chapterPanel = SETUP_PANEL( new CGameChapterPanel( this, NULL, chapterName, i, chapterID, szFullFileName, m_bCommentaryMode ) );
		chapterPanel->SetVisible( false );

		UpdatePanelLockedStatus( iUnlockedChapter, i + 1, chapterPanel );

		if ( GameUI().IsConsoleUI() )
		{
			if ( bBonusesUnlocked )
			{
				// check to see if it has associated challenges
				for ( int iBonusMap = 0; iBonusMap < BonusMapsDatabase()->BonusCount(); ++iBonusMap )
				{
					BonusMapDescription_t *pMap = BonusMapsDatabase()->GetBonusData( iBonusMap );
					if ( Q_stricmp( pMap->szChapterName, szFullFileName ) == 0 && !pMap->bLocked )
					{
						chapterPanel->m_bHasBonus = true;
						chapterPanel->SetControlVisible( "HasBonusLabel", true );
					}
				}
			}
		}

		m_ChapterPanels.AddToTail( chapterPanel );
	}

	KeyValues *pKeys = NULL;
	if ( GameUI().IsConsoleUI() )
	{
		 pKeys = BasePanel()->GetConsoleControlSettings()->FindKey( "NewGameDialog.res" );
	}
	LoadControlSettings( "Resource/NewGameDialog.res", NULL, pKeys );

	// Reset all properties
	for ( int i = 0; i < NUM_SLOTS; ++i )
	{
		m_PanelIndex[i] = INVALID_INDEX;
	}

	if ( !m_ChapterPanels.Count() )
	{
		UpdateMenuComponents( SCROLL_NONE );
		return;
	}

	int indent = 8;
	if ( IsProportional() )
	{
		indent = scheme()->GetProportionalScaledValueEx( GetScheme(), indent );
	}

	int wide = 16;
	if ( IsProportional() )
	{
		wide = scheme()->GetProportionalScaledValueEx( GetScheme(), wide );
	}

	// Layout panel positions relative to the dialog center.
	int panelWidth = m_ChapterPanels[0]->GetWide() + wide;
	int dialogWidth = GetWide();
	
	m_PanelXPos[2] = ( dialogWidth - panelWidth ) / 2 + indent;
	
	if (m_ChapterPanels.Count() > 1)
	{
		m_PanelXPos[1] = m_PanelXPos[2] - panelWidth;
		m_PanelXPos[0] = m_PanelXPos[1];
		m_PanelXPos[3] = m_PanelXPos[2] + panelWidth;
		m_PanelXPos[4] = m_PanelXPos[3];
	}
	else
	{
		m_PanelXPos[0] = m_PanelXPos[1] = m_PanelXPos[3] =
		m_PanelXPos[4] = m_PanelXPos[2];
	}


	m_PanelAlpha[0] = 0;
	m_PanelAlpha[1] = 255;
	m_PanelAlpha[2] = 255;
	m_PanelAlpha[3] = 255;
	m_PanelAlpha[4] = 0;

	int panelHeight;
	m_ChapterPanels[0]->GetSize( panelWidth, panelHeight );
	m_pCenterBg->SetWide( panelWidth + wide);
	m_pCenterBg->SetPos( m_PanelXPos[2] - indent, m_PanelYPos[2] - (m_pCenterBg->GetTall() - panelHeight) + indent );
	m_pCenterBg->SetBgColor( Color( 190, 115, 0, 255 ) );

	// start the first item selected
	SetSelectedChapterIndex( 0 );
}

CNewGameDialog::~CNewGameDialog()
{
	delete m_pFooter;
	m_pFooter = NULL;
}

void CNewGameDialog::Activate( void )
{
	m_bMapStarting = false;

	if ( GameUI().IsConsoleUI() )
	{
		// Stop blinking the menu item now that we've seen the unlocked stuff
		CBasePanel *pBasePanel = BasePanel();
		if ( pBasePanel )
			pBasePanel->SetMenuItemBlinkingState( "OpenNewGameDialog", false );

		BonusMapsDatabase()->SetBlink( false );
	}

	// Commentary stuff is set up on activate because in XBox the new game menu is never deleted
	SetTitle( ( ( m_bCommentaryMode ) ? ( "#GameUI_LoadCommentary" ) : ( "#GameUI_NewGame") ), true);

	if ( m_pCommentaryLabel )
		m_pCommentaryLabel->SetVisible( m_bCommentaryMode );

	// work out which chapters are unlocked
	ConVarRef var( "sv_unlockedchapters" );
	const char *unlockedChapter = var.IsValid() ? var.GetString() : "1";
	int iUnlockedChapter = atoi(unlockedChapter);

	for ( int i = 0; i < m_ChapterPanels.Count(); i++)
	{
		CGameChapterPanel *pChapterPanel = m_ChapterPanels[ i ];

		if ( pChapterPanel )
		{
			pChapterPanel->SetCommentaryMode( m_bCommentaryMode );

			UpdatePanelLockedStatus( iUnlockedChapter, i + 1, pChapterPanel );
		}
	}

	BaseClass::Activate();
}

//-----------------------------------------------------------------------------
// Purpose: Apply special properties of the menu
//-----------------------------------------------------------------------------
void CNewGameDialog::ApplySettings( KeyValues *inResourceData )
{
	BaseClass::ApplySettings( inResourceData );

	int ypos = inResourceData->GetInt( "chapterypos", 40 );
	if ( IsProportional() )
	{
		ypos = scheme()->GetProportionalScaledValueEx( GetScheme(), ypos );
	}

	for ( int i = 0; i < NUM_SLOTS; ++i )
	{
		m_PanelYPos[i] = ypos;
	}

	m_pCenterBg->SetTall( inResourceData->GetInt( "centerbgtall", 0 ) );

	g_ScrollSpeedSlow = inResourceData->GetFloat( "scrollslow", 0.0f );
	g_ScrollSpeedFast = inResourceData->GetFloat( "scrollfast", 0.0f );
	SetFastScroll( false );
}

void CNewGameDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	if ( m_pFooter )
	{
		KeyValues *pFooterControlSettings = BasePanel()->GetConsoleControlSettings()->FindKey( "NewGameFooter.res" );
		m_pFooter->LoadControlSettings( "null", NULL, pFooterControlSettings );
	}

	UpdateMenuComponents( SCROLL_NONE );

	m_pCommentaryLabel = dynamic_cast<vgui::Label*>( FindChildByName( "CommentaryUnlock" ) );
	if ( m_pCommentaryLabel )
		m_pCommentaryLabel->SetVisible( m_bCommentaryMode );

	if ( GameUI().IsConsoleUI() )
	{
		if ( !m_bCommentaryMode && BonusMapsDatabase()->BonusesUnlocked() && !m_ChapterPanels[ m_PanelIndex[SLOT_CENTER] ]->HasBonus() )
		{
			// Find the first bonus
			ScrollSelectionPanels( SCROLL_LEFT );
			m_bScrollToFirstBonusMap = true;
		}
	}
}

static float GetArrowAlpha( void )
{
	// X360TBD: Pulsing arrows
	return 255.f;
}

//-----------------------------------------------------------------------------
// Purpose: sets the correct properties for visible components
//-----------------------------------------------------------------------------
void CNewGameDialog::UpdateMenuComponents( EScrollDirection dir )
{
	// This is called prior to any scrolling, 
	// so we need to look ahead to the post-scroll state
	int centerIdx = SLOT_CENTER;
	if ( dir == SCROLL_LEFT )
	{
		++centerIdx;
	}
	else if ( dir == SCROLL_RIGHT )
	{
		--centerIdx;
	}
	int leftIdx = centerIdx - 1;
	int rightIdx = centerIdx + 1;

	if ( GameUI().IsConsoleUI() )
	{
		bool bHasBonus = false;
		if ( m_PanelIndex[centerIdx] != INVALID_INDEX )
		{
			wchar_t buffer[ MAX_PATH ];
			m_ChapterPanels[ m_PanelIndex[centerIdx] ]->m_pChapterNameLabel->GetText( buffer, sizeof(buffer) );
			m_pChapterTitleLabels[(unsigned)m_ActiveTitleIdx]->SetText( buffer );

			// If it has bonuses show the scroll up and down arrows
			bHasBonus = m_ChapterPanels[ m_PanelIndex[centerIdx] ]->HasBonus();
		}

		vgui::Panel *leftArrow = this->FindChildByName( "LeftArrow" );
		vgui::Panel *rightArrow = this->FindChildByName( "RightArrow" );
		if ( leftArrow )
		{
			if ( m_PanelIndex[leftIdx] != INVALID_INDEX )
			{
				leftArrow->SetFgColor( Color( 255, 255, 255, GetArrowAlpha() ) );
			}
			else
			{
				leftArrow->SetFgColor( Color( 128, 128, 128, 64 ) );
			}
		}
		if ( rightArrow )
		{
			if ( m_PanelIndex[rightIdx] != INVALID_INDEX )
			{
				rightArrow->SetFgColor( Color( 255, 255, 255, GetArrowAlpha() ) );
			}
			else
			{
				rightArrow->SetFgColor( Color( 128, 128, 128, 64 ) );
			}
		}

		if ( bHasBonus )
		{
			// Find the bonus description for this panel
			for ( int iBonus = 0; iBonus < BonusMapsDatabase()->BonusCount(); ++iBonus )
			{
				m_pBonusMapDescription = BonusMapsDatabase()->GetBonusData( iBonus );
				if ( Q_stricmp( m_pBonusMapDescription->szChapterName, m_ChapterPanels[ m_PanelIndex[centerIdx] ]->GetConfigFile() ) == 0 )
					break;
			}
		}
		else
		{
			m_pBonusMapDescription = NULL;
		}

		vgui::Panel *upArrow = this->FindChildByName( "UpArrow" );
		vgui::Panel *downArrow = this->FindChildByName( "DownArrow" );

		if ( upArrow )
			upArrow->SetVisible( bHasBonus );
		if ( downArrow )
			downArrow->SetVisible( bHasBonus );

		m_pBonusSelection->SetVisible( bHasBonus );
		m_pBonusSelectionBorder->SetVisible( bHasBonus );

		UpdateBonusSelection();
	}

	// No buttons in the xbox ui
	if ( !GameUI().IsConsoleUI() )
	{
		if ( m_PanelIndex[leftIdx] == INVALID_INDEX || m_PanelIndex[leftIdx] == 0 )
		{
			m_pPrevButton->SetVisible( false );
			m_pPrevButton->SetEnabled( false );
		}
		else
		{
			m_pPrevButton->SetVisible( true );
			m_pPrevButton->SetEnabled( true );
		}

		if ( m_ChapterPanels.Count() < 4 ) // if there are less than 4 chapters show the next button but disabled
		{
			m_pNextButton->SetVisible( true );
			m_pNextButton->SetEnabled( false );
		}
		else if ( m_PanelIndex[rightIdx] == INVALID_INDEX || m_PanelIndex[rightIdx] == m_ChapterPanels.Count()-1 )
		{
			m_pNextButton->SetVisible( false );
			m_pNextButton->SetEnabled( false );
		}
		else
		{
			m_pNextButton->SetVisible( true );
			m_pNextButton->SetEnabled( true );
		}
	}
}

void CNewGameDialog::UpdateBonusSelection( void )
{
	int iNumChallenges = 0;
	if ( m_pBonusMapDescription )
	{
		if ( m_pBonusMapDescription->m_pChallenges )
			iNumChallenges = m_pBonusMapDescription->m_pChallenges->Count();

		// Wrap challenge selection to fit number of possible selections
		if ( m_iBonusSelection < 0 )
			m_iBonusSelection = iNumChallenges + 1;
		else if ( m_iBonusSelection >= iNumChallenges + 2 )
			m_iBonusSelection = 0;
	}
	else
	{
		// No medals to show
		SetControlVisible( "ChallengeEarnedMedal", false );
		SetControlVisible( "ChallengeBestLabel", false );
		SetControlVisible( "ChallengeNextMedal", false );
		SetControlVisible( "ChallengeNextLabel", false );
		return;
	}

	if ( m_iBonusSelection == 0 )
	{
		m_pBonusSelection->SetText( "#GameUI_BonusMapsStandard" );
		SetControlVisible( "ChallengeEarnedMedal", false );
		SetControlVisible( "ChallengeBestLabel", false );
		SetControlVisible( "ChallengeNextMedal", false );
		SetControlVisible( "ChallengeNextLabel", false );
	}
	else if ( m_iBonusSelection == 1 )
	{
		m_pBonusSelection->SetText( "#GameUI_BonusMapsAdvanced" );
		SetControlVisible( "ChallengeEarnedMedal", false );
		SetControlVisible( "ChallengeBestLabel", false );
		SetControlVisible( "ChallengeNextMedal", false );
		SetControlVisible( "ChallengeNextLabel", false );

		char szMapAdvancedName[ 256 ] = "";
		if ( m_pBonusMapDescription )
		{
			Q_snprintf( szMapAdvancedName, sizeof( szMapAdvancedName ), "%s_advanced", m_pBonusMapDescription->szMapFileName );
		}

		BonusMapDescription_t *pAdvancedDescription = NULL;

		// Find the bonus description for this panel
		for ( int iBonus = 0; iBonus < BonusMapsDatabase()->BonusCount(); ++iBonus )
		{
			pAdvancedDescription = BonusMapsDatabase()->GetBonusData( iBonus );
			if ( Q_stricmp( szMapAdvancedName, pAdvancedDescription->szMapFileName ) == 0 )
				break;
		}

		if ( pAdvancedDescription && pAdvancedDescription->bComplete )
		{
			CBitmapImagePanel *pBitmap = dynamic_cast<CBitmapImagePanel*>( FindChildByName( "ChallengeEarnedMedal" ) );
			pBitmap->SetVisible( true );
			pBitmap->setTexture( "hud/icon_complete" );
		}
	}
	else
	{
		int iChallenge = m_iBonusSelection - 2;
		ChallengeDescription_t *pChallengeDescription = &((*m_pBonusMapDescription->m_pChallenges)[ iChallenge ]);

		// Set the display text for the selected challenge
		m_pBonusSelection->SetText( pChallengeDescription->szName );

		int iBest, iEarnedMedal, iNext, iNextMedal;
		GetChallengeMedals( pChallengeDescription, iBest, iEarnedMedal, iNext, iNextMedal );

		char szBuff[ 512 ];

		// Set earned medal
		if ( iEarnedMedal > -1 && iBest != -1 )
		{
			if ( iChallenge < 10 )
				Q_snprintf( szBuff, sizeof( szBuff ), "medals/medal_0%i_%s", iChallenge, g_pszMedalNames[ iEarnedMedal ] );
			else
				Q_snprintf( szBuff, sizeof( szBuff ), "medals/medal_%i_%s", iChallenge, g_pszMedalNames[ iEarnedMedal ] );

			CBitmapImagePanel *pBitmap = dynamic_cast<CBitmapImagePanel*>( FindChildByName( "ChallengeEarnedMedal" ) );
			pBitmap->SetVisible( true );
			pBitmap->setTexture( szBuff );
		}
		else
		{
			CBitmapImagePanel *pBitmap = dynamic_cast<CBitmapImagePanel*>( FindChildByName( "ChallengeEarnedMedal" ) );
			pBitmap->SetVisible( false );
		}

		// Set next medal
		if ( iNextMedal > 0 )
		{
			if ( iChallenge < 10 )
				Q_snprintf( szBuff, sizeof( szBuff ), "medals/medal_0%i_%s", iChallenge, g_pszMedalNames[ iNextMedal ] );
			else
				Q_snprintf( szBuff, sizeof( szBuff ), "medals/medal_%i_%s", iChallenge, g_pszMedalNames[ iNextMedal ] );

			CBitmapImagePanel *pBitmap = dynamic_cast<CBitmapImagePanel*>( FindChildByName( "ChallengeNextMedal" ) );
			pBitmap->SetVisible( true );
			pBitmap->setTexture( szBuff );
		}
		else
		{
			SetControlVisible( "ChallengeNextMedal", false );
		}

		wchar_t szWideBuff[ 64 ];
		wchar_t szWideBuff2[ 64 ];

		// Best label
		if ( iBest != -1 )
		{
			Q_snprintf( szBuff, sizeof( szBuff ), "%i", iBest );
			g_pVGuiLocalize->ConvertANSIToUnicode( szBuff, szWideBuff2, sizeof( szWideBuff2 ) );
			g_pVGuiLocalize->ConstructString( szWideBuff, sizeof( szWideBuff ), g_pVGuiLocalize->Find( "#GameUI_BonusMapsBest" ), 1, szWideBuff2 );
			g_pVGuiLocalize->ConvertUnicodeToANSI( szWideBuff, szBuff, sizeof( szBuff ) );

			SetControlString( "ChallengeBestLabel", szBuff );
			SetControlVisible( "ChallengeBestLabel", true );
		}
		else
		{
			SetControlVisible( "ChallengeBestLabel", false );
		}

		// Next label
		if ( iNext != -1 )
		{
			Q_snprintf( szBuff, sizeof( szBuff ), "%i", iNext );
			g_pVGuiLocalize->ConvertANSIToUnicode( szBuff, szWideBuff2, sizeof( szWideBuff2 ) );
			g_pVGuiLocalize->ConstructString( szWideBuff, sizeof( szWideBuff ), g_pVGuiLocalize->Find( "#GameUI_BonusMapsGoal" ), 1, szWideBuff2 );
			g_pVGuiLocalize->ConvertUnicodeToANSI( szWideBuff, szBuff, sizeof( szBuff ) );

			SetControlString( "ChallengeNextLabel", szBuff );
			SetControlVisible( "ChallengeNextLabel", true );
		}
		else
		{
			SetControlVisible( "ChallengeNextLabel", false );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: sets a chapter as selected
//-----------------------------------------------------------------------------
void CNewGameDialog::SetSelectedChapterIndex( int index )
{
	m_iSelectedChapter = index;

	for (int i = 0; i < m_ChapterPanels.Count(); i++)
	{
		if ( i == index )
		{
			m_ChapterPanels[i]->SetSelected( true );
		}
		else
		{
			m_ChapterPanels[i]->SetSelected( false );
		}
	}

	if ( m_pPlayButton )
	{
		m_pPlayButton->SetEnabled( true );
	}

	// Setup panels to the left of the selected panel
	int selectedSlot = GameUI().IsConsoleUI() ? SLOT_CENTER : index % 3 + 1;
	int currIdx = index;
	for ( int i = selectedSlot; i >= 0 && currIdx >= 0; --i )
	{
		m_PanelIndex[i] = currIdx;
		--currIdx;
		InitPanelIndexForDisplay( i );
	}

	// Setup panels to the right of the selected panel
	currIdx = index + 1;
	for ( int i = selectedSlot + 1; i < NUM_SLOTS && currIdx < m_ChapterPanels.Count(); ++i )
	{
		m_PanelIndex[i] = currIdx;
		++currIdx;
		InitPanelIndexForDisplay( i );
	}

	UpdateMenuComponents( SCROLL_NONE );
}

//-----------------------------------------------------------------------------
// Purpose: sets a chapter as selected
//-----------------------------------------------------------------------------
void CNewGameDialog::SetSelectedChapter( const char *chapter )
{
	Assert( chapter );
	for (int i = 0; i < m_ChapterPanels.Count(); i++)
	{
		if ( chapter && !Q_stricmp(m_ChapterPanels[i]->GetChapter(), chapter) )
		{
			m_iSelectedChapter = i;
			m_ChapterPanels[m_iSelectedChapter]->SetSelected( true );
		}
		else
		{
			m_ChapterPanels[i]->SetSelected( false );
		}
	}

	if ( m_pPlayButton )
	{
		m_pPlayButton->SetEnabled( true );
	}
}


//-----------------------------------------------------------------------------
// iUnlockedChapter - the value of sv_unlockedchapters, 1-based. A value of 0
//		is treated as a 1, since at least one chapter must be unlocked.
//
// i - the 1-based index of the chapter we're considering.
//-----------------------------------------------------------------------------
void CNewGameDialog::UpdatePanelLockedStatus( int iUnlockedChapter, int i, CGameChapterPanel *pChapterPanel )
{
	if ( iUnlockedChapter <= 0 )
	{
		iUnlockedChapter = 1;
	}

	// Commentary mode requires chapters to be finished before they can be chosen
	bool bLocked = false;

	if ( m_bCommentaryMode )
	{
		bLocked = ( iUnlockedChapter <= i );
	}
	else
	{
		if ( iUnlockedChapter < i )
		{
			// Never lock the first chapter
			bLocked = ( i != 0 );
		}
	}

	pChapterPanel->SetEnabled( !bLocked );
}

//-----------------------------------------------------------------------------
// Purpose: Called before a panel scroll starts.
//-----------------------------------------------------------------------------
void CNewGameDialog::PreScroll( EScrollDirection dir )
{
	int hideIdx = INVALID_INDEX;
	if ( dir == SCROLL_LEFT )
	{
		hideIdx = m_PanelIndex[SLOT_LEFT];
	}
	else if ( dir == SCROLL_RIGHT )
	{
		hideIdx = m_PanelIndex[SLOT_RIGHT];
	}
	if ( hideIdx != INVALID_INDEX )
	{
		// Push back the panel that's about to be hidden
		// so the next panel scrolls over the top of it.
		m_ChapterPanels[hideIdx]->SetZPos( 0 );
	}

	// Flip the active title label prior to the crossfade
	m_ActiveTitleIdx ^= 0x01;
}

//-----------------------------------------------------------------------------
// Purpose: Called after a panel scroll finishes.
//-----------------------------------------------------------------------------
void CNewGameDialog::PostScroll( EScrollDirection dir )
{
	int index = INVALID_INDEX;
	if ( dir == SCROLL_LEFT )
	{
		index = m_PanelIndex[SLOT_RIGHT];
	}
	else if ( dir == SCROLL_RIGHT )
	{
		index = m_PanelIndex[SLOT_LEFT];
	}

	// Fade in the revealed panel
	if ( index != INVALID_INDEX )
	{
		CGameChapterPanel *panel = m_ChapterPanels[index];
		panel->SetZPos( 50 );
		GetAnimationController()->RunAnimationCommand( panel, "alpha", 255, 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_LINEAR );
	}

	if ( GameUI().IsConsoleUI() )
	{
		if ( BonusMapsDatabase()->BonusesUnlocked() && m_bScrollToFirstBonusMap )
		{
			if ( !m_ChapterPanels[ m_PanelIndex[SLOT_CENTER] ]->HasBonus() )
			{
				// Find the first bonus
				ScrollSelectionPanels( SCROLL_LEFT );
			}
			else
			{
				// Found a bonus, stop scrolling
				m_bScrollToFirstBonusMap = false;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Initiates a panel scroll and starts the animation.
//-----------------------------------------------------------------------------
void CNewGameDialog::ScrollSelectionPanels( EScrollDirection dir )
{
	// Only initiate a scroll if panels aren't currently scrolling
	if ( !m_bScrolling )
	{
		// Handle any pre-scroll setup
		PreScroll( dir );

		if ( dir == SCROLL_LEFT)
		{
			m_ScrollCt += SCROLL_LEFT;
		}
		else if ( dir == SCROLL_RIGHT && m_PanelIndex[SLOT_CENTER] != 0 )
		{
			m_ScrollCt += SCROLL_RIGHT;
		}

		m_bScrolling = true;
		AnimateSelectionPanels();

		// Update the arrow colors, help text, and buttons. Doing it here looks better than having
		// the components change after the entire scroll animation has finished.
		UpdateMenuComponents( m_ScrollDirection );
	}
}

void CNewGameDialog::ScrollBonusSelection( EScrollDirection dir )
{
	// Don't scroll if there's no bonuses for this panel
	if ( !m_pBonusMapDescription )
		return;

	m_iBonusSelection += dir;

	vgui::surface()->PlaySound( "UI/buttonclick.wav" );

	UpdateBonusSelection();
}

//-----------------------------------------------------------------------------
// Purpose: Initiates the scripted scroll and fade effects of all five slotted panels 
//-----------------------------------------------------------------------------
void CNewGameDialog::AnimateSelectionPanels( void )
{
	int idxOffset = 0;
	int startIdx = SLOT_LEFT;
	int endIdx = SLOT_RIGHT;

	// Don't scroll outside the bounds of the panel list
	if ( m_ScrollCt >= SCROLL_LEFT && (m_PanelIndex[SLOT_CENTER] < m_ChapterPanels.Count() - 1 || !GameUI().IsConsoleUI()) )
	{
		idxOffset = -1;
		endIdx = SLOT_OFFRIGHT;
		m_ScrollDirection = SCROLL_LEFT;
	}
	else if ( m_ScrollCt <= SCROLL_RIGHT && (m_PanelIndex[SLOT_CENTER] > 0 || !GameUI().IsConsoleUI()) )
	{
		idxOffset = 1;
		startIdx = SLOT_OFFLEFT;
		m_ScrollDirection = SCROLL_RIGHT;
	}

	if ( 0 == idxOffset )
	{
		// Kill the scroll, it's outside the bounds
		m_ScrollCt = 0;
		m_bScrolling = false;
		m_ScrollDirection = SCROLL_NONE;
		vgui::surface()->PlaySound( "player/suit_denydevice.wav" );
		return;
	}

	// Should never happen
	if ( startIdx > endIdx )
		return;

	for ( int i = startIdx; i <= endIdx; ++i )
	{
		if ( m_PanelIndex[i] != INVALID_INDEX )
		{
			int nextIdx = i + idxOffset;
			CGameChapterPanel *panel = m_ChapterPanels[ m_PanelIndex[i] ];
			GetAnimationController()->RunAnimationCommand( panel, "xpos",  m_PanelXPos[nextIdx],  0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_LINEAR );
			GetAnimationController()->RunAnimationCommand( panel, "ypos",  m_PanelYPos[nextIdx],  0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_LINEAR );
			GetAnimationController()->RunAnimationCommand( panel, "alpha", m_PanelAlpha[nextIdx], 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_LINEAR );
		}
	}

	if ( GameUI().IsConsoleUI() )
	{
		vgui::surface()->PlaySound( "UI/buttonclick.wav" );

		// Animate the center background panel
		GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 0, 0, m_ScrollSpeed * 0.25f, vgui::AnimationController::INTERPOLATOR_LINEAR );
		
		// Crossfade the chapter title labels
		int inactiveTitleIdx = m_ActiveTitleIdx ^ 0x01;
		GetAnimationController()->RunAnimationCommand( m_pChapterTitleLabels[(unsigned)m_ActiveTitleIdx], "alpha", 255, 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_LINEAR );
		GetAnimationController()->RunAnimationCommand( m_pChapterTitleLabels[inactiveTitleIdx], "alpha", 0, 0, m_ScrollSpeed, vgui::AnimationController::INTERPOLATOR_LINEAR );
		
		// Scrolling up through chapters, offset is negative
		m_iSelectedChapter -= idxOffset;
	}

	PostMessage( this, new KeyValues( "FinishScroll" ), m_ScrollSpeed );
}

//-----------------------------------------------------------------------------
// Purpose: After a scroll, each panel slot holds the index of a panel that has 
//			scrolled to an adjacent slot. This function updates each slot so
//			it holds the index of the panel that is actually in that slot's position.
//-----------------------------------------------------------------------------
void CNewGameDialog::ShiftPanelIndices( int offset )
{
	// Shift all the elements over one slot, then calculate what the last slot's index should be.
	int lastSlot = NUM_SLOTS - 1;
	if ( offset > 0 )
	{
		// Hide the panel that's dropping out of the slots
		if ( IsValidPanel( m_PanelIndex[0] ) )
		{
			m_ChapterPanels[ m_PanelIndex[0] ]->SetVisible( false );
		}

		// Scrolled panels to the right, so shift the indices one slot to the left
		Q_memmove( &m_PanelIndex[0], &m_PanelIndex[1], lastSlot * sizeof( m_PanelIndex[0] ) );
		if ( m_PanelIndex[lastSlot] != INVALID_INDEX )
		{
			int num = m_PanelIndex[ lastSlot ] + 1;
			if ( IsValidPanel( num ) )
			{
				m_PanelIndex[lastSlot] = num;
				InitPanelIndexForDisplay( lastSlot );
			}
			else
			{
				m_PanelIndex[lastSlot] = INVALID_INDEX;
			}
		}
	}
	else
	{
		// Hide the panel that's dropping out of the slots
		if ( IsValidPanel( m_PanelIndex[lastSlot] ) )
		{
			m_ChapterPanels[ m_PanelIndex[lastSlot] ]->SetVisible( false );
		}

		// Scrolled panels to the left, so shift the indices one slot to the right
		Q_memmove( &m_PanelIndex[1], &m_PanelIndex[0], lastSlot * sizeof( m_PanelIndex[0] ) );
		if ( m_PanelIndex[0] != INVALID_INDEX )
		{
			int num = m_PanelIndex[0] - 1;
			if ( IsValidPanel( num ) )
			{
				m_PanelIndex[0] = num;
				InitPanelIndexForDisplay( 0 );
			}
			else
			{
				m_PanelIndex[0] = INVALID_INDEX;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Validates an index into the selection panels vector
//-----------------------------------------------------------------------------
bool CNewGameDialog::IsValidPanel( const int idx )
{
	if ( idx < 0 || idx >= m_ChapterPanels.Count() )
		return false;
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Sets up a panel's properties before it is displayed
//-----------------------------------------------------------------------------
void CNewGameDialog::InitPanelIndexForDisplay( const int idx )
{
	CGameChapterPanel *panel = m_ChapterPanels[ m_PanelIndex[idx] ];
	if ( panel )
	{
		panel->SetPos( m_PanelXPos[idx], m_PanelYPos[idx] );
		panel->SetAlpha( m_PanelAlpha[idx] );
		panel->SetVisible( true );
		if ( m_PanelAlpha[idx] )
		{
			panel->SetZPos( 50 );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Sets which scroll speed should be used
//-----------------------------------------------------------------------------
void CNewGameDialog::SetFastScroll( bool fast )
{
	m_ScrollSpeed = fast ? g_ScrollSpeedFast : g_ScrollSpeedSlow;
}

//-----------------------------------------------------------------------------
// Purpose: Checks if a button is being held down, and speeds up the scroll 
//-----------------------------------------------------------------------------
void CNewGameDialog::ContinueScrolling( void )
{
	if ( !GameUI().IsConsoleUI() )
	{
		if ( m_PanelIndex[SLOT_CENTER-1] % 3 )
		{
	//		m_ButtonPressed = m_ScrollDirection;
			ScrollSelectionPanels( m_ScrollDirection );
		}
		return;
	}

	if ( m_ButtonPressed == m_ScrollDirection )
	{
		SetFastScroll( true );
		ScrollSelectionPanels( m_ScrollDirection );
	}
	else if ( m_ButtonPressed != SCROLL_NONE )
	{
		// The other direction has been pressed - start a slow scroll
		SetFastScroll( false );
		ScrollSelectionPanels( (EScrollDirection)m_ButtonPressed );
	}
	else
	{
		SetFastScroll( false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called when a scroll distance of one slot has been completed
//-----------------------------------------------------------------------------
void CNewGameDialog::FinishScroll( void )
{
	// Fade the center bg panel back in
	GetAnimationController()->RunAnimationCommand( m_pCenterBg, "alpha", 255, 0, m_ScrollSpeed * 0.25f, vgui::AnimationController::INTERPOLATOR_LINEAR );

	ShiftPanelIndices( m_ScrollDirection );
	m_bScrolling = false;
	m_ScrollCt = 0;
	
	// End of scroll step
	PostScroll( m_ScrollDirection );

	// Continue scrolling if necessary
	ContinueScrolling();
}

//-----------------------------------------------------------------------------
// Purpose: starts the game at the specified skill level
//-----------------------------------------------------------------------------
void CNewGameDialog::StartGame( void )
{
	if ( m_ChapterPanels.IsValidIndex( m_iSelectedChapter ) )
	{
		char mapcommand[512];
		mapcommand[0] = 0;
		Q_snprintf( mapcommand, sizeof( mapcommand ), "disconnect\ndeathmatch 0\nprogress_enable\nexec %s\n", m_ChapterPanels[m_iSelectedChapter]->GetConfigFile() );

		// Set commentary
		ConVarRef commentary( "commentary" );
		commentary.SetValue( m_bCommentaryMode );

		ConVarRef sv_cheats( "sv_cheats" );
		sv_cheats.SetValue( m_bCommentaryMode );

		if ( IsPC() )
		{
			// If commentary is on, we go to the explanation dialog (but not for teaser trailers)
			if ( m_bCommentaryMode && !m_ChapterPanels[m_iSelectedChapter]->IsTeaserChapter() )
			{
				// Check our current state and disconnect us from any multiplayer server we're connected to.
				// This fixes an exploit where players would click "start" on the commentary dialog to enable
				// sv_cheats on the client (via the code above) and then hit <esc> to get out of the explanation dialog.
				if ( GameUI().IsInMultiplayer() )
				{
					engine->ExecuteClientCmd( "disconnect" );
				}

				DHANDLE<CCommentaryExplanationDialog> hCommentaryExplanationDialog;
				if ( !hCommentaryExplanationDialog.Get() )
				{
					hCommentaryExplanationDialog = new CCommentaryExplanationDialog( BasePanel(), mapcommand );
				}
				hCommentaryExplanationDialog->Activate();
			}
			else
			{
				// start map
				BasePanel()->FadeToBlackAndRunEngineCommand( mapcommand );
			}
		}
		else if ( IsX360() )
		{
			if ( m_ChapterPanels[m_iSelectedChapter]->HasBonus() && m_iBonusSelection > 0 )
			{
				if ( m_iBonusSelection == 1 )
				{
					// Run the advanced chamber instead of the config file
					char *pLastSpace = Q_strrchr( mapcommand, '\n' );
					pLastSpace[ 0 ] = '\0';
					pLastSpace = Q_strrchr( mapcommand, '\n' );

					Q_snprintf( pLastSpace, sizeof( mapcommand ) - Q_strlen( mapcommand ), "\nmap %s_advanced\n", m_pBonusMapDescription->szMapFileName );
				}
				else
				{
					char sz[ 256 ];

					int iChallenge = m_iBonusSelection - 1;

					// Set up the challenge mode
					Q_snprintf( sz, sizeof( sz ), "sv_bonus_challenge %i\n", iChallenge );
					engine->ClientCmd_Unrestricted( sz );

					ChallengeDescription_t *pChallengeDescription = &((*m_pBonusMapDescription->m_pChallenges)[ iChallenge - 1 ]);

					// Set up medal goals
					BonusMapsDatabase()->SetCurrentChallengeObjectives( pChallengeDescription->iBronze, pChallengeDescription->iSilver, pChallengeDescription->iGold );
					BonusMapsDatabase()->SetCurrentChallengeNames( m_pBonusMapDescription->szFileName, m_pBonusMapDescription->szMapName, pChallengeDescription->szName );
				}
			}

			m_bMapStarting = true;
			BasePanel()->FadeToBlackAndRunEngineCommand( mapcommand );
		}

		OnClose();
	}
}

void CNewGameDialog::OnClose( void )
{
	m_KeyRepeat.Reset();

	if ( GameUI().IsConsoleUI() && !m_bMapStarting )
	{
		BasePanel()->RunCloseAnimation( "CloseNewGameDialog_OpenMainMenu" );			
		BonusMapsDatabase()->WriteSaveData();	// Closing this dialog is a good time to save
	}
	BaseClass::OnClose();
}

//-----------------------------------------------------------------------------
// Purpose: handles button commands
//-----------------------------------------------------------------------------
void CNewGameDialog::OnCommand( const char *command )
{
	bool bReset = true;

	if ( !stricmp( command, "Play" ) )
	{
		if ( m_bMapStarting )
			return;

		if ( GameUI().IsConsoleUI() )
		{
			if ( m_ChapterPanels[m_iSelectedChapter]->IsEnabled() )
			{
				if ( !GameUI().HasSavedThisMenuSession() && GameUI().IsInLevel() && engine->GetMaxClients() == 1 )
				{
					vgui::surface()->PlaySound( "UI/buttonclickrelease.wav" );
					BasePanel()->ShowMessageDialog( MD_SAVE_BEFORE_NEW_GAME, this );
				}
				else
				{
					OnCommand( "StartNewGame" );
				}
			}
			else
			{
				// This chapter isn't unlocked!
				m_bMapStarting = false;
				vgui::surface()->PlaySound( "player/suit_denydevice.wav" );

				if ( m_bCommentaryMode )
				{
					BasePanel()->ShowMessageDialog( MD_COMMENTARY_CHAPTER_UNLOCK_EXPLANATION, this );
				}
			}
		}
		else
		{
			StartGame();
		}
	}

#ifdef _X360
	else if ( !stricmp( command, "StartNewGame" ) )
	{
		ConVarRef commentary( "commentary" );

		if ( m_bCommentaryMode && !commentary.GetBool() )
		{
			// Using the commentary menu, but not already in commentary mode, explain the rules
			PostMessage( (vgui::Panel*)this, new KeyValues( "command", "command", "StartNewGameWithCommentaryExplanation" ), 0.2f );
		}
		else
		{
			if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED || 
				 !ModInfo().IsSinglePlayerOnly() )
			{
				// Multiplayer or no storage device so don't bore them with autosave details
				m_bMapStarting = true;
				OnCommand( "StartNewGameNoCommentaryExplanation" );
			}
			else
			{
				// Don't allow other inputs
				m_bMapStarting = true;

				// Remind them how autosaves work
				PostMessage( (vgui::Panel*)this, new KeyValues( "command", "command", "StartNewGameWithAutosaveExplanation" ), 0.2f );
			}
		}
	}
	else if ( !stricmp( command, "StartNewGameWithAutosaveExplanation" ) )
	{
		BasePanel()->ShowMessageDialog( MD_AUTOSAVE_EXPLANATION, this );
	}
	else if ( !stricmp( command, "StartNewGameWithCommentaryExplanation" ) )
	{
		if ( ModInfo().IsSinglePlayerOnly() )
		{
			// Don't allow other inputs
			m_bMapStarting = true;
			BasePanel()->ShowMessageDialog( MD_COMMENTARY_EXPLANATION, this );
		}
		else
		{
			// Don't allow other inputs
			m_bMapStarting = true;
			BasePanel()->ShowMessageDialog( MD_COMMENTARY_EXPLANATION_MULTI, this );
		}
	}
	else if ( !stricmp( command, "StartNewGameNoCommentaryExplanation" ) )
	{
		vgui::surface()->PlaySound( "UI/buttonclickrelease.wav" );
		BasePanel()->RunAnimationWithCallback( this, "CloseNewGameDialog", new KeyValues( "StartGame" ) );
	}
#endif

	else if ( !stricmp( command, "Next" ) )
	{
		if ( m_bMapStarting )
			return;

		ScrollSelectionPanels( SCROLL_LEFT );
		bReset = false;
	}
	else if ( !stricmp( command, "Prev" ) )
	{
		if ( m_bMapStarting )
			return;

		ScrollSelectionPanels( SCROLL_RIGHT );
		bReset = false;
	}
	else if ( !stricmp( command, "Mode_Next" ) )
	{
		if ( m_bMapStarting )
			return;

		ScrollBonusSelection( SCROLL_LEFT );
		bReset = false;
	}
	else if ( !stricmp( command, "Mode_Prev" ) )
	{
		if ( m_bMapStarting )
			return;

		ScrollBonusSelection( SCROLL_RIGHT );
		bReset = false;
	}
	else if ( !Q_stricmp( command, "ReleaseModalWindow" ) )
	{
		vgui::surface()->RestrictPaintToSinglePanel(NULL);
	}
	else
	{
		BaseClass::OnCommand( command );
	}

	if ( bReset )
	{
		m_KeyRepeat.Reset();
	}
}

void CNewGameDialog::PaintBackground()
{
	if ( !GameUI().IsConsoleUI() )
	{
		BaseClass::PaintBackground();
		return;
	}

	int wide, tall;
	GetSize( wide, tall );

	Color col = GetBgColor();
	DrawBox( 0, 0, wide, tall, col, 1.0f );

	int y = 0;
	if ( m_pChapterTitleLabels[0] )
	{
		// offset by title
		int titleX, titleY, titleWide, titleTall;
		m_pChapterTitleLabels[0]->GetBounds( titleX, titleY, titleWide, titleTall );	
		y += titleY + titleTall;
	}
	else
	{
		y = 8;
	}

	// draw an inset
	Color darkColor;
	darkColor.SetColor( 0.70f * (float)col.r(), 0.70f * (float)col.g(), 0.70f * (float)col.b(), col.a() );
	vgui::surface()->DrawSetColor( darkColor );
	vgui::surface()->DrawFilledRect( 8, y, wide - 8, tall - 8 );
}

void CNewGameDialog::OnKeyCodePressed( KeyCode code )
{
	switch ( code )
	{
	case KEY_XBUTTON_LEFT:
	case KEY_XSTICK1_LEFT:
	case KEY_XSTICK2_LEFT:
	case KEY_LEFT:
	case STEAMCONTROLLER_DPAD_LEFT:
		if ( !m_bScrolling )
		{
			for ( int i = 0; i < m_ChapterPanels.Count(); ++i )
			{
				if ( m_ChapterPanels[ i ]->IsSelected() )
				{
					int nNewChapter = i - 1;
					if ( nNewChapter >= 0 )
					{
						if ( nNewChapter < m_PanelIndex[ SLOT_LEFT ] && m_PanelIndex[ SLOT_LEFT ] != -1 )
						{
							ScrollSelectionPanels( SCROLL_RIGHT );
						}
						else if ( m_ChapterPanels[ nNewChapter ]->IsEnabled() )
						{
							SetSelectedChapterIndex( nNewChapter );
						}
					}
					break;
				}
			}
		}
		return;

	case KEY_XBUTTON_RIGHT:
	case KEY_XSTICK1_RIGHT:
	case KEY_XSTICK2_RIGHT:
	case KEY_RIGHT:
	case STEAMCONTROLLER_DPAD_RIGHT:
		if ( !m_bScrolling )
		{
			for ( int i = 0; i < m_ChapterPanels.Count(); ++i )
			{
				if ( m_ChapterPanels[ i ]->IsSelected() )
				{
					int nNewChapter = i + 1;
					if ( nNewChapter < m_ChapterPanels.Count() )
					{
						if ( nNewChapter > m_PanelIndex[ SLOT_RIGHT ] && m_PanelIndex[ SLOT_RIGHT ] != -1 )
						{
							ScrollSelectionPanels( SCROLL_LEFT );
						}
						else if ( m_ChapterPanels[ nNewChapter ]->IsEnabled() )
						{
							SetSelectedChapterIndex( nNewChapter );
						}
					}
					break;
				}
			}
		}
		return;

	case KEY_XBUTTON_B:
	case STEAMCONTROLLER_B:
		OnCommand( "Close" );
		return;

	case KEY_XBUTTON_A:
	case STEAMCONTROLLER_A:
		OnCommand( "Play" );
		return;
	}

	m_KeyRepeat.KeyDown( code );

	BaseClass::OnKeyCodePressed( code );
}

void CNewGameDialog::OnKeyCodeReleased( vgui::KeyCode code )
{
	m_KeyRepeat.KeyUp( code );

	BaseClass::OnKeyCodeReleased( code );
}

void CNewGameDialog::OnThink()
{
	vgui::KeyCode code = m_KeyRepeat.KeyRepeated();
	if ( code )
	{
		OnKeyCodeTyped( code );
	}

	BaseClass::OnThink();
}