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


#include "LoadingDialog.h"
#include "EngineInterface.h"
#include "IGameUIFuncs.h"

#include <vgui/IInput.h>
#include <vgui/ISurface.h>
#include <vgui/ILocalize.h>
#include <vgui/IScheme.h>
#include <vgui/ISystem.h>
#include <vgui_controls/ProgressBar.h>
#include <vgui_controls/Label.h>
#include <vgui_controls/Button.h>
#include <vgui_controls/HTML.h>
#include <vgui_controls/RichText.h>
#include "tier0/icommandline.h"

#include "GameUI_Interface.h"
#include "ModInfo.h"
#include "BasePanel.h"

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

using namespace vgui;

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CLoadingDialog::CLoadingDialog( vgui::Panel *parent ) : Frame(parent, "LoadingDialog")
{
	SetDeleteSelfOnClose(true);

	// Use console style
	m_bConsoleStyle = GameUI().IsConsoleUI();

	if ( !m_bConsoleStyle )
	{
		SetSize( 416, 100 );
		SetTitle( "#GameUI_Loading", true );
	}

	// center the loading dialog, unless we have another dialog to show in the background
	m_bCenter = !GameUI().HasLoadingBackgroundDialog();

	m_bShowingSecondaryProgress = false;
	m_flSecondaryProgress = 0.0f;
	m_flLastSecondaryProgressUpdateTime = 0.0f;
	m_flSecondaryProgressStartTime = 0.0f;

	m_pProgress = new ProgressBar( this, "Progress" );
	m_pProgress2 = new ProgressBar( this, "Progress2" );
	m_pInfoLabel = new Label( this, "InfoLabel", "" );
	m_pCancelButton = new Button( this, "CancelButton", "#GameUI_Cancel" );
	m_pTimeRemainingLabel = new Label( this, "TimeRemainingLabel", "" );
	m_pCancelButton->SetCommand( "Cancel" );

	if ( ModInfo().IsSinglePlayerOnly() == false && m_bConsoleStyle == true )
	{
		m_pLoadingBackground = new Panel( this, "LoadingDialogBG" );
	}
	else
	{
		m_pLoadingBackground = NULL;
	}

	SetMinimizeButtonVisible( false );
	SetMaximizeButtonVisible( false );
	SetCloseButtonVisible( false );
	SetSizeable( false );
	SetMoveable( false );

	if ( m_bConsoleStyle )
	{
		m_bCenter = false;
		m_pProgress->SetVisible( false );
		m_pProgress2->SetVisible( false );
		m_pInfoLabel->SetVisible( false );
		m_pCancelButton->SetVisible( false );
		m_pTimeRemainingLabel->SetVisible( false );
		m_pCancelButton->SetVisible( false );

		SetMinimumSize( 0, 0 );
		SetTitleBarVisible( false );

		m_flProgressFraction = 0;
	}
	else
	{
		m_pInfoLabel->SetBounds(20, 32, 392, 24);
		m_pProgress->SetBounds(20, 64, 300, 24); 
		m_pCancelButton->SetBounds(330, 64, 72, 24);
		m_pProgress2->SetVisible(false);
	}

	SetupControlSettings( false );
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CLoadingDialog::~CLoadingDialog()
{
	if ( input()->GetAppModalSurface() == GetVPanel() )
	{
		vgui::surface()->RestrictPaintToSinglePanel( NULL );
	}
}

void CLoadingDialog::PaintBackground()
{
	if ( !m_bConsoleStyle )
	{
		BaseClass::PaintBackground();
		return;
	}

	// draw solid progress bar with curved endcaps
	int panelWide, panelTall;
	GetSize( panelWide, panelTall );
	int barWide, barTall;
	m_pProgress->GetSize( barWide, barTall );
	int x = ( panelWide - barWide )/2;
	int y = panelTall - barTall;

	if ( m_pLoadingBackground )
	{
		vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" );
		Color color = GetSchemeColor( "TanDarker", Color(255, 255, 255, 255), vgui::scheme()->GetIScheme(scheme) );

		m_pLoadingBackground->SetFgColor( color );
		m_pLoadingBackground->SetBgColor( color );

		m_pLoadingBackground->SetPaintBackgroundEnabled( true );
	}
	
	if ( ModInfo().IsSinglePlayerOnly() )
	{
		DrawBox( x, y, barWide, barTall, Color( 0, 0, 0, 255 ), 1.0f );
	}

	DrawBox( x+2, y+2, barWide-4, barTall-4, Color( 100, 100, 100, 255 ), 1.0f );

	barWide = m_flProgressFraction * ( barWide - 4 );
	if ( barWide >= 12 )
	{
		// cannot draw a curved box smaller than 12 without artifacts
		DrawBox( x+2, y+2, barWide, barTall-4, Color( 200, 100, 0, 255 ), 1.0f );
	}
}

//-----------------------------------------------------------------------------
// Purpose: sets up dialog layout
//-----------------------------------------------------------------------------
void CLoadingDialog::SetupControlSettings( bool bForceShowProgressText )
{
	m_bShowingVACInfo = false;

	if ( GameUI().IsConsoleUI() )
	{
		KeyValues *pControlSettings = BasePanel()->GetConsoleControlSettings()->FindKey( "LoadingDialogNoBanner.res" );
		LoadControlSettings( "null", NULL, pControlSettings );
		return;
	}

	if ( ModInfo().IsSinglePlayerOnly() && !bForceShowProgressText )
	{
		LoadControlSettings("Resource/LoadingDialogNoBannerSingle.res");
	}
	else if ( gameuifuncs->IsConnectedToVACSecureServer() )
	{
		LoadControlSettings("Resource/LoadingDialogVAC.res");
		m_bShowingVACInfo = true;
	}
	else
	{
		LoadControlSettings("Resource/LoadingDialogNoBanner.res");
	}
}

//-----------------------------------------------------------------------------
// Purpose: Activates the loading screen, initializing and making it visible
//-----------------------------------------------------------------------------
void CLoadingDialog::Open()
{
	if ( !m_bConsoleStyle )
	{
		SetTitle( "#GameUI_Loading", true );
	}

	HideOtherDialogs( true );
	BaseClass::Activate();

	if ( !m_bConsoleStyle )
	{
		m_pProgress->SetVisible( true );
		if ( !ModInfo().IsSinglePlayerOnly() )
		{
			m_pInfoLabel->SetVisible( true );
		}
		m_pInfoLabel->SetText("");
		
		m_pCancelButton->SetText("#GameUI_Cancel");
		m_pCancelButton->SetCommand("Cancel");
	}
}


//-----------------------------------------------------------------------------
// Purpose: error display file
//-----------------------------------------------------------------------------
void CLoadingDialog::SetupControlSettingsForErrorDisplay( const char *settingsFile )
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	m_bCenter = true;
	SetTitle("#GameUI_Disconnected", true);
	m_pInfoLabel->SetText("");
	LoadControlSettings( settingsFile );
	HideOtherDialogs( true );

	BaseClass::Activate();
	
	m_pProgress->SetVisible(false);

	m_pInfoLabel->SetVisible(true);
	m_pCancelButton->SetText("#GameUI_Close");
	m_pCancelButton->SetCommand("Close");
	m_pInfoLabel->InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: shows or hides other top-level dialogs
//-----------------------------------------------------------------------------
void CLoadingDialog::HideOtherDialogs( bool bHide )
{
	if ( bHide )
	{
		if ( GameUI().HasLoadingBackgroundDialog() )
		{
			// if we have a loading background dialog, hide any other dialogs by moving the full-screen background dialog to the
			// front, then moving ourselves in front of it
			GameUI().ShowLoadingBackgroundDialog();
			vgui::ipanel()->MoveToFront( GetVPanel() );
			vgui::input()->SetAppModalSurface( GetVPanel() );
		}
		else
		{
			// if there is no loading background dialog, use VGUI paint restrictions to hide other dialogs
			vgui::surface()->RestrictPaintToSinglePanel(GetVPanel());
		}
	}
	else
	{
		if ( GameUI().HasLoadingBackgroundDialog() )
		{
			GameUI().HideLoadingBackgroundDialog();
			vgui::input()->SetAppModalSurface( NULL );
		}
		else
		{
			// remove any rendering restrictions
			vgui::surface()->RestrictPaintToSinglePanel(NULL);
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Turns dialog into error display
//-----------------------------------------------------------------------------
void CLoadingDialog::DisplayGenericError(const char *failureReason, const char *extendedReason)
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	// In certain race conditions, DisplayGenericError can get called AFTER OnClose() has been called.
	// If that happens and we don't call Activate(), then it'll continue closing when we don't want it to.
	Activate(); 
	
	SetupControlSettingsForErrorDisplay("Resource/LoadingDialogError.res");

	if ( extendedReason && strlen( extendedReason ) > 0 ) 
	{
		wchar_t compositeReason[256], finalMsg[512], formatStr[256];
		if ( extendedReason[0] == '#' )
		{
			wcsncpy(compositeReason, g_pVGuiLocalize->Find(extendedReason), sizeof( compositeReason ) / sizeof( wchar_t ) );
		}
		else
		{
			g_pVGuiLocalize->ConvertANSIToUnicode(extendedReason, compositeReason, sizeof( compositeReason ));
		}

		if ( failureReason[0] == '#' )
		{
			wcsncpy(formatStr, g_pVGuiLocalize->Find(failureReason), sizeof( formatStr ) / sizeof( wchar_t ) );
		}
		else
		{
			g_pVGuiLocalize->ConvertANSIToUnicode(failureReason, formatStr, sizeof( formatStr ));
		}

		g_pVGuiLocalize->ConstructString(finalMsg, sizeof( finalMsg ), formatStr, 1, compositeReason);
		m_pInfoLabel->SetText(finalMsg);
	}
	else
	{
		m_pInfoLabel->SetText(failureReason);
	}

	int wide, tall;
	int x,y;
	m_pInfoLabel->GetContentSize( wide, tall );
	m_pInfoLabel->GetPos( x, y );
	SetTall( tall + y + 50 );

	int buttonX, buttonY;
	m_pCancelButton->GetPos( buttonX, buttonY );
	m_pCancelButton->SetPos( buttonX, tall + y + 6 );

	m_pCancelButton->RequestFocus();
}


//-----------------------------------------------------------------------------
// Purpose: explain to the user they can't join secure servers due to a VAC ban
//-----------------------------------------------------------------------------
void CLoadingDialog::DisplayVACBannedError()
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	SetupControlSettingsForErrorDisplay("Resource/LoadingDialogErrorVACBanned.res");
	SetTitle("#VAC_ConnectionRefusedTitle", true);
}


//-----------------------------------------------------------------------------
// Purpose: explain to the user they can't connect to public servers due to 
//			not having a valid connection to Steam
//			this should only happen if they are a pirate
//-----------------------------------------------------------------------------
void CLoadingDialog::DisplayNoSteamConnectionError()
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	SetupControlSettingsForErrorDisplay("Resource/LoadingDialogErrorNoSteamConnection.res");
}


//-----------------------------------------------------------------------------
// Purpose: explain to the user they got kicked from a server due to that same account 
//			logging in from another location. This also triggers the refresh login dialog on OK 
//			being pressed.
//-----------------------------------------------------------------------------
void CLoadingDialog::DisplayLoggedInElsewhereError()
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	SetupControlSettingsForErrorDisplay("Resource/LoadingDialogErrorLoggedInElsewhere.res");
	m_pCancelButton->SetText("#GameUI_RefreshLogin_Login");
	m_pCancelButton->SetCommand("Login");
}


