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

#include "toolutils/miniviewport.h"
#include "tier1/utlstring.h"
#include "vgui/ISurface.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/MaterialSystemUtil.h"
#include "materialsystem/imesh.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/itexture.h"
#include "tier1/KeyValues.h"
#include "toolframework/ienginetool.h"
#include "toolutils/enginetools_int.h"
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "view_shared.h"
#include "texture_group_names.h"
#include "vgui_controls/PropertySheet.h"
#include "tier2/tier2.h"
#include <windows.h>	// for MultiByteToWideChar
#include "cdll_int.h"

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

class CMiniViewportEngineRenderArea;

using namespace vgui;

extern IMatSystemSurface *g_pMatSystemSurface;

#define DEFAULT_PREVIEW_WIDTH 1280

//-----------------------------------------------------------------------------
// Purpose: This is a "frame" which is used to position the engine
//-----------------------------------------------------------------------------
class CMiniViewportPropertyPage : public vgui::EditablePanel
{
	DECLARE_CLASS_SIMPLE( CMiniViewportPropertyPage, vgui::EditablePanel );

public:
	CMiniViewportPropertyPage( Panel *parent, const char *panelName );

	virtual Color GetBgColor();

	void	GetEngineBounds( int& x, int& y, int& w, int& h );

	void	RenderFrameBegin();

	CMiniViewportEngineRenderArea *GetViewportArea() { return m_pViewportArea; }

private:
	virtual void PerformLayout();

	Color	m_bgColor;

	CMiniViewportEngineRenderArea				*m_pViewportArea;
};

//-----------------------------------------------------------------------------
//
// the actual renderable area
//
//-----------------------------------------------------------------------------
class CMiniViewportEngineRenderArea : public vgui::EditablePanel
{
	DECLARE_CLASS_SIMPLE( CMiniViewportEngineRenderArea, vgui::EditablePanel );

public:
	CMiniViewportEngineRenderArea( Panel *parent, const char *panelName );
	~CMiniViewportEngineRenderArea();

	virtual void PaintBackground();
	virtual void GetEngineBounds( int& x, int& y, int& w, int& h );
	virtual void ApplySchemeSettings( IScheme *pScheme );

	void	RenderFrameBegin();
	void	SetOverlayText( const char *pText );

	// Called when the layoff texture needs to be released
	void ReleaseLayoffTexture();

protected:
	void			InitSceneMaterials();
	void			ShutdownSceneMaterials();

	// Paints the black borders around the engine window
	void PaintEngineBorders( int x, int y, int w, int h );

	// Paints the engine window	itself
	void PaintEngineWindow( int x, int y, int w, int h );

	// Paints the overlay text
	void PaintOverlayText( );

	int m_nEngineOutputTexture;
	vgui::HFont	m_OverlayTextFont;
	CUtlString m_OverlayText;

	CTextureReference			m_ScreenBuffer;	
	CMaterialReference			m_ScreenMaterial;
};


CMiniViewportEngineRenderArea::CMiniViewportEngineRenderArea( Panel *parent, const char *panelName )
	: BaseClass( parent, panelName )
{
	SetPaintEnabled( false );
	SetPaintBorderEnabled( false );
	SetPaintBackgroundEnabled( true );

	m_nEngineOutputTexture = vgui::surface()->CreateNewTextureID();
}

CMiniViewportEngineRenderArea::~CMiniViewportEngineRenderArea()
{
	ShutdownSceneMaterials();
}

void CMiniViewportEngineRenderArea::RenderFrameBegin()
{
	if ( !enginetools->IsInGame() )
		return;

	InitSceneMaterials();

	CViewSetup playerViewSetup;
	int x, y, w, h;
	GetEngineBounds( x, y, w, h );
	enginetools->GetPlayerView( playerViewSetup, 0, 0, w, h );

	// NOTE: This is a workaround to a nasty problem. Vgui uses stencil
	// to determing if the panels should occlude each other. The engine
	// has now started to use stencil for various random effects.
	// To prevent these different stencil uses from clashing, we will
	// render the engine prior to vgui painting + cache the result off in
	// 
	// Make the engine draw the scene
	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	pRenderContext->PushRenderTargetAndViewport( m_ScreenBuffer, 0, 0, w, h );

	// Tell the engine to tell the client to render the view (sans viewmodel)
	enginetools->SetMainView( playerViewSetup.origin, playerViewSetup.angles );
	enginetools->RenderView( playerViewSetup, VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH, RENDERVIEW_DRAWHUD | RENDERVIEW_DRAWVIEWMODEL );

	// Pop the target
	pRenderContext->PopRenderTargetAndViewport();
}

