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

#include "commeditdoc.h"
#include "tier1/KeyValues.h"
#include "tier1/utlbuffer.h"
#include "toolutils/enginetools_int.h"
#include "filesystem.h"
#include "commedittool.h"
#include "toolframework/ienginetool.h"
#include "dmecommentarynodeentity.h"
#include "datamodel/idatamodel.h"
#include "toolutils/attributeelementchoicelist.h"
#include "commentarynodebrowserpanel.h"
#include "vgui_controls/messagebox.h"


//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CCommEditDoc::CCommEditDoc( ICommEditDocCallback *pCallback ) : m_pCallback( pCallback )
{
	m_hRoot = NULL;
	m_pTXTFileName[0] = 0;
	m_bDirty = false;
	g_pDataModel->InstallNotificationCallback( this );
}

CCommEditDoc::~CCommEditDoc()
{
	g_pDataModel->RemoveNotificationCallback( this );
}


//-----------------------------------------------------------------------------
// Inherited from INotifyUI
//-----------------------------------------------------------------------------
void CCommEditDoc::NotifyDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags )
{
	OnDataChanged( pReason, nNotifySource, nNotifyFlags );
}

	
//-----------------------------------------------------------------------------
// Gets the file name
//-----------------------------------------------------------------------------
const char *CCommEditDoc::GetTXTFileName()
{
	return m_pTXTFileName;
}

void CCommEditDoc::SetTXTFileName( const char *pFileName )
{
	Q_strncpy( m_pTXTFileName, pFileName, sizeof( m_pTXTFileName ) );
	Q_FixSlashes( m_pTXTFileName );
	SetDirty( true );
}

//-----------------------------------------------------------------------------
// Dirty bits
//-----------------------------------------------------------------------------
void CCommEditDoc::SetDirty( bool bDirty )
{
	m_bDirty = bDirty;
}

bool CCommEditDoc::IsDirty() const
{
	return m_bDirty;
}


//-----------------------------------------------------------------------------
// Handles creation of the right element for a keyvalue
//-----------------------------------------------------------------------------
class CElementForKeyValueCallback : public IElementForKeyValueCallback
{
public:
	const char *GetElementForKeyValue( const char *pszKeyName, int iNestingLevel )
	{
		if ( iNestingLevel == 1 && !Q_strncmp(pszKeyName, "entity", 6) )
			return "DmeCommentaryNodeEntity";

		return NULL;
	}
};

//-----------------------------------------------------------------------------
// Saves/loads from file
//-----------------------------------------------------------------------------
bool CCommEditDoc::LoadFromFile( const char *pFileName )
{
	Assert( !m_hRoot.Get() );

	CAppDisableUndoScopeGuard guard( "CCommEditDoc::LoadFromFile", 0 );
	SetDirty( false );

	if ( !pFileName[0] )
		return false;

	char mapname[ 256 ];

	// Compute the map name
	const char *pMaps = Q_stristr( pFileName, "\\maps\\" );
	if ( !pMaps )
		return false;

	// Build map name
	//int nNameLen = (int)( (size_t)pComm - (size_t)pMaps ) - 5;
	Q_StripExtension( pFileName, mapname, sizeof(mapname) );
	char *pszFileName = (char*)Q_UnqualifiedFileName(mapname);

	// Set the txt file name. 
	// If we loaded an existing commentary file, keep the same filename.
	// If we loaded a .bsp, change the name & the extension.
	if ( !V_stricmp( Q_GetFileExtension( pFileName ), "bsp" ) )
	{
		const char *pCommentaryAppend = "_commentary.txt";
		Q_StripExtension( pFileName, m_pTXTFileName, sizeof(m_pTXTFileName)- strlen(pCommentaryAppend) - 1 );
		Q_strcat( m_pTXTFileName, pCommentaryAppend, sizeof( m_pTXTFileName ) );

		if ( g_pFileSystem->FileExists( m_pTXTFileName ) )
		{
			char pBuf[1024];
			Q_snprintf( pBuf, sizeof(pBuf), "File %s already exists!\n", m_pTXTFileName ); 
			m_pTXTFileName[0] = 0;
			vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Unable to overwrite file!\n", pBuf, g_pCommEditTool );
			pMessageBox->DoModal( );
			return false;
		}

		DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( m_pTXTFileName );

		m_hRoot = CreateElement<CDmElement>( "root", fileid );
		CDmrElementArray<> subkeys( m_hRoot->AddAttribute( "subkeys", AT_ELEMENT_ARRAY ) );
		CDmElement *pRoot2 = CreateElement<CDmElement>( "Entities", fileid );
		pRoot2->AddAttribute( "subkeys", AT_ELEMENT_ARRAY );
		subkeys.AddToTail( pRoot2 );
		g_pDataModel->SetFileRoot( fileid, m_hRoot );
	}
	else
	{
		char *pComm = Q_stristr( pszFileName, "_commentary" );
		if ( !pComm )
		{
			char pBuf[1024];
			Q_snprintf( pBuf, sizeof(pBuf), "File %s is not a commentary file!\nThe file name must end in _commentary.txt.\n", m_pTXTFileName ); 
			m_pTXTFileName[0] = 0;
			vgui::MessageBox *pMessageBox = new vgui::MessageBox( "Bad file name!\n", pBuf, g_pCommEditTool );
			pMessageBox->DoModal( );
			return false;
		}

		// Clip off the "_commentary" at the end of the filename
		*pComm = '\0';

		// This is not undoable
		CDisableUndoScopeGuard guardFile;

		CDmElement *pTXT = NULL;

		CElementForKeyValueCallback KeyValuesCallback;
		g_pDataModel->SetKeyValuesElementCallback( &KeyValuesCallback );
		DmFileId_t fileid = g_pDataModel->RestoreFromFile( pFileName, NULL, "keyvalues", &pTXT );
		g_pDataModel->SetKeyValuesElementCallback( NULL );

		if ( fileid == DMFILEID_INVALID )
		{
			m_pTXTFileName[0] = 0;
			return false;
		}

		SetTXTFileName( pFileName );
		m_hRoot = pTXT;
	}

	guard.Release();
	SetDirty( false );

	char cmd[ 256 ];
	Q_snprintf( cmd, sizeof( cmd ), "disconnect; map %s\n", pszFileName );
	enginetools->Command( cmd );
	enginetools->Execute( );

	return true;
}

void CCommEditDoc::SaveToFile( )
{
	if ( m_hRoot.Get() && m_pTXTFileName && m_pTXTFileName[0] )
	{
		g_pDataModel->SaveToFile( m_pTXTFileName, NULL, "keyvalues", "keyvalues", m_hRoot );
	}

	SetDirty( false );
}

	
//-----------------------------------------------------------------------------
// Returns the root object
//-----------------------------------------------------------------------------
CDmElement *CCommEditDoc::GetRootObject()
{
	return m_hRoot;
}

	
//-----------------------------------------------------------------------------
// Returns the entity list
//-----------------------------------------------------------------------------
CDmAttribute *CCommEditDoc::GetEntityList()
{
	CDmrElementArray<> mainKeys( m_hRoot, "subkeys" );
	if ( !mainKeys.IsValid() || mainKeys.Count() == 0 )
		return NULL;
	CDmeHandle<CDmElement> hEntityList;
	hEntityList = mainKeys[ 0 ];
	return hEntityList ? hEntityList->GetAttribute( "subkeys", AT_ELEMENT_ARRAY ) : NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CCommEditDoc::AddNewInfoTarget( const Vector &vecOrigin, const QAngle &angAngles )
{
	CDmrCommentaryNodeEntityList entities( GetEntityList() );
	if ( !entities.IsValid() )
		return;

	CDmeCommentaryNodeEntity *pTarget;
	{
		CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Add Info Target", "Add Info Target" );

		pTarget = CreateElement<CDmeCommentaryNodeEntity>( "target", entities.GetOwner()->GetFileId() );
		pTarget->SetName( "entity" );
		pTarget->SetValue( "classname", "info_target" );
		pTarget->SetRenderOrigin( vecOrigin );
		pTarget->SetRenderAngles( angAngles );

		entities.AddToTail( pTarget );
		pTarget->MarkDirty();
		pTarget->DrawInEngine( true );
	}

	g_pCommEditTool->GetCommentaryNodeBrowser()->SelectNode( pTarget );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CCommEditDoc::AddNewInfoTarget( void )
{
	Vector vecOrigin;
	QAngle angAngles;
	float flFov;
	clienttools->GetLocalPlayerEyePosition( vecOrigin, angAngles, flFov );
	AddNewInfoTarget( vecOrigin, vec3_angle ); 
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CCommEditDoc::AddNewCommentaryNode( const Vector &vecOrigin, const QAngle &angAngles )
{
	CDmrCommentaryNodeEntityList entities = GetEntityList();

	CDmeCommentaryNodeEntity *pNode;
	{
		CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Add Commentary Node", "Add Commentary Node" );

		pNode = CreateElement<CDmeCommentaryNodeEntity>( "node", entities.GetOwner()->GetFileId() );
		pNode->SetName( "entity" );
		pNode->SetValue( "classname", "point_commentary_node" );
		pNode->SetRenderOrigin( vecOrigin );
		pNode->SetRenderAngles( angAngles );
		pNode->SetValue<CUtlString>( "precommands", "" );
		pNode->SetValue<CUtlString>( "postcommands", "" );
		pNode->SetValue<CUtlString>( "commentaryfile", "" );
		pNode->SetValue<CUtlString>( "viewtarget", "" );
		pNode->SetValue<CUtlString>( "viewposition", "" );
		pNode->SetValue<int>( "prevent_movement", 0 );
		pNode->SetValue<CUtlString>( "speakers", "" );
		pNode->SetValue<CUtlString>( "synopsis", "" );

		entities.AddToTail( pNode );
		pNode->MarkDirty();
		pNode->DrawInEngine( true );
	}

	g_pCommEditTool->GetCommentaryNodeBrowser()->SelectNode( pNode );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CCommEditDoc::AddNewCommentaryNode( void )
{
	Vector vecOrigin;
	QAngle angAngles;
	float flFov;
	clienttools->GetLocalPlayerEyePosition( vecOrigin, angAngles, flFov );
	AddNewCommentaryNode( vecOrigin, vec3_angle );
}


//-----------------------------------------------------------------------------
// Deletes a commentary node
//-----------------------------------------------------------------------------
void CCommEditDoc::DeleteCommentaryNode( CDmElement *pRemoveNode )
{
	CDmrCommentaryNodeEntityList entities = GetEntityList();
	int nCount = entities.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		if ( pRemoveNode == entities[i] )
		{
			CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Delete Commentary Node", "Delete Commentary Node" );
			CDmeCommentaryNodeEntity *pNode = entities[ i ];
			pNode->DrawInEngine( false );
			entities.FastRemove( i );
			return;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &vecOrigin - 
//			&angAbsAngles - 
// Output : CDmeCommentaryNodeEntity
//-----------------------------------------------------------------------------
CDmeCommentaryNodeEntity *CCommEditDoc::GetCommentaryNodeForLocation( Vector &vecOrigin, QAngle &angAbsAngles )
{
	CDmrCommentaryNodeEntityList entities = GetEntityList();
	int nCount = entities.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		CDmeCommentaryNodeEntity *pNode = entities[ i ];
		if ( !pNode )
			continue;

		Vector &vecAngles = *(Vector*)(&pNode->GetRenderAngles());
		if ( pNode->GetRenderOrigin().DistTo( vecOrigin ) < 1e-3 && vecAngles.DistTo( *(Vector*)&angAbsAngles ) < 1e-1 )
			return pNode;
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Populate string choice lists
//-----------------------------------------------------------------------------
bool CCommEditDoc::GetStringChoiceList( const char *pChoiceListType, CDmElement *pElement, 
									const char *pAttributeName, bool bArrayElement, StringChoiceList_t &list )
{
	if ( !Q_stricmp( pChoiceListType, "info_targets" ) )
	{
		CDmrCommentaryNodeEntityList entities = GetEntityList();

		StringChoice_t sChoice;
		sChoice.m_pValue = "";
		sChoice.m_pChoiceString = "";
		list.AddToTail( sChoice );

		int nCount = entities.Count();
		for ( int i = 0; i < nCount; ++i )
		{
			CDmeCommentaryNodeEntity *pNode = entities[ i ];
			if ( !pNode )
				continue;

			if ( !V_stricmp( pNode->GetClassName(), "info_target" ) )
			{
				sChoice.m_pValue = pNode->GetTargetName();
				sChoice.m_pChoiceString = pNode->GetTargetName();
				list.AddToTail( sChoice );
			}
		}
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Populate element choice lists
//-----------------------------------------------------------------------------
bool CCommEditDoc::GetElementChoiceList( const char *pChoiceListType, CDmElement *pElement, 
									 const char *pAttributeName, bool bArrayElement, ElementChoiceList_t &list )
{
	if ( !Q_stricmp( pChoiceListType, "allelements" ) )
	{
		AddElementsRecursively( m_hRoot, list );
		return true;
	}

	if ( !Q_stricmp( pChoiceListType, "info_targets" ) )
	{
		CDmrCommentaryNodeEntityList entities = GetEntityList();

		bool bFound = false;
		int nCount = entities.Count();
		for ( int i = 0; i < nCount; ++i )
		{
			CDmeCommentaryNodeEntity *pNode = entities[ i ];
			if ( pNode && !V_stricmp( pNode->GetClassName(), "info_target" ) )
			{
				bFound = true;
				ElementChoice_t sChoice;
				sChoice.m_pValue = pNode;
				sChoice.m_pChoiceString = pNode->GetTargetName();
				list.AddToTail( sChoice );
			}
		}
		return bFound;
	}

	// by default, try to treat the choice list type as a Dme element type
	AddElementsRecursively( m_hRoot, list, pChoiceListType );

	return list.Count() > 0;
}

	
//-----------------------------------------------------------------------------
// Called when data changes
//-----------------------------------------------------------------------------
void CCommEditDoc::OnDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags )
{
	SetDirty( nNotifyFlags & NOTIFY_SETDIRTYFLAG ? true : false );
	m_pCallback->OnDocChanged( pReason, nNotifySource, nNotifyFlags );
}