//========= Copyright Valve Corporation, All rights reserved. ============//
//
//=======================================================================================//

#include "cbase.h"

#if defined( REPLAY_ENABLED )

#include "replayrenderoverlay.h"
#include "vgui_controls/TextImage.h"
#include "replay/genericclassbased_replay.h"
#include "iclientmode.h"
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "ienginevgui.h"
#include "vgui/IVGui.h"
#include "econ/confirm_dialog.h"
#include "replay/ireplaymanager.h"
#include "replay/irecordingsessionmanager.h"
#include "replay/ireplaymoviemanager.h"
#include "replay/replayrenderer.h"
#include "econ/econ_controls.h"

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

//-----------------------------------------------------------------------------

extern IReplayMovieManager *g_pReplayMovieManager;

//-----------------------------------------------------------------------------

using namespace vgui;

//-----------------------------------------------------------------------------

#define TMP_ENCODED_AUDIO			".tmp.aac"
#ifdef USE_WEBM_FOR_REPLAY
#define TMP_ENCODED_VIDEO			".tmp.webm"
#else
#define TMP_ENCODED_VIDEO			".tmp.mov"
#endif

//-----------------------------------------------------------------------------

ConVar replay_enablerenderpreview( "replay_enablerenderpreview", "1", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Enable preview during replay render." );

//-----------------------------------------------------------------------------

void OnRenderCancelDialogButtonPressed( bool bConfirm, void *pContext )
{
	if ( bConfirm )
	{
		g_pReplayMovieManager->CancelRender();
	}
}

//-----------------------------------------------------------------------------

CReplayRenderOverlay::CReplayRenderOverlay( Panel *pParent ) 
:	BaseClass( pParent, "ReplayRenderOverlay" ),
	m_pBottom( NULL ),
	m_pCancelButton( NULL ),
	m_pTitleLabel( NULL ),
	m_pProgressLabel( NULL ),
	m_pFilenameLabel( NULL ),
	m_pRenderProgress( NULL ),
	m_pRenderer( NULL ),
	m_pPreviewCheckButton( NULL ),
	m_unNumFrames( 0 ),
	m_flStartTime( 0.0f ),
	m_flPreviousTimeLeft( 0.0f )
{
	if ( pParent == NULL )
	{
		vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
		SetScheme(scheme);
		SetProportional( true );
	}

	ivgui()->AddTickSignal( GetVPanel(), 10 );

	m_pRenderer = new CReplayRenderer( this );
}

CReplayRenderOverlay::~CReplayRenderOverlay()
{
	ivgui()->RemoveTickSignal( GetVPanel() );

	delete m_pRenderer;
}

void CReplayRenderOverlay::Show()
{
	// Setup panel
	SetVisible( true );
	SetMouseInputEnabled( true );
	SetKeyBoardInputEnabled( true );
	MakePopup( true );
	MoveToFront();
	TFModalStack()->PushModal( this );

	// Make sure game UI is hidden
	engine->ClientCmd_Unrestricted( "gameui_hide" );

	InvalidateLayout( false, true );
}

void CReplayRenderOverlay::Hide()
{
	SetVisible( false );
	TFModalStack()->PopModal( this );
	MarkForDeletion();
}

void CReplayRenderOverlay::ApplySchemeSettings( IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	// Load controls
	LoadControlSettings( "Resource/UI/replayrenderoverlay.res", "GAME" );

	// Layout bottom
	m_pBottom = dynamic_cast< EditablePanel * >( FindChildByName( "BottomPanel" ) );
	if ( !m_pBottom )
		return;

	// Find some controls
	m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) );
	m_pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) );
	m_pRenderProgress = dynamic_cast< ProgressBar * >( FindChildByName( "RenderProgress" ) );
	m_pCancelButton = dynamic_cast< CExButton * >( FindChildByName( "CancelButton" ) );
	m_pFilenameLabel = dynamic_cast< CExLabel * >( FindChildByName( "FilenameLabel" ) );
	m_pPreviewCheckButton = dynamic_cast< CheckButton * >( FindChildByName( "PreviewCheckButton" ) );

	m_pPreviewCheckButton->SetProportional( false );
	m_pPreviewCheckButton->SetSelected( replay_enablerenderpreview.GetBool() );
	m_pPreviewCheckButton->AddActionSignalTarget( this );

	const char *pMovieFilename = m_pRenderer->GetMovieFilename();
	if ( m_pFilenameLabel && pMovieFilename )
	{
		const char *pFilename = V_UnqualifiedFileName( pMovieFilename );
		m_pFilenameLabel->SetText( pFilename );
	}
}

