//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: A panel "metaclass" is a name given to a particular type of 
// panel with particular instance data. Such panels tend to be dynamically
// added and removed from their parent panels.
//
// $Workfile:     $
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "panelmetaclassmgr.h"
#include <KeyValues.h>
#include <vgui_controls/Panel.h>
#include "utldict.h"
#include "filesystem.h"
#include <KeyValues.h>

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

//-----------------------------------------------------------------------------
// Helper KeyValue parsing methods
//-----------------------------------------------------------------------------
bool ParseRGBA( KeyValues *pValues, const char* pFieldName, int& r, int& g, int& b, int& a )
{
	r = g = b = a = 255;
	const char *pColorString = pValues->GetString( pFieldName, "255 255 255 255" );
	if ( !pColorString || !pColorString[ 0 ] )
		return false;

	// Try and scan them in
	int scanned;
	scanned = sscanf( pColorString, "%i %i %i %i", &r, &g, &b, &a );
	if ( scanned != 4 )
	{
		Warning( "Couldn't scan four color values from %s\n", pColorString );
		return false;
	}

	return true;
}

bool ParseRGBA( KeyValues* pValues, const char* pFieldName, Color& c )
{
	int r, g, b, a;
	if (!ParseRGBA( pValues, pFieldName, r, g, b, a ))
		return false;

	c.SetColor( r, g, b, a );
	return true;
}

//-----------------------------------------------------------------------------
// FIXME: Why do we have vgui::KeyValues too!?!??! Bleah
/*-----------------------------------------------------------------------------
bool ParseRGBA( KeyValues *pValues, const char* pFieldName, int& r, int& g, int& b, int& a )
{
	r = g = b = a = 255;
	const char *pColorString = pValues->GetString( pFieldName, "255 255 255 255" );
	if ( !pColorString || !pColorString[ 0 ] )
		return false;

	// Try and scan them in
	int scanned;
	scanned = sscanf( pColorString, "%i %i %i %i", &r, &g, &b, &a );
	if ( scanned != 4 )
	{
		Warning( "Couldn't scan four color values from %s\n", pColorString );
		return false;
	}

	return true;
}

bool ParseRGBA( KeyValues* pValues, const char* pFieldName, Color& c )
{
	int r, g, b, a;
	if (!ParseRGBA( pValues, pFieldName, r, g, b, a ))
		return false;

	c.SetColor( r, g, b, a );
	return true;
} */


bool ParseCoord( KeyValues *pValues, const char* pFieldName, int& x, int& y )
{
	x = y = 0;
	const char *pCoordString = pValues->GetString( pFieldName, "0 0" );
	if ( !pCoordString || !pCoordString[ 0 ] )
		return false;

	// Try and scan them in
	int scanned;
	scanned = sscanf( pCoordString, "%i %i", &x, &y );
	if ( scanned != 2 )
	{
		Warning( "Couldn't scan 2d coordinate values from %s\n", pCoordString );
		return false;
	}

	// coords are within 640x480 screen space
	x = ( x * ( ( float )ScreenWidth() / 640.0 ) );
	y = ( y * ( ( float )ScreenHeight() / 480.0 ) );

	return true;
}

bool ParseRect( KeyValues *pValues, const char* pFieldName, int& x, int& y, int& w, int& h )
{
	x = y = w = h = 0;
	const char *pRectString = pValues->GetString( pFieldName, "0 0 0 0" );
	if ( !pRectString || !pRectString[ 0 ] )
		return false;

	// Try and scan them in
	int scanned;
	scanned = sscanf( pRectString, "%i %i %i %i", &x, &y, &w, &h );
	if ( scanned != 4 )
	{
		Warning( "Couldn't scan rectangle values from %s\n", pRectString );
		return false;
	}

	// coords are within 640x480 screen space
	x = ( x * ( ( float )ScreenWidth() / 640.0 ) );
	y = ( y * ( ( float )ScreenHeight() / 480.0 ) );
	w = ( w * ( ( float )ScreenWidth() / 640.0 ) );
	h = ( h * ( ( float )ScreenHeight() / 480.0 ) );

	return true;
}


//-----------------------------------------------------------------------------
// Helper class to make meta class panels (for use in entities, so they autocleanup)
//-----------------------------------------------------------------------------
CPanelWrapper::CPanelWrapper() : m_pPanel(NULL)
{
}

CPanelWrapper::~CPanelWrapper()
{
	Deactivate();
}

void CPanelWrapper::Activate( char const* pMetaClassName, vgui::Panel *pParent, int sortorder, void *pVoidInitData )
{
	if ( m_pPanel )
	{
		Deactivate();
	}

	m_pPanel = PanelMetaClassMgr()->CreatePanelMetaClass( pMetaClassName, sortorder, pVoidInitData, pParent );
}

