//============ Copyright (c) Valve Corporation, All rights reserved. ============
//
// Functionality to render a glowing outline around client renderable objects.
//
//===============================================================================

#ifndef GLOW_OUTLINE_EFFECT_H
#define GLOW_OUTLINE_EFFECT_H

#if defined( COMPILER_MSVC )
#pragma once
#endif

#include "utlvector.h"
#include "vector.h"
#include "interfaces.hpp"

class C_BaseEntity;
class CViewSetup;
class CMatRenderContextPtr;

static const int GLOW_FOR_ALL_SPLIT_SCREEN_SLOTS = -1;

enum GlowRenderStyle_t
{
	GLOWRENDERSTYLE_DEFAULT = 0,
	GLOWRENDERSTYLE_RIMGLOW3D,
	GLOWRENDERSTYLE_EDGE_HIGHLIGHT,
	GLOWRENDERSTYLE_EDGE_HIGHLIGHT_PULSE,
	GLOWRENDERSTYLE_COUNT,
};

class CGlowObjectManager
{
public:
	CGlowObjectManager() :
	  m_nFirstFreeSlot( GlowObjectDefinition_t::END_OF_FREE_LIST )
	  {
	  }

	  int RegisterGlowObject( C_BaseEntity *pEntity, const Vector &vGlowColor, float flGlowAlpha, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded, int nSplitScreenSlot )
	  {
		  int nIndex;
		  if ( m_nFirstFreeSlot == GlowObjectDefinition_t::END_OF_FREE_LIST )
		  {
			  nIndex = m_GlowObjectDefinitions.AddToTail();
		  }
		  else
		  {
			  nIndex = m_nFirstFreeSlot;
			  m_nFirstFreeSlot = m_GlowObjectDefinitions[nIndex].m_nNextFreeSlot;
		  }

		  m_GlowObjectDefinitions[nIndex].m_flGlowPulseOverdrive = 0.0f;
		  m_GlowObjectDefinitions[nIndex].m_bGlowAlphaCappedByRenderAlpha = false;
		  m_GlowObjectDefinitions[nIndex].m_flGlowAlphaFunctionOfMaxVelocity = 0.0f;
		  m_GlowObjectDefinitions[nIndex].m_flGlowAlphaMax = 1.0f;
		  m_GlowObjectDefinitions[nIndex].m_pEntity = pEntity;
		  m_GlowObjectDefinitions[nIndex].m_vGlowColor = vGlowColor;
		  m_GlowObjectDefinitions[nIndex].m_flGlowAlpha = flGlowAlpha;
		  m_GlowObjectDefinitions[nIndex].m_bRenderWhenOccluded = bRenderWhenOccluded;
		  m_GlowObjectDefinitions[nIndex].m_bRenderWhenUnoccluded = bRenderWhenUnoccluded;
		  m_GlowObjectDefinitions[nIndex].m_bFullBloomRender = false;
		  m_GlowObjectDefinitions[nIndex].m_nFullBloomStencilTestValue = 0;
		  m_GlowObjectDefinitions[nIndex].m_nSplitScreenSlot = nSplitScreenSlot;
		  m_GlowObjectDefinitions[nIndex].m_nNextFreeSlot = GlowObjectDefinition_t::ENTRY_IN_USE;
		  m_GlowObjectDefinitions[nIndex].m_nRenderStyle = GLOWRENDERSTYLE_DEFAULT;

		  return nIndex;
	  }

	  int AddGlowBox( Vector vecOrigin, QAngle angOrientation, Vector mins, Vector maxs, Color colColor, float flLifetime )
	  {
		  return 0; /*
		  int nIndex = m_GlowBoxDefinitions.AddToTail();
		  m_GlowBoxDefinitions[nIndex].m_vPosition = vecOrigin;
		  m_GlowBoxDefinitions[nIndex].m_angOrientation = angOrientation;
		  m_GlowBoxDefinitions[nIndex].m_vMins = mins;
		  m_GlowBoxDefinitions[nIndex].m_vMaxs = maxs;
		  m_GlowBoxDefinitions[nIndex].m_colColor = colColor;
		  m_GlowBoxDefinitions[nIndex].m_flBirthTimeIndex = iff.g_pGlobals->curtime;
		  m_GlowBoxDefinitions[nIndex].m_flTerminationTimeIndex = iff.g_pGlobals->curtime + flLifetime;
		  return nIndex;
		  */
	  }