void CMiniViewportEngineRenderArea::InitSceneMaterials()
{
	if ( m_ScreenBuffer )
		return;

	if ( g_pMaterialSystem->IsTextureLoaded( "_rt_LayoffResult"	) ) 
	{
		ITexture *pTexture = g_pMaterialSystem->FindTexture( "_rt_LayoffResult", TEXTURE_GROUP_RENDER_TARGET );
		m_ScreenBuffer.Init( pTexture );
	}
	else
	{
		// For now, layoff dimensions match aspect of back buffer
		int nBackBufferWidth, nBackBufferHeight;
		g_pMaterialSystem->GetBackBufferDimensions( nBackBufferWidth, nBackBufferHeight );
		float flAspect = nBackBufferWidth / (float)nBackBufferHeight;
		int nPreviewWidth = min( DEFAULT_PREVIEW_WIDTH, nBackBufferWidth );
		int nPreviewHeight = ( int )( nPreviewWidth / flAspect + 0.5f );

		g_pMaterialSystem->BeginRenderTargetAllocation();								// Begin allocating RTs which IFM can scribble into

		// LDR final result of either HDR or LDR rendering
		m_ScreenBuffer.Init( g_pMaterialSystem->CreateNamedRenderTargetTextureEx2(
			"_rt_LayoffResult", nPreviewWidth, nPreviewHeight, RT_SIZE_OFFSCREEN,
			g_pMaterialSystem->GetBackBufferFormat(), MATERIAL_RT_DEPTH_SHARED, TEXTUREFLAGS_BORDER ) );

		g_pMaterialSystem->EndRenderTargetAllocation();									// End allocating RTs which IFM can scribble into
	}

	KeyValues *pVMTKeyValues = NULL;
	pVMTKeyValues= new KeyValues( "UnlitGeneric" );
	pVMTKeyValues->SetString( "$basetexture", m_ScreenBuffer->GetName() );
	pVMTKeyValues->SetInt( "$nofog", 1 );
	m_ScreenMaterial.Init( "MiniViewportEngineRenderAreaSceneMaterial", pVMTKeyValues );
	m_ScreenMaterial->Refresh();
}


//-----------------------------------------------------------------------------
// Called when the layoff texture needs to be released
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::ReleaseLayoffTexture()
{
	m_ScreenBuffer.Shutdown();
	m_ScreenMaterial.Shutdown();
}


//-----------------------------------------------------------------------------
// Apply scheme settings
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::ApplySchemeSettings( IScheme *pScheme )
{   
	BaseClass::ApplySchemeSettings( pScheme );
	m_OverlayTextFont = pScheme->GetFont( "DefaultLargeOutline" );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::ShutdownSceneMaterials()
{
	m_ScreenBuffer.Shutdown();
	m_ScreenMaterial.Shutdown();
}


//-----------------------------------------------------------------------------
// Sets text to draw over the window
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::SetOverlayText( const char *pText )
{
	m_OverlayText = pText;
}

	
//-----------------------------------------------------------------------------
// Paints the black borders around the engine window
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::PaintEngineBorders( int x, int y, int w, int h )
{
	// Draws black borders around the engine window
	surface()->DrawSetColor( Color( 0, 0, 0, 255 ) );
	if ( x != 0 )
	{
		surface()->DrawFilledRect( 0, 0, x, h );
		surface()->DrawFilledRect( x + w, 0, w + 2 * x, h );
	}
	else if ( y != 0 )
	{
		surface()->DrawFilledRect( 0, 0, w, y );
		surface()->DrawFilledRect( 0, y + h, w, h + 2 * y );
	}
}


//-----------------------------------------------------------------------------
// Paints the overlay text
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::PaintOverlayText( )
{
	if ( !m_OverlayText.Length() )
		return;

	int cw, ch;
	GetSize( cw, ch );

	int nTextWidth, nTextHeight;
	int nBufLen = m_OverlayText.Length()+1;
	wchar_t *pTemp = (wchar_t*)_alloca( nBufLen * sizeof(wchar_t) );
	::MultiByteToWideChar( CP_UTF8, 0, m_OverlayText.Get(), -1, pTemp, nBufLen );

	g_pMatSystemSurface->GetTextSize( m_OverlayTextFont, pTemp, nTextWidth, nTextHeight );
	int lx = (cw - nTextWidth) / 2;
	if ( lx < 10 )
	{
		lx = 10;
	}
	int ly = ch - 10 - nTextHeight;
	g_pMatSystemSurface->DrawColoredTextRect( m_OverlayTextFont, 
		lx, ly, cw - lx, ch - ly,
		255, 255, 255, 255, "%s", m_OverlayText.Get() );
}


//-----------------------------------------------------------------------------
// Paints the engine window	itself
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::PaintEngineWindow( int x, int y, int w, int h )
{
	if ( !enginetools->IsInGame() )
	{
		surface()->DrawSetColor( Color( 127, 127, 200, 63 ) );
		surface()->DrawFilledRect( x, y, x + w, y + h );
	}
	else
	{
		CMatRenderContextPtr pRenderContext( g_pMaterialSystem );

		g_pMatSystemSurface->DrawSetTextureMaterial( m_nEngineOutputTexture, m_ScreenMaterial );
		surface()->DrawSetColor( Color( 0, 0, 0, 255 ) );

		int nTexWidth = m_ScreenBuffer->GetActualWidth();
		int nTexHeight = m_ScreenBuffer->GetActualHeight();
		float flOOWidth = 1.0f / nTexWidth;
		float flOOHeight = 1.0f / nTexHeight;

		float s0, s1, t0, t1;

		s0 = ( 0.5f ) * flOOWidth;
		t0 = ( 0.5f ) * flOOHeight;
		s1 = ( (float)w - 0.5f ) * flOOWidth;
		t1 = ( (float)h - 0.5f ) * flOOHeight;

		vgui::surface()->DrawTexturedSubRect( x, y, x+w, y+h, s0, t0, s1, t1 );

		PaintOverlayText();
	}
}

//-----------------------------------------------------------------------------
// Paints the background
//-----------------------------------------------------------------------------
void CMiniViewportEngineRenderArea::PaintBackground()
{
	int x, y, w, h;
	GetEngineBounds( x, y, w, h );
	PaintEngineBorders( x, y, w, h );
	PaintEngineWindow( x, y, w, h );
}

void CMiniViewportEngineRenderArea::GetEngineBounds( int& x, int& y, int& w, int& h )
{
	x = 0;
	y = 0;
	GetSize( w, h );

	// Check aspect ratio
	int sx, sy;
	surface()->GetScreenSize( sx, sy );

	if ( sy > 0 && 
		h > 0 )
	{
		float screenaspect = (float)sx / (float)sy;
		float aspect = (float)w / (float)h;

		float ratio = screenaspect / aspect;

		// Screen is wider, need bars at top and bottom
		if ( ratio > 1.0f )
		{
			int usetall = (float)w / screenaspect;
			y = ( h - usetall ) / 2;
			h = usetall;
		}
		// Screen is narrower, need bars at left/right
		else
		{
			int usewide = (float)h * screenaspect;
			x = ( w - usewide ) / 2;
			w = usewide;
		}
	}
}

CMiniViewportPropertyPage::CMiniViewportPropertyPage(Panel *parent, const char *panelName ) :
	BaseClass( parent, panelName )
{
	m_bgColor = Color( 0, 0, 0, 0 );

	m_pViewportArea = new CMiniViewportEngineRenderArea( this, "Engine" );
}

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

	int w, h;
	GetSize( w, h );
	m_pViewportArea->SetBounds( 0, 0, w, h );
}