void CPanelWrapper::Deactivate( void )
{
	if ( m_pPanel )
	{
		PanelMetaClassMgr()->DestroyPanelMetaClass( m_pPanel );
		m_pPanel = NULL;
	}
}

vgui::Panel *CPanelWrapper::GetPanel( )
{
	return m_pPanel;
}


//-----------------------------------------------------------------------------
// Purpose: Singleton class responsible for managing metaclass panels 
//-----------------------------------------------------------------------------
class CPanelMetaClassMgrImp : public IPanelMetaClassMgr
{
public:
	// constructor, destructor
	CPanelMetaClassMgrImp();
	virtual ~CPanelMetaClassMgrImp();

	// Members of IPanelMetaClassMgr
	virtual void LoadMetaClassDefinitionFile( const char* pLevelName );
	virtual void InstallPanelType( const char* pPanelName, IPanelFactory* pFactory );
	virtual vgui::Panel *CreatePanelMetaClass( const char* pMetaClassName,
		int sortorder, void *pInitData, vgui::Panel *pParent, const char *pChainName );
	virtual void DestroyPanelMetaClass( vgui::Panel *pPanel );

private:
	struct MetaClassDict_t
	{
		unsigned short	m_KeyValueIndex;
		unsigned short	m_TypeIndex;
		KeyValues*		m_pKeyValues;
	};

	// various parsing helper methods
	bool ParseSingleMetaClass( const char* pFileName, const char* pInstanceName,
		KeyValues* pMetaClass, int keyValueIndex );
	bool ParseMetaClassList( const char* pFileName, KeyValues* pKeyValues, int keyValueIndex );

	// No copy constructor
	CPanelMetaClassMgrImp( const CPanelMetaClassMgrImp & );

	// List of panel types...
	CUtlDict< IPanelFactory*, unsigned short > m_PanelTypeDict;

	// List of metaclass types
	CUtlDict< MetaClassDict_t, unsigned short > m_MetaClassDict;

	// Create key value accesor
	CUtlDict< KeyValues*, unsigned short >	m_MetaClassKeyValues;
};


//-----------------------------------------------------------------------------
// Returns the singleton panel metaclass mgr interface
//-----------------------------------------------------------------------------
IPanelMetaClassMgr* PanelMetaClassMgr()
{
	// NOTE: the CPanelFactory implementation requires the local static here
	// even though it means an extra check every time PanelMetaClassMgr is accessed
	static CPanelMetaClassMgrImp s_MetaClassMgrImp;
	return &s_MetaClassMgrImp;
}


//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CPanelMetaClassMgrImp::CPanelMetaClassMgrImp() : m_PanelTypeDict( true, 0, 32 )
{
}

CPanelMetaClassMgrImp::~CPanelMetaClassMgrImp()
{
}


//-----------------------------------------------------------------------------
// Call this to install a new panel type
//-----------------------------------------------------------------------------
void CPanelMetaClassMgrImp::InstallPanelType( const char* pPanelName, IPanelFactory* pFactory )
{
	Assert( pPanelName && pFactory );
	
	// convert to lowercase
	int len = Q_strlen(pPanelName) + 1;
	char* pTemp = (char*)stackalloc( len );
	Q_strncpy( pTemp, pPanelName, len );
	Q_strnlwr( pTemp, len );

	m_PanelTypeDict.Insert( pTemp, pFactory );

	stackfree( pTemp );
}


//-----------------------------------------------------------------------------
// Parse a single metaclass
//-----------------------------------------------------------------------------
bool CPanelMetaClassMgrImp::ParseSingleMetaClass( const char* pFileName,
	const char* pMetaClassName, KeyValues* pMetaClassValues, int keyValueIndex )
{
	// Complain about duplicately defined metaclass names...
	if ( m_MetaClassDict.Find( pMetaClassName ) != m_MetaClassDict.InvalidIndex() )
	{
		Warning( "Meta class %s duplicately defined (file %s)\n", pMetaClassName, pFileName );
		return false;
	}

	// find the type...
	const char* pPanelType = pMetaClassValues->GetString( "type" );
	if (!pPanelType || !pPanelType[0])
	{
		Warning( "Unable to find type of meta class %s in file %s\n", pMetaClassName, pFileName );
		return false;
	}

	unsigned short i = m_PanelTypeDict.Find( pPanelType );
	if (i == m_PanelTypeDict.InvalidIndex())
	{
		Warning( "Type %s of meta class %s undefined!\n", pPanelType, pMetaClassName );
		stackfree(pLwrMetaClass);
		return false;
	}

	// Add it to the metaclass dictionary
	MetaClassDict_t element;
	element.m_TypeIndex = i;
	element.m_KeyValueIndex = keyValueIndex;
	element.m_pKeyValues = pMetaClassValues;
	m_MetaClassDict.Insert( pMetaClassName, element );

	return true;
}