	  void UnregisterGlowObject( int nGlowObjectHandle )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );

		  m_GlowObjectDefinitions[nGlowObjectHandle].m_nNextFreeSlot = m_nFirstFreeSlot;
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_pEntity = NULL;
		  m_nFirstFreeSlot = nGlowObjectHandle;
	  }

	  void UnregisterGlowObject( C_BaseEntity *pEntity )
	  {
		  for ( int i=0; i<m_GlowObjectDefinitions.Count(); i++ )
		  {
			  if ( m_GlowObjectDefinitions[i].m_pEntity == pEntity )
			  {
				  UnregisterGlowObject(i);
				  return;
			  }
		  }
	  }

	  void SetGlowAlphaPulseOverdrive( int nGlowObjectHandle, float flAdditionalAlpha )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_flGlowPulseOverdrive = flAdditionalAlpha;
	  }

	  void UnregisterAllGlowBoxes()
	  {
		  m_GlowBoxDefinitions.RemoveAll();
	  }

	  void SetGlowAlphaCappedByRenderAlpha( int nGlowObjectHandle, bool bGlowAlphaCappedByRenderAlpha )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_bGlowAlphaCappedByRenderAlpha = bGlowAlphaCappedByRenderAlpha;
	  }

	  void SetGlowAlphaFunctionOfMaxVelocity( int nGlowObjectHandle, float flMaxVelocity )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_flGlowAlphaFunctionOfMaxVelocity = flMaxVelocity;
	  }

	  void SetGlowAlphaMax( int nGlowObjectHandle, float flGlowAlphaMax )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_flGlowAlphaMax = flGlowAlphaMax;
	  }

	  void SetEntity( int nGlowObjectHandle, C_BaseEntity *pEntity )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_pEntity = pEntity;
	  }

	  void SetColor( int nGlowObjectHandle, const Vector &vGlowColor ) 
	  { 
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_vGlowColor = vGlowColor;
	  }

	  void SetAlpha( int nGlowObjectHandle, float flAlpha ) 
	  { 
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_flGlowAlpha = flAlpha;
	  }

	  void SetRenderFlags( int nGlowObjectHandle, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_bRenderWhenOccluded = bRenderWhenOccluded;
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_bRenderWhenUnoccluded = bRenderWhenUnoccluded;
	  }

	  void SetRenderStyle( int nGlowObjectHandle, GlowRenderStyle_t nNewRenderStyle )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  Assert( nNewRenderStyle >= GLOWRENDERSTYLE_DEFAULT && nNewRenderStyle < GLOWRENDERSTYLE_COUNT );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_nRenderStyle = nNewRenderStyle;
	  }

	  void SetFullBloomRender( int nGlowObjectHandle, bool bFullBloomRender, int nStencilTestValue )
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_bFullBloomRender = bFullBloomRender;
		  m_GlowObjectDefinitions[nGlowObjectHandle].m_nFullBloomStencilTestValue = nStencilTestValue;
	  }

	  bool IsRenderingWhenOccluded( int nGlowObjectHandle ) const
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  return m_GlowObjectDefinitions[nGlowObjectHandle].m_bRenderWhenOccluded;
	  }

	  bool IsRenderingWhenUnoccluded( int nGlowObjectHandle ) const
	  {
		  Assert( !m_GlowObjectDefinitions[nGlowObjectHandle].IsUnused() );
		  return m_GlowObjectDefinitions[nGlowObjectHandle].m_bRenderWhenUnoccluded;
	  }

	  void RenderGlowEffects( const CViewSetup *pSetup, int nSplitScreenSlot );

//private:

	
	void ApplyEntityGlowEffects( const CViewSetup *pSetup, int nSplitScreenSlot, CMatRenderContextPtr &pRenderContext, float flBloomScale, int x, int y, int w, int h );

	void RenderGlowBoxes( int iPass, CMatRenderContextPtr &pRenderContext );

	struct GlowObjectDefinition_t
	{
		bool ShouldDraw( int nSlot ) const { 
			return 0;// m_pEntity && (m_nSplitScreenSlot == GLOW_FOR_ALL_SPLIT_SCREEN_SLOTS || m_nSplitScreenSlot == nSlot) && (m_bRenderWhenOccluded || m_bRenderWhenUnoccluded) && m_pEntity->ShouldDraw();
		}
		bool IsUnused() const { 
			return m_nNextFreeSlot != GlowObjectDefinition_t::ENTRY_IN_USE; 
		}
		void DrawModel();


		// Linked list of free slots
		int m_nNextFreeSlot;

		C_BaseEntity* m_pEntity;
		Vector m_vGlowColor;
		float m_flGlowAlpha;

		bool m_bGlowAlphaCappedByRenderAlpha;
		float m_flGlowAlphaFunctionOfMaxVelocity;
		float m_flGlowAlphaMax;
		float m_flGlowPulseOverdrive;
		bool m_bRenderWhenOccluded;
		bool m_bRenderWhenUnoccluded;
		bool m_bFullBloomRender;
		int m_nFullBloomStencilTestValue; // only render full bloom objects if stencil is equal to this value (value of -1 implies no stencil test)
		int m_nRenderStyle;
		int m_nSplitScreenSlot;


		// Special values for GlowObjectDefinition_t::m_nNextFreeSlot
		static const int END_OF_FREE_LIST = -1;
		static const int ENTRY_IN_USE = -2;
	};

	void RenderGlowModels( const CViewSetup *pSetup, int nSplitScreenSlot, CMatRenderContextPtr &pRenderContext, CUtlVector<GlowObjectDefinition_t> &vecGlowObjects );

	void DownSampleAndBlurRT( const CViewSetup *pSetup, CMatRenderContextPtr &pRenderContext, float flBloomScale, ITexture *pRtFullFrame, ITexture *pRtQuarterSize0, ITexture *pRtQuarterSize1 );

	CUtlVector< GlowObjectDefinition_t > m_GlowObjectDefinitions;
	int m_nFirstFreeSlot;

	struct GlowBoxDefinition_t
	{
		Vector m_vPosition;
		QAngle m_angOrientation;
		Vector m_vMins;
		Vector m_vMaxs;
		float m_flBirthTimeIndex;
		float m_flTerminationTimeIndex; //when to die
		Color m_colColor;
	};

	CUtlVector< GlowBoxDefinition_t > m_GlowBoxDefinitions;

};

