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

#ifndef DMEPANEL_H
#define DMEPANEL_H

#ifdef _WIN32
#pragma once
#endif


#include "tier0/basetypes.h"
#include "tier1/utlstringmap.h"
#include "vgui_controls/EditablePanel.h"
#include "datamodel/dmelement.h"
#include "datamodel/dmehandle.h"


//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
class CDmElement;
class CBaseDmePanelFactory;

namespace vgui
{
	class Panel;
	class EditablePanel;
	class ComboBox;
	class IScheme;
}


//-----------------------------------------------------------------------------
// Dme Panel factory iteration handle
//-----------------------------------------------------------------------------
DECLARE_POINTER_HANDLE( DmeFactoryHandle_t );
#define DMEFACTORY_HANDLE_INVALID ((DmeFactoryHandle_t)0)


//-----------------------------------------------------------------------------
// Dme Panel: used for editing arbitrary dme elements
//-----------------------------------------------------------------------------
class CDmePanel : public vgui::EditablePanel
{
	DECLARE_CLASS_SIMPLE( CDmePanel, vgui::EditablePanel );

public:
	// constructor, destructor
	CDmePanel( vgui::Panel *pParent, const char *pPanelName, bool bComboBoxVisible = true );
	virtual ~CDmePanel();

	virtual void PerformLayout();
	virtual void ApplySchemeSettings( vgui::IScheme *pScheme );

	void SetDmeElement( CDmElement *pDmeElement, bool bForce = false, const char *pPanelName = NULL );

	// Switch to a new editor
	void SetEditor( const char *pEditorName );

	// Drag/drop
	bool IsDroppable( CUtlVector< KeyValues * >& msglist );
	void OnPanelDropped( CUtlVector< KeyValues * >& msglist );

	// Refreshes the current panel owing to external change
	// Values only means no topological change
	void Refresh( bool bValuesOnly );

	// Sets the default editor type
	void SetDefaultEditorType( const char *pEditorType );

private:
	struct EditorPanelMap_t
	{
		vgui::EditablePanel *m_pEditorPanel;
		CBaseDmePanelFactory *m_pFactory;
	};

	MESSAGE_FUNC( OnTextChanged, "TextChanged" );	
	MESSAGE_FUNC( OnDmeElementChanged, "DmeElementChanged" );	
	MESSAGE_FUNC_PARAMS( OnViewedElementChanged, "NotifyViewedElementChanged", kv );

	// Copy/paste support
	MESSAGE_FUNC( OnCut, "OnCut" );
	MESSAGE_FUNC( OnCopy, "OnCopy" );
	MESSAGE_FUNC( OnPaste, "OnPaste" );
	MESSAGE_FUNC( OnPasteReference, "OnPasteReference" );
	MESSAGE_FUNC( OnPasteInsert, "OnPasteInsert" );
	MESSAGE_FUNC( OnEditDelete, "OnEditDelete" );

	// Context menu support
	MESSAGE_FUNC_PARAMS( OnOpenContextMenu, "OpenContextMenu", params );

	// Delete cached panels
	void DeleteCachedPanels();

	// Populate editor name combo box
	void PopulateEditorNames( const char *pPanelName = NULL );

	// Deactivates the current editor
	void DeactivateCurrentEditor();

	// Post message to the dme panel
	void PostMessageToDmePanel( const char *pMessage );

	static bool CreateDmePanel( vgui::Panel *pParent, const char *pPanelName, CDmElement *pElement, const char *pEditorName, EditorPanelMap_t *pMap );

	vgui::ComboBox *m_pEditorNames;

	CDmeHandle< CDmElement > m_hElement;
	CUtlStringMap< CUtlString > m_LastUsedEditorType;
	CUtlStringMap< CUtlVector< EditorPanelMap_t > > m_EditorPanelCache;
	vgui::EditablePanel *m_pDmeEditorPanel;
	CUtlString m_CurrentEditorName;
	CUtlString m_DefaultEditorType;
};