Color CMiniViewportPropertyPage::GetBgColor()
{
	return m_bgColor;
}


void CMiniViewportPropertyPage::GetEngineBounds( int& x, int& y, int& w, int& h )
{
	m_pViewportArea->GetEngineBounds( x, y, w, h );
	m_pViewportArea->LocalToScreen( x, y );
}

void CMiniViewportPropertyPage::RenderFrameBegin()
{
	m_pViewportArea->RenderFrameBegin();
}

CMiniViewport::CMiniViewport( vgui::Panel *parent, bool contextLabel, vgui::IToolWindowFactory *factory /*= 0*/, 
	vgui::Panel *page /*= NULL*/, char const *title /*= NULL*/, bool contextMenu /*= false*/ ) :
	BaseClass( parent, contextLabel, factory, page, title, contextMenu, false )
{
	SetCloseButtonVisible( false );

	GetPropertySheet()->SetDraggableTabs( false );

	// Add the viewport panel
	m_hPage = new CMiniViewportPropertyPage( this, "ViewportPage" );

	AddPage( m_hPage.Get(), "#ToolMiniViewport", false );
}

void CMiniViewport::GetViewport( bool& enabled, int& x, int& y, int& w, int& h )
{
	enabled = false;
	x = y = w = h = 0;

	int screenw, screenh;
	surface()->GetScreenSize( screenw, screenh );

	m_hPage->GetEngineBounds( x, y, w, h );

	y = screenh - ( y + h );
}

void CMiniViewport::GetEngineBounds( int& x, int& y, int& w, int& h )
{
	m_hPage->GetEngineBounds( x, y, w, h );
}


//-----------------------------------------------------------------------------
// Called when the layoff texture needs to be released
//-----------------------------------------------------------------------------
void CMiniViewport::ReleaseLayoffTexture()
{
	if ( m_hPage.Get() )
	{
		m_hPage->GetViewportArea()->ReleaseLayoffTexture();
	}
}


//-----------------------------------------------------------------------------
// Sets text to draw over the window
//-----------------------------------------------------------------------------
void CMiniViewport::SetOverlayText( const char *pText )
{
	if ( m_hPage.Get() )
	{
		m_hPage->GetViewportArea()->SetOverlayText( pText );
	}
}

void CMiniViewport::RenderFrameBegin()
{
	if ( m_hPage.Get() )
	{
		m_hPage->RenderFrameBegin();
	}
}