CGlowObjectManager &GlowObjectManager();

class CGlowObject
{
public:
	CGlowObject( C_BaseEntity *pEntity, const Vector &vGlowColor = Vector( 1.0f, 1.0f, 1.0f ), float flGlowAlpha = 1.0f, bool bRenderWhenOccluded = false, bool bRenderWhenUnoccluded = false, int nSplitScreenSlot = GLOW_FOR_ALL_SPLIT_SCREEN_SLOTS )
	{
		m_nGlowObjectHandle = GlowObjectManager().RegisterGlowObject( pEntity, vGlowColor, flGlowAlpha, bRenderWhenOccluded, bRenderWhenUnoccluded, nSplitScreenSlot );
	}

	~CGlowObject()
	{
		GlowObjectManager().UnregisterGlowObject( m_nGlowObjectHandle );
	}

	void SetGlowAlphaPulseOverdrive( float flAdditionalAlpha )
	{
		//this value is added to existing glow alpha, and fades away in a few frames
		GlowObjectManager().SetGlowAlphaPulseOverdrive( m_nGlowObjectHandle, flAdditionalAlpha );
	}

	void SetGlowAlphaCappedByRenderAlpha( bool bGlowAlphaCappedByRenderAlpha )
	{
		GlowObjectManager().SetGlowAlphaCappedByRenderAlpha( m_nGlowObjectHandle, bGlowAlphaCappedByRenderAlpha );
	}

	void SetGlowAlphaFunctionOfMaxVelocity( float flMaxVelocity )
	{
		GlowObjectManager().SetGlowAlphaFunctionOfMaxVelocity( m_nGlowObjectHandle, flMaxVelocity );
	}

	void SetGlowAlphaMax( float flGlowAlphaMax )
	{
		GlowObjectManager().SetGlowAlphaMax( m_nGlowObjectHandle, flGlowAlphaMax );
	}

	void SetEntity( C_BaseEntity *pEntity )
	{
		GlowObjectManager().SetEntity( m_nGlowObjectHandle, pEntity );
	}

	void SetColor( const Vector &vGlowColor )
	{
		GlowObjectManager().SetColor( m_nGlowObjectHandle, vGlowColor );
	}

	void SetAlpha( float flAlpha )
	{
		GlowObjectManager().SetAlpha( m_nGlowObjectHandle, flAlpha );
	}

	void SetRenderFlags( bool bRenderWhenOccluded, bool bRenderWhenUnoccluded )
	{
		GlowObjectManager().SetRenderFlags( m_nGlowObjectHandle, bRenderWhenOccluded, bRenderWhenUnoccluded );
	}

	void SetRenderStyle( GlowRenderStyle_t nNewRenderStyle )
	{
		GlowObjectManager().SetRenderStyle( m_nGlowObjectHandle, nNewRenderStyle );
	}

	void SetFullBloomRender( bool bFullBloomRender, int nStencilTestValue = -1 )
	{
		return GlowObjectManager().SetFullBloomRender( m_nGlowObjectHandle, bFullBloomRender, nStencilTestValue );
	}

	bool IsRenderingWhenOccluded() const
	{
		return GlowObjectManager().IsRenderingWhenOccluded( m_nGlowObjectHandle );
	}

	bool IsRenderingWhenUnoccluded() const
	{
		return GlowObjectManager().IsRenderingWhenUnoccluded( m_nGlowObjectHandle );
	}

	bool IsRendering() const
	{
		return IsRenderingWhenOccluded() || IsRenderingWhenUnoccluded();
	}

	// Add more accessors/mutators here as needed

private:
	int m_nGlowObjectHandle;

	// Assignment & copy-construction disallowed
	CGlowObject( const CGlowObject &other );
	CGlowObject& operator=( const CGlowObject &other );
};

#endif // GLOW_OUTLINE_EFFECT_H