//-----------------------------------------------------------------------------
// Dme Panel factory methods
//-----------------------------------------------------------------------------
class CBaseDmePanelFactory
{
public:
	virtual vgui::EditablePanel *CreateDmePanel( vgui::Panel *pParent, const char *pPanelName, CDmElement *pElement ) = 0;
	virtual void SetDmeElement( vgui::EditablePanel *pPanel, CDmElement *pElement ) = 0;

protected:
	// Constructor, protected because these should never be instanced directly
	CBaseDmePanelFactory( const char *pElementType, const char *pEditorName, const char *pEditorDisplayName, bool bIsDefault, bool bIsOverride );

public:
	const char *m_pElementType;
	const char *m_pEditorName;
	const char *m_pEditorDisplayName;
	bool m_bIsDefault : 1;
	bool m_bIsOverride : 1;

	CBaseDmePanelFactory *m_pNext;
	static CBaseDmePanelFactory* s_pFirstDmePanelFactory;
};


template< class PanelType, class ElementType >
class CDmePanelFactory : public CBaseDmePanelFactory
{
	typedef CBaseDmePanelFactory BaseClass;

public:
	// Constructor
	CDmePanelFactory( const char *pElementType, const char *pEditorName, const char *pEditorDisplayName, bool bIsDefault, bool bIsOverride ) :
		BaseClass( pElementType, pEditorName, pEditorDisplayName, bIsDefault, bIsOverride )
	{
	}

	virtual vgui::EditablePanel *CreateDmePanel( vgui::Panel *pParent, const char *pPanelName, CDmElement *pElement )
	{
		ElementType *pTypedElement = CastElement<ElementType>( pElement );
		Assert( pTypedElement && pElement->IsA( m_pElementType ) );

		// NOTE: The panel factory assumes T contains the following method:
		// void SetDmeElement( ElementType *pElement );
		// You'll get compile errors about 'SetDmeElement' not being defined if not
		PanelType *pPanel = new PanelType( pParent, pPanelName );
		pPanel->SetDmeElement( pTypedElement );
		return pPanel;
	}

	virtual void SetDmeElement( vgui::EditablePanel *pPanel, CDmElement *pElement )
	{
		PanelType *pTypedPanel = static_cast< PanelType* >( pPanel );
		ElementType *pTypedElement = static_cast< ElementType* >( pElement );
		pTypedPanel->SetDmeElement( pTypedElement );
	}
};


template< class PanelType, class ElementType, class DisplayType >
class CDmePanelConverterFactory : public CBaseDmePanelFactory
{
	typedef CBaseDmePanelFactory BaseClass;

public:
	// Constructor
	CDmePanelConverterFactory( const char *pElementType, const char *pDisplayType, const char *pEditorName, const char *pEditorDisplayName, bool bIsDefault, bool bIsOverride ) :
		BaseClass( pElementType, pEditorName, pEditorDisplayName, bIsDefault, bIsOverride )
	{
		m_pDisplayType = pDisplayType;
	}

	virtual vgui::EditablePanel *CreateDmePanel( vgui::Panel *pParent, const char *pPanelName, CDmElement *pElement )
	{
		ElementType *pTypedElement = CastElement<ElementType>( pElement );
		Assert( pTypedElement && pElement->IsA( m_pElementType ) );

		// NOTE: To use the converter factory, the element must implement a method
		// CDmElement *GetDmePanelElement( const char *pDisplayType );
		DisplayType *pDisplayElement = CastElement<DisplayType>( pTypedElement->GetDmePanelElement( m_pDisplayType ) );

		// NOTE: The panel factory assumes T contains the following method:
		// void SetDmeElement( ElementType *pElement );
		// You'll get compile errors about 'SetDmeElement' not being defined if not
		PanelType *pPanel = new PanelType( pParent, pPanelName );
		pPanel->SetDmeElement( pDisplayElement );
		return pPanel;
	}

	virtual void SetDmeElement( vgui::EditablePanel *pPanel, CDmElement *pElement )
	{
		PanelType *pTypedPanel = static_cast< PanelType* >( pPanel );
		ElementType *pTypedElement = static_cast< ElementType* >( pElement );
		DisplayType *pDisplayElement = CastElement<DisplayType>( pTypedElement->GetDmePanelElement( m_pDisplayType ) );
		pTypedPanel->SetDmeElement( pDisplayElement );
	}

private:
	const char *m_pDisplayType;
};


//-----------------------------------------------------------------------------
// Helper macro to create the panel factory
// IMPLEMENT_DMEPANEL_FACTORY_OVERRIDE is used by applications to override
// DmePanels implemented in libraries
//-----------------------------------------------------------------------------
#define IMPLEMENT_DMEPANEL_FACTORY( _panelClassName, _dmeLookupName, _editorName, _editorDisplayName, _isDefault )	\
	CDmePanelFactory< _panelClassName, C##_dmeLookupName > g_##_panelClassName##_##_dmeLookupName##_Factory( #_dmeLookupName, _editorName, _editorDisplayName, _isDefault, false );	\
	_panelClassName *g_##_panelClassName##_##_dmeLookupName##LinkerHack = NULL;

#define IMPLEMENT_DMEPANEL_FACTORY_OVERRIDE( _panelClassName, _dmeLookupName, _editorName, _editorDisplayName, _isDefault )	\
	CDmePanelFactory< _panelClassName, C##_dmeLookupName > g_##_panelClassName##_##_dmeLookupName##_Factory( #_dmeLookupName, _editorName, _editorDisplayName, _isDefault, true );	\
	_panelClassName *g_##_panelClassName##_##_dmeLookupName##LinkerHack = NULL;

#define USING_DMEPANEL_FACTORY( _panelClassName, _dmeLookupName )\
	class _panelClassName;										\
	extern _panelClassName *g_##_panelClassName##_##_dmeLookupName##LinkerHack;		\
	_panelClassName *g_##_panelClassName##_##_dmeLookupName##PullInModule = g_##_panelClassName##_##_dmeLookupName##LinkerHack;


//-----------------------------------------------------------------------------
// Helper macro to create the converter panel factory 
// IMPLEMENT_DMEPANEL_CONVERSION_FACTORY_OVERRIDE is used by applications to override
// DmePanels implemented in libraries
//-----------------------------------------------------------------------------
#define IMPLEMENT_DMEPANEL_CONVERSION_FACTORY( _panelClassName, _dmeLookupName, _dmeDisplayName, _editorName, _editorDisplayName, _isDefault )	\
	CDmePanelConverterFactory< _panelClassName, C##_dmeLookupName, C##_dmeDisplayName > g_##_panelClassName##_##_dmeLookupName##_Factory( #_dmeLookupName, #_dmeDisplayName, _editorName, _editorDisplayName, _isDefault, false );	\
	_panelClassName *g_##_panelClassName##_##_dmeLookupName##LinkerHack = NULL;

#define IMPLEMENT_DMEPANEL_CONVERSION_FACTORY_OVERRIDE( _panelClassName, _dmeLookupName, _dmeDisplayName, _editorName, _editorDisplayName, _isDefault )	\
	CDmePanelConverterFactory< _panelClassName, C##_dmeLookupName, C##_dmeDisplayName > g_##_panelClassName##_##_dmeLookupName##_Factory( #_dmeLookupName, #_dmeDisplayName, _editorName, _editorDisplayName, _isDefault, true );	\
	_panelClassName *g_##_panelClassName##_##_dmeLookupName##LinkerHack = NULL;

#define USING_DMEPANEL_CONVERSION_FACTORY( _panelClassName, _dmeLookupName )	\
	class _panelClassName;											\
	extern _panelClassName *g_##_panelClassName##_##_dmeLookupName##LinkerHack;		\
	_panelClassName *g_##_panelClassName##_##_dmeLookupName##PullInModule = g_##_panelClassName##_##_dmeLookupName##LinkerHack;



//-----------------------------------------------------------------------------
// Get Dme Factories for a particular element type
//-----------------------------------------------------------------------------
DmeFactoryHandle_t DmePanelFirstFactory( CDmElement *pElement = NULL );
DmeFactoryHandle_t DmePanelNextFactory( DmeFactoryHandle_t h, CDmElement *pElement = NULL );
const char *DmePanelFactoryName( DmeFactoryHandle_t h );
const char *DmePanelFactoryDisplayName( DmeFactoryHandle_t h );
const char *DmePanelFactoryElementType( DmeFactoryHandle_t h );
bool DmePanelFactoryIsDefault( DmeFactoryHandle_t h );


//-----------------------------------------------------------------------------
// Dme Panel factory methods
//-----------------------------------------------------------------------------
vgui::EditablePanel *CreateDmePanel( vgui::Panel *pParent, const char *pPanelName, CDmElement *pElement, const char *pEditorName = NULL );


#endif // DMEPANEL_H