void CReplayRenderOverlay::PerformLayout()
{
	BaseClass::PerformLayout();

	if ( !m_pBottom )
		return;

	int sw, sh;
	vgui::surface()->GetScreenSize( sw, sh );
	SetBounds( 0, 0, sw, sh );

	int nBottomPanelHeight = sh * .13f;
	int nBottomPanelStartY = sh - nBottomPanelHeight;
	m_pBottom->SetBounds( 0, nBottomPanelStartY, sw, nBottomPanelHeight );

	int nBottomW = sw;
	int nBottomH = nBottomPanelHeight;

	// Setup progress bar
	if ( !m_pRenderProgress )
		return;

	int nProgHeight = YRES(20);
	int nMargin = nBottomW/5;
	int nProgX = nMargin;
	int nProgY = nBottomPanelStartY + ( nBottomH - nProgHeight ) / 2;
	int nProgW = nBottomW - 2*nMargin;

	// Only show progress bar if replay is valid and length of render is non-zero, and the record start tick exists
	CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
	if ( pReplay )
	{
		const float flTotalTime = pReplay->m_flLength;
		const int nServerRecordStartTick = g_pClientReplayContext->GetRecordingSessionManager()->GetServerStartTickForSession( pReplay->m_hSession );	// NOTE: Returns -1 on fail
		if ( flTotalTime > 0.0f && nServerRecordStartTick >= 0 )
		{
			m_pRenderProgress->SetVisible( true );
			m_pRenderProgress->SetBounds( nProgX, nProgY, nProgW, nProgHeight );
			m_pRenderProgress->SetSegmentInfo( XRES(1), XRES(8) );
		}
	}

	// Layout title label
	const int nTitleLabelY = nBottomPanelStartY + ( m_pBottom->GetTall() - m_pTitleLabel->GetTall() ) / 2;
	if ( m_pTitleLabel )
	{
		m_pTitleLabel->SizeToContents();
		m_pTitleLabel->SetPos( ( nProgX - m_pTitleLabel->GetWide() ) / 2, nTitleLabelY );
	}

	// Layout preview check button
	if ( m_pPreviewCheckButton )
	{
		m_pPreviewCheckButton->SizeToContents();
		m_pPreviewCheckButton->SetPos( ( nProgX - m_pPreviewCheckButton->GetWide() ) / 2, nTitleLabelY + m_pTitleLabel->GetTall() + YRES(3) );
	}

	// Layout filename label
	if ( m_pFilenameLabel )
	{
		int nProgBottomY = nProgY + nProgHeight;
		m_pFilenameLabel->SizeToContents();
		m_pFilenameLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pFilenameLabel->GetTall() ) / 2 );
	}

	// Layout progress label
	if ( m_pProgressLabel )
	{
		int nProgBottomY = nProgY + nProgHeight;
		m_pProgressLabel->SizeToContents();
		m_pProgressLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pProgressLabel->GetTall() ) / 2 );
		m_pProgressLabel->SetWide( nProgW );
	}

	// Layout cancel button
	if ( !m_pCancelButton )
		return;

	// Put cancel button half way in between progress bar and screen right
	int nProgRightX = nProgX + nProgW;
	m_pCancelButton->SetPos(
		nProgRightX + ( m_pBottom->GetWide() - nProgRightX - m_pCancelButton->GetWide() ) / 2,
		nBottomPanelStartY + ( m_pBottom->GetTall() - m_pCancelButton->GetTall() ) / 2
	);

	SetXToRed( m_pCancelButton );

	m_pCancelButton->RequestFocus();
}

void CReplayRenderOverlay::OnTick()
{
#if _DEBUG
	if ( m_bReloadScheme )
	{
		InvalidateLayout( true, true );
		m_bReloadScheme = false;
	}
#endif

	// Update progress
	if ( m_pRenderProgress )
	{
		CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
		if ( pReplay && m_pRenderProgress->IsVisible() )
		{
			float flCurTime, flTotalTime;
			g_pClientReplayContext->GetPlaybackTimes( flCurTime, flTotalTime, pReplay, m_pRenderer->GetPerformance() );
			const float flProgress = ( flTotalTime == 0.0f ) ? 1.0f : ( flCurTime / flTotalTime );

			Assert( flTotalTime > 0.0f );	// NOTE: Progress bar will always be invisible if total time is 0, but check anyway to be safe.
			m_pRenderProgress->SetProgress( MAX( m_pRenderProgress->GetProgress(), flProgress ) ); // The MAX() here keeps the progress bar from thrashing

			if ( m_pProgressLabel )
			{
				// @note Tom Bui: this is a horribly ugly hack, but the first couple of frames take a really freaking long time, so that
				// really blows out the estimate
				float flTimePassed = 0.0f;
				++m_unNumFrames;				
				const uint32 kNumFramesToWait = 10;
				if ( m_unNumFrames < kNumFramesToWait )
				{
					m_flStartTime = gpGlobals->realtime;
				}
				else if ( m_unNumFrames > kNumFramesToWait )
				{
					flTimePassed = gpGlobals->realtime - m_flStartTime;
					float flEstimatedTimeLeft = flProgress > 0.0f ? ( flTimePassed / flProgress ) - flTimePassed : 0.0f;
					// exponential moving average FIR filter
					// S(t) =  smoothing_factor * Y(t) + (1 - smoothing_factor)* Y(t-1)
					// previous value is essentially 90% of the current value
					const float kSmoothingFactor = 0.1f;
					if ( m_flPreviousTimeLeft == 0.0f )
					{
						m_flPreviousTimeLeft = flEstimatedTimeLeft;
					}
					else
					{
						m_flPreviousTimeLeft = kSmoothingFactor * flEstimatedTimeLeft + ( 1 - kSmoothingFactor ) * m_flPreviousTimeLeft;
					}
				}
				
				wchar_t wszTimeLeft[256];
				wchar_t wszTime[256];
				{
					const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( m_flPreviousTimeLeft ) );
					g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTimeLeft, sizeof( wszTimeLeft ) );
				}
				{
					const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( flTimePassed ) );
					g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTime, sizeof( wszTime ) );
				}
				wchar_t wszText[256];
				g_pVGuiLocalize->ConstructString( wszText,sizeof( wszText ), g_pVGuiLocalize->Find( "#Replay_RenderOverlay_TimeLeft" ), 2, wszTime, wszTimeLeft );
				m_pProgressLabel->SetText( wszText );
			}
		}
	}
}

void CReplayRenderOverlay::OnMousePressed( MouseCode nCode )
{
#if _DEBUG
	m_bReloadScheme = true;
#endif
	BaseClass::OnMousePressed( nCode );
}

void CReplayRenderOverlay::OnKeyCodeTyped( vgui::KeyCode nCode )
{
	if ( nCode == KEY_ESCAPE )
	{
		if ( TFModalStack()->Top() == GetVPanel() )
		{
			OnCommand( "confirmcancel" );
			return;
		}
	}

	BaseClass::OnKeyCodeTyped( nCode );
}

void CReplayRenderOverlay::OnCommand( const char *pCommand )
{
	if ( !V_stricmp( pCommand, "confirmcancel" ) )
	{
		ShowConfirmDialog( "#Replay_CancelRenderTitle", "#Replay_ConfirmCancelRender", "#Replay_YesCancel", "#Replay_No", OnRenderCancelDialogButtonPressed, this, NULL, "replay\\replaydialog_warn.wav" );
		return;
	}

	BaseClass::OnCommand( pCommand );
}

void CReplayRenderOverlay::OnCheckButtonChecked( Panel *pPanel )
{
	replay_enablerenderpreview.SetValue( (int)m_pPreviewCheckButton->IsSelected() );
}

//-----------------------------------------------------------------------------

static CReplayRenderOverlay *s_pRenderOverlay = NULL;

void ReplayUI_OpenReplayRenderOverlay()
{
	if ( !g_pReplayMovieManager->IsRendering() )
		return;

	// Delete any existing panel
	if ( s_pRenderOverlay )
	{
		s_pRenderOverlay->MarkForDeletion();
	}

	// Create the panel - get the render resolution from the settings
	s_pRenderOverlay = SETUP_PANEL( new CReplayRenderOverlay( NULL ) );	// Parenting to NULL allows us to turn off world rendering in engine/view.cpp (V_RenderView())

	// Set the panel as the movie renderer, so it can receive begin/end render calls from the engine
	g_pClientReplayContext->SetMovieRenderer( s_pRenderOverlay->m_pRenderer );
}

void ReplayUI_HideRenderOverlay()
{
	if ( s_pRenderOverlay )
	{
		s_pRenderOverlay->MarkForDeletion();
		s_pRenderOverlay = NULL;
	}

	g_pClientReplayContext->SetMovieRenderer( NULL );
}

//-----------------------------------------------------------------------------

#endif