//-----------------------------------------------------------------------------
// Purpose: sets status info text
//-----------------------------------------------------------------------------
void CLoadingDialog::SetStatusText(const char *statusText)
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	m_pInfoLabel->SetText(statusText);
}

//-----------------------------------------------------------------------------
// Purpose: returns the previous state
//-----------------------------------------------------------------------------
bool CLoadingDialog::SetShowProgressText( bool show )
{
	if ( m_bConsoleStyle )
	{
		return false;
	}

	bool bret = m_pInfoLabel->IsVisible();
	if ( bret != show )
	{
		SetupControlSettings( show );
		m_pInfoLabel->SetVisible( show );
	}
	return bret;
}

//-----------------------------------------------------------------------------
// Purpose: updates time remaining
//-----------------------------------------------------------------------------
void CLoadingDialog::OnThink()
{
	BaseClass::OnThink();

	if ( !m_bConsoleStyle && m_bShowingSecondaryProgress )
	{
		// calculate the time remaining string
		wchar_t unicode[512];
		if (m_flSecondaryProgress >= 1.0f)
		{
			m_pTimeRemainingLabel->SetText("complete");
		}
		else if (ProgressBar::ConstructTimeRemainingString(unicode, sizeof(unicode), m_flSecondaryProgressStartTime, (float)system()->GetFrameTime(), m_flSecondaryProgress, m_flLastSecondaryProgressUpdateTime, true))
		{
			m_pTimeRemainingLabel->SetText(unicode);
		}
		else
		{
			m_pTimeRemainingLabel->SetText("");
		}
	}

	SetAlpha( 255 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLoadingDialog::PerformLayout()
{
	if ( m_bConsoleStyle )
	{
		// place in lower center
		int screenWide, screenTall;
		surface()->GetScreenSize( screenWide, screenTall );
		int wide,tall;
		GetSize( wide, tall );
		int x = 0;
		int y = 0;

		if ( ModInfo().IsSinglePlayerOnly() )
		{
			x = ( screenWide - wide ) * 0.50f;
			y = ( screenTall - tall ) * 0.86f;
		}
		else
		{
			x = ( screenWide - ( wide * 1.30f ) );
			y = ( ( screenTall * 0.875f ) );
		}

		SetPos( x, y );
	}
	else if ( m_bCenter )
	{
		MoveToCenterOfScreen();
	}
	else
	{
		// if we're not supposed to be centered, move ourselves to the lower right hand corner of the screen
		int x, y, screenWide, screenTall;
		surface()->GetWorkspaceBounds( x, y, screenWide, screenTall );
		int wide,tall;
		GetSize( wide, tall );

		if ( IsPC() )
		{
			x = screenWide - ( wide + 10 );
			y = screenTall - ( tall + 10 );
		}
		else
		{
			// Move farther in so we're title safe
			x = screenWide - wide - (screenWide * 0.05);
			y = screenTall - tall - (screenTall * 0.05);
		}

		x -= m_iAdditionalIndentX;
		y -= m_iAdditionalIndentY;

		SetPos( x, y );
	}
	
	BaseClass::PerformLayout();
	
	vgui::ipanel()->MoveToFront( GetVPanel() );
}

//-----------------------------------------------------------------------------
// Purpose: returns true if the number of ticks has changed
//-----------------------------------------------------------------------------
bool CLoadingDialog::SetProgressPoint( float fraction )
{
	if ( m_bConsoleStyle )
	{
		if ( fraction >= 0.99f )
		{
			// show the progress artifically completed to fill in 100%
			fraction = 1.0f;
		}
		fraction = clamp( fraction, 0.0f, 1.0f );
		if ( (int)(fraction * 25) != (int)(m_flProgressFraction * 25) )
		{
			m_flProgressFraction = fraction;
			return true;
		}
		return IsX360();
	}

	if ( !m_bShowingVACInfo && gameuifuncs->IsConnectedToVACSecureServer() )
	{
		SetupControlSettings( false );
	}

	int nOldDrawnSegments = m_pProgress->GetDrawnSegmentCount();
	m_pProgress->SetProgress( fraction );
	int nNewDrawSegments = m_pProgress->GetDrawnSegmentCount();
	return (nOldDrawnSegments != nNewDrawSegments) || IsX360();
}

//-----------------------------------------------------------------------------
// Purpose: sets and shows the secondary progress bar
//-----------------------------------------------------------------------------
void CLoadingDialog::SetSecondaryProgress( float progress )
{
	if ( m_bConsoleStyle )
		return;

	// don't show the progress if we've jumped right to completion
	if (!m_bShowingSecondaryProgress && progress > 0.99f)
		return;

	// if we haven't yet shown secondary progress then reconfigure the dialog
	if (!m_bShowingSecondaryProgress)
	{
		LoadControlSettings("Resource/LoadingDialogDualProgress.res");
		m_bShowingSecondaryProgress = true;
		m_pProgress2->SetVisible(true);
		m_flSecondaryProgressStartTime = (float)system()->GetFrameTime();
	}

	// if progress has increased then update the progress counters
	if (progress > m_flSecondaryProgress)
	{
		m_pProgress2->SetProgress(progress);
		m_flSecondaryProgress = progress;
		m_flLastSecondaryProgressUpdateTime = (float)system()->GetFrameTime();
	}

	// if progress has decreased then reset progress counters
	if (progress < m_flSecondaryProgress)
	{
		m_pProgress2->SetProgress(progress);
		m_flSecondaryProgress = progress;
		m_flLastSecondaryProgressUpdateTime = (float)system()->GetFrameTime();
		m_flSecondaryProgressStartTime = (float)system()->GetFrameTime();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLoadingDialog::SetSecondaryProgressText(const char *statusText)
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	SetControlString( "SecondaryProgressLabel", statusText );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLoadingDialog::OnClose()
{
	// remove any rendering restrictions
	HideOtherDialogs( false );

	BaseClass::OnClose();
}

//-----------------------------------------------------------------------------
// Purpose: command handler
//-----------------------------------------------------------------------------
void CLoadingDialog::OnCommand(const char *command)
{
	if ( !stricmp(command, "Cancel") )
	{
		// disconnect from the server
		engine->ClientCmd_Unrestricted("disconnect\n");

		// close
		Close();
	}
	else
	{
		BaseClass::OnCommand(command);
	}
}

void CLoadingDialog::OnKeyCodeTyped(KeyCode code)
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	if ( code == KEY_ESCAPE )
	{
		OnCommand("Cancel");
	}
	else
	{
		BaseClass::OnKeyCodeTyped(code);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Maps ESC to quiting loading
//-----------------------------------------------------------------------------
void CLoadingDialog::OnKeyCodePressed(KeyCode code)
{
	if ( m_bConsoleStyle )
	{
		return;
	}

	ButtonCode_t nButtonCode = GetBaseButtonCode( code );

	if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == KEY_XBUTTON_A || nButtonCode == STEAMCONTROLLER_A || nButtonCode == STEAMCONTROLLER_B )
	{
		OnCommand("Cancel");
	}
	else
	{
		BaseClass::OnKeyCodePressed(code);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Singleton accessor
//-----------------------------------------------------------------------------
extern vgui::DHANDLE<CLoadingDialog> g_hLoadingDialog;
CLoadingDialog *LoadingDialog()
{
	return g_hLoadingDialog.Get();
}