//-----------------------------------------------------------------------------
// Parse the metaclass list
//-----------------------------------------------------------------------------
bool CPanelMetaClassMgrImp::ParseMetaClassList( const char* pFileName, 
												  KeyValues* pKeyValues, int keyValueIdx )
{
	// Iterate over all metaclasses...
	KeyValues* pIter = pKeyValues->GetFirstSubKey();
	while( pIter )
	{
		if (!ParseSingleMetaClass( pFileName, pIter->GetName(), pIter, keyValueIdx ))
		{
		//	return false;
			Warning( "MetaClass missing for %s\n", pIter->GetName() );
		}
		pIter = pIter->GetNextKey();
	}

	return true;
}


//-----------------------------------------------------------------------------
// Loads up a file containing metaclass definitions
//-----------------------------------------------------------------------------
void CPanelMetaClassMgrImp::LoadMetaClassDefinitionFile( const char *pFileName )
{
	MEM_ALLOC_CREDIT();

	// Blat out previous metaclass definitions read in from this file...
	int i = m_MetaClassKeyValues.Find( pFileName );
	if (i != m_MetaClassKeyValues.InvalidIndex() )
	{
		// Blow away the previous keyvalues	from that file
		unsigned short j = m_MetaClassDict.First();
		while ( j != m_MetaClassDict.InvalidIndex() )
		{
			unsigned short next = m_MetaClassDict.Next(j);
			if ( m_MetaClassDict[j].m_KeyValueIndex == i)
			{
				m_MetaClassDict.RemoveAt(j);
			}

			j = next;
		}

		m_MetaClassKeyValues[i]->deleteThis();
		m_MetaClassKeyValues.RemoveAt(i); 
	}

	// Create a new keyvalues entry
	KeyValues* pKeyValues = new KeyValues(pFileName);
	int idx = m_MetaClassKeyValues.Insert( pFileName, pKeyValues );

	// Read in all metaclass definitions...

	// Load the file
	if ( !pKeyValues->LoadFromFile( filesystem, pFileName ) )
	{
		Warning( "Couldn't find metaclass definition file %s\n", pFileName );
		pKeyValues->deleteThis();
		m_MetaClassKeyValues.RemoveAt(idx);
		return;
	}
	else
	{
		// Go ahead and parse the data now
		if ( !ParseMetaClassList( pFileName, pKeyValues, idx ) )
		{
			Warning( "Detected one or more errors parsing %s\n", pFileName );
		}
	}
}


//-----------------------------------------------------------------------------
// Performs key value chaining
//-----------------------------------------------------------------------------
static void KeyValueChainRecursive( KeyValues* pKeyValues, const char *pSectionName )
{
	KeyValues* pSection = pKeyValues->FindKey( pSectionName );

	if (pSection)
	{
		pKeyValues->ChainKeyValue( pSection );
	}

	KeyValues* pIter = pKeyValues->GetFirstSubKey();
	while (pIter)
	{
		// Don't both setting up links on a keyvalue that has no children
		if (pIter->GetFirstSubKey())
			KeyValueChainRecursive( pIter, pSectionName );

		pIter = pIter->GetNextKey();
	}
}


//-----------------------------------------------------------------------------
// Create, destroy panel...
//-----------------------------------------------------------------------------
vgui::Panel *CPanelMetaClassMgrImp::CreatePanelMetaClass( const char* pMetaClassName,
	int sortorder, void *pInitData, vgui::Panel *pParent, const char *pChainName )
{
	// Search for the metaclass name
	int i = m_MetaClassDict.Find( pMetaClassName );
	if (i == m_MetaClassDict.InvalidIndex())
		return NULL; 

	// Now that we've got the metaclass, we can figure out what kind of
	// panel to instantiate...
	MetaClassDict_t &metaClass = m_MetaClassDict[i];
	IPanelFactory* pFactory = m_PanelTypeDict[metaClass.m_TypeIndex];

	// Set up the key values for use in initialization
	if (pChainName)
	{
		KeyValueChainRecursive( metaClass.m_pKeyValues, pChainName );
	}

	// Create and initialize the panel
	vgui::Panel *pPanel = pFactory->Create( pMetaClassName, metaClass.m_pKeyValues, pInitData, pParent );
	if ( pPanel )
	{
		pPanel->SetZPos( sortorder );
	}

	return pPanel;
}

void CPanelMetaClassMgrImp::DestroyPanelMetaClass( vgui::Panel *pPanel )
{
//	if ( pPanel )
//		pPanel->MarkForDeletion();
	delete pPanel;
}