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

#include "stdafx.h"
#include "MapPath.h"
#include "hammer.h"
#include "EditPathDlg.h"
#include "MapEntity.h"

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


float GetFileVersion(void);


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CMapPath::CMapPath(void)
{
	m_iDirection = dirOneway;
	SetName("");
	SetClass("path_corner");
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CMapPath::~CMapPath(void)
{
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CMapPathNode::CMapPathNode(void)
{
	bSelected = FALSE;
	szName[0] = 0;
}

CMapPathNode::CMapPathNode(const CMapPathNode& src)
{
	*this = src;
}



//-----------------------------------------------------------------------------
// Purpose: 
// Input  : src - 
// Output : CMapPathNode
//-----------------------------------------------------------------------------
CMapPathNode &CMapPathNode::operator=(const CMapPathNode &src)
{
	// we don't care.
	Q_strncpy( szName, src.szName, sizeof(szName) );
	bSelected = src.bSelected;
	kv.RemoveAll();
	for ( int i=src.kv.GetFirst(); i != src.kv.GetInvalidIndex(); i=src.kv.GetNext( i ) )
	{
		MDkeyvalue KeyValue = src.kv.GetKeyValue(i);
		kv.SetValue(KeyValue.szKey, KeyValue.szValue);
	}
	pos = src.pos;
	dwID = src.dwID;

	return *this;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : dwID - 
//			piIndex - 
// Output : CMapPathNode *
//-----------------------------------------------------------------------------
CMapPathNode *CMapPath::NodeForID(DWORD dwID, int* piIndex)
{
	for(int iNode = 0; iNode < m_Nodes.Count(); iNode++)
	{
		if(m_Nodes[iNode].dwID == dwID)
		{
			if(piIndex)
				piIndex[0] = iNode;
			return &m_Nodes[iNode];
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : DWORD
//-----------------------------------------------------------------------------
DWORD CMapPath::GetNewNodeID(void)
{
	DWORD dwNewID = 1;
	while(true)
	{
		int iNode;
		for(iNode = 0; iNode < m_Nodes.Count(); iNode++)
		{
			if(m_Nodes[iNode].dwID == dwNewID)
				break;
		}

		if(iNode == m_Nodes.Count())
			return dwNewID;

		++dwNewID;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : dwAfterID - 
//			vecPos - 
// Output : 
//-----------------------------------------------------------------------------
DWORD CMapPath::AddNode(DWORD dwAfterID, const Vector &vecPos)
{
	int iPos;

	if(dwAfterID == ADD_START)
		iPos = 0;
	else if(dwAfterID == ADD_END)
		iPos = m_Nodes.Count();
	else if(!NodeForID(dwAfterID, &iPos))
		return 0;	// not found!

	CMapPathNode node;
	node.pos = vecPos;
	node.bSelected = FALSE;
	node.dwID = GetNewNodeID();

	if(iPos == m_Nodes.Count())
	{
		// add at tail
		m_Nodes.AddToTail(node);
	}
	else
	{
		m_Nodes.InsertBefore( iPos, node );
	}

	return node.dwID;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : dwID - 
//			*pt - 
//-----------------------------------------------------------------------------
void CMapPath::SetNodePosition(DWORD dwID, Vector& pt)
{
	int iIndex;
	NodeForID(dwID, &iIndex);

	m_Nodes[iIndex].pos = pt;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : dwID - 
//-----------------------------------------------------------------------------
void CMapPath::DeleteNode(DWORD dwID)
{
	int iIndex;
	if ( NodeForID(dwID, &iIndex) )
	{
		m_Nodes.Remove(iIndex);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : file - 
//			fIsStoring - 
//-----------------------------------------------------------------------------
void CMapPath::SerializeRMF(std::fstream& file, BOOL fIsStoring)
{
	int iSize;

	if(fIsStoring)
	{
		// save!!
		file.write(m_szName, 128);
		file.write(m_szClass, 128);
		file.write((char*) &m_iDirection, sizeof(m_iDirection));
	
		iSize = m_Nodes.Count();
		file.write((char*) &iSize, sizeof iSize);
		for(int i = 0; i < m_Nodes.Count(); i++)
		{
			CMapPathNode& node = m_Nodes[i];
			// store each node
			file.write((char*) &node.pos[0], 3 * sizeof(float));
			file.write((char*) &node.dwID, sizeof(node.dwID));
			file.write((char*) &node.szName, sizeof(node.szName));
			
			//
			// Write keyvalue count.
			//
			WCKeyValues &kv = node.kv;
			iSize = 0;
			for ( int z=kv.GetFirst(); z != kv.GetInvalidIndex(); z=kv.GetNext( z ) )
			{
				++iSize;
			}
			file.write((char*) &iSize, sizeof(iSize));

			//
			// Write keyvalues.
			//
			for (int k = kv.GetFirst(); k != kv.GetInvalidIndex(); k=kv.GetNext( k ) )
			{
				MDkeyvalue &KeyValue = kv.GetKeyValue(k);
				if (KeyValue.szKey[0] != '\0')
				{
					KeyValue.SerializeRMF(file, TRUE);
				}
			}
		}
	}
	else
	{
		// load!!
		file.read(m_szName, 128);
		file.read(m_szClass, 128);
		file.read((char*) &m_iDirection, sizeof m_iDirection);

		file.read((char*) &iSize, sizeof iSize);
		int nNodes = iSize;
		m_Nodes.RemoveAll();

		// read nodes
		for(int i = 0; i < nNodes; i++)
		{
			CMapPathNode node;
			// store each node
			file.read((char*) &node.pos[0], 3 * sizeof(float));
			file.read((char*) &node.dwID, sizeof(node.dwID));
			if(GetFileVersion() >= 1.6f)
			{
				file.read((char*) &node.szName, sizeof(node.szName));

				// read keyvalues
				file.read((char*) &iSize, sizeof(iSize));
				WCKeyValues &kv = node.kv;
				for (int k = 0; k < iSize; k++)
				{
					MDkeyvalue KeyValue;
					KeyValue.SerializeRMF(file, FALSE);
					kv.SetValue( KeyValue.szKey, KeyValue.szValue );
				}
			}

			m_Nodes.AddToTail(node);
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : iIndex - 
//			iName - 
//			str - 
//-----------------------------------------------------------------------------
void CMapPath::GetNodeName(int iIndex, int iName, CString& str)
{
	if(m_Nodes[iIndex].szName[0])
		str = m_Nodes[iIndex].szName;
	else
	{
		if(iName)
			str.Format("%s%02d", m_szName, iName);
		else
			str = m_szName;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : file - 
//			fIsStoring - 
//			*pIntersecting - 
//-----------------------------------------------------------------------------
void CMapPath::SerializeMAP(std::fstream& file, BOOL fIsStoring, BoundBox *pIntersecting)
{
	if( m_Nodes.Count() == 0)
		return;

	// if saving WITHIN a box, check all nodes to see if they all 
	//  fit within that box. if not, don't save ANY of the path.
	if(pIntersecting)
	{
		for(int i = 0; i < m_Nodes.Count(); i++)
		{
			if (!pIntersecting->ContainsPoint(m_Nodes[i].pos))
			{
				return;	// doesn't intersect - don't save path
			}
		}
	}


	Assert(fIsStoring);

	CString strTemp;
	MDkeyvalue kvTemp;

	// initialize nodes for saving
	for(int i = 0; i < m_Nodes.Count(); i++)
	{
		m_Nodes[i].nTargets = 0;
	}

	int iDirec = 1;
	int iCurNode = 0;
	int iMax = m_Nodes.Count()-1;
	int iName = 0;

	// resolve targets 
	int iLastNodeIndex = -1;
	BOOL bFirstPass = TRUE;

ResolveNamesAgain:
	while(1)
	{
		// store targetname
		GetNodeName(iCurNode, iName, strTemp);

		// store our name in the previous node (if not -1)
		if(iLastNodeIndex != -1)
		{
			CMapPathNode &prevNode = m_Nodes[iLastNodeIndex];
			strcpy(prevNode.szTargets[prevNode.nTargets++], strTemp);
		}

		++iName;

		iLastNodeIndex = iCurNode;

		if(iCurNode == iMax)
			break;
		iCurNode += iDirec;
	}

	if(bFirstPass && m_iDirection == dirPingpong && m_Nodes.Count() > 2)
	{
		// redo loop
		bFirstPass = FALSE;
		iDirec = -1;
		iCurNode = m_Nodes.Count()-2;
		iMax = 0;
		goto ResolveNamesAgain;
	}
	else if (m_iDirection == dirCircular)
	{
		//
		// Connect the last node to the first node.
		//
		CMapPathNode &LastNode = m_Nodes[iMax];
		GetNodeName(iCurNode, 0, strTemp);
		strcpy(LastNode.szTargets[LastNode.nTargets], strTemp);
		LastNode.nTargets++;
	}

	iDirec = 1;
	iCurNode = 0;
	iMax = m_Nodes.Count()-1;
	iName = 0;

SaveAgain:
	while(1)
	{
		file << "{" << "\r\n";

		// store name
		kvTemp.Set("classname", m_szClass);
		kvTemp.SerializeMAP(file, TRUE);

		CMapPathNode &node = m_Nodes[iCurNode];

		// store location
		strTemp.Format("%.0f %.0f %.0f", node.pos[0], node.pos[1], 
			node.pos[2]);
		kvTemp.Set("origin", strTemp);
		kvTemp.SerializeMAP(file, TRUE);

		// store targetname
		GetNodeName(iCurNode, iName, strTemp);
		kvTemp.Set("targetname", strTemp);
		kvTemp.SerializeMAP(file, TRUE);

		// store target (if not last)
		BOOL bStoreTarget = TRUE;
		if(iCurNode == iMax && m_iDirection == dirOneway)
			bStoreTarget = FALSE;

		if (bStoreTarget)
		{
			kvTemp.Set("target", (iDirec == 1) ? node.szTargets[0] : node.szTargets[1]);
			kvTemp.SerializeMAP(file, TRUE);
		}

		// other keyvalues
		WCKeyValues &kv = node.kv;
		for (int k = kv.GetFirst(); k != kv.GetInvalidIndex(); k=kv.GetNext( k ) )
		{
			MDkeyvalue &KeyValue = kv.GetKeyValue(k);
			if (KeyValue.szKey[0] != '\0')
			{
				KeyValue.SerializeMAP(file, TRUE);
			}
		}

		file << "}" << "\r\n";

		++iName;
		iLastNodeIndex = iCurNode;

		if(iCurNode == iMax)
			break;
		iCurNode += iDirec;
	}

	if(iDirec == 1 && m_iDirection == dirPingpong && m_Nodes.Count() > 2)
	{
		// redo loop
		iDirec = -1;
		iCurNode = m_Nodes.Count()-2;
		iMax = 1;
		goto SaveAgain;
	}
}

// Edit

void CMapPath::EditInfo()
{
	CEditPathDlg dlg;
	dlg.m_strName = m_szName;
	dlg.m_strClass = m_szClass;
	dlg.m_iDirection = m_iDirection;
	
	if(dlg.DoModal() != IDOK)
		return;

	SetName(dlg.m_strName);
	SetClass(dlg.m_strClass);
	m_iDirection = dlg.m_iDirection;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : dwNodeID - 
// Output : CMapEntity *
//-----------------------------------------------------------------------------
CMapEntity *CMapPath::CreateEntityForNode(DWORD dwNodeID)
{
	int iIndex;
	CMapPathNode *pNode = NodeForID(dwNodeID, &iIndex);
	if (pNode == NULL)
	{
		return NULL;	// no node, no entity!
	}

	CMapEntity *pEntity = new CMapEntity;

	for (int k = pNode->kv.GetFirst(); k != pNode->kv.GetInvalidIndex(); k=pNode->kv.GetNext( k ) )
	{
		pEntity->SetKeyValue(pNode->kv.GetKey(k), pNode->kv.GetValue(k));
	}
	
	// store target/targetname properties:
	CString str;
	str.Format("%s%02d", m_szName, iIndex);
	pEntity->SetKeyValue("targetname", str);

	int iNext = iIndex + 1;
	if(iNext != -1)
	{
		str.Format("%s%02d", m_szName, iNext);
		pEntity->SetKeyValue("target", str);
	}

	pEntity->SetClass(m_szClass);

	return pEntity;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : dwNodeID - 
//			*pEntity - 
//-----------------------------------------------------------------------------
void CMapPath::CopyNodeFromEntity(DWORD dwNodeID, CMapEntity *pEntity)
{
	CMapPathNode *pNode = NodeForID(dwNodeID);
	if (!pNode)
	{
		return;	// no node, no copy!
	}

	pNode->kv.RemoveAll();

	//
	// Copy all the keys except target and targetname from the entity to the pathnode.
	//
	for ( int i=pEntity->GetFirstKeyValue(); i != pEntity->GetInvalidKeyValue(); i=pEntity->GetNextKeyValue( i ) )
	{
		if (!strcmp(pEntity->GetKey(i), "target") || !strcmp(pEntity->GetKey(i), "targetname"))
		{
			continue;
		}

		pNode->kv.SetValue(pEntity->GetKey(i), pEntity->GetKeyValue(i));
	}
}


/*
//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*pNode - 
// Output : CChunkFileResult_t
//-----------------------------------------------------------------------------

UNDONE: Nobody uses the path tool because the user interface is so poor.
		Path support has been pulled until the tool itself can be fixed or replaced.

CChunkFileResult_t CMapPathNode::LoadKeyCallback(const char *szKey, const char *szValue, CMapPathNode *pNode)
{
	if (!stricmp(szKey, "origin"))
	{
		CChunkFile::ReadKeyValueVector3(szValue, pNode->pos);
	}
	else if (!stricmp(szKey, "id"))
	{
		CChunkFile::ReadKeyValueInt(szValue, &pNode->dwID);
	}
	else if (!stricmp(szKey, "name"))
	{
		strcpy(pNode->szName, szValue);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFile - 
// Output : ChunkFileResult_t
//-----------------------------------------------------------------------------
ChunkFileResult_t CMapPathNode::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo)
{
	ChunkFileResult_t  eResult = pFile->BeginChunk("node");

	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->WriteKeyValueVector3("origin", node.pos);
	}

	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->WriteKeyValueInt("id", node.dwID);
	}

	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->WriteKeyValue("name", node.szName);
	}
	
	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->BeginChunk("keys");
	}

	//
	// Write keyvalues.
	//
	if (eResult == ChunkFile_Ok)
	{
		iSize = kv.GetCount();
		for (int k = 0; k < iSize; k++)
		{
			MDkeyvalue &KeyValue = kv.GetKeyValue(k);
			if (eResult == ChunkFile_Ok)
			{
				eResult = pFile->WriteKeyValue(KeyValue.GetKey(), KeyValue.GetValue());
			}
		}
	}

	// End the keys chunk.
	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->EndChunk();
	}

	// End the node chunk.
	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->EndChunk();
	}

	return(eResult);
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *szKey - 
//			*szValue - 
//			*pPath - 
// Output : CChunkFileResult_t
//-----------------------------------------------------------------------------
CChunkFileResult_t CMapPath::LoadKeyCallback(const char *szKey, const char *szValue, CMapPath *pPath)
{
	if (!stricmp(szKey, "name"))
	{
		pPath->SetName(szValue);
	}
	else if (!stricmp(szKey, "classname"))
	{
		pPath->SetClass(szValue);
	}
	else if (!stricmp(szKey, "direction"))
	{
		CChunkFile::ReadKeyValueInt(szValue, &pPath->m_iDirection);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFile - 
//-----------------------------------------------------------------------------
void CMapPath::LoadVMF(CChunkFile *pFile)
{
	file.read((char*) &iSize, sizeof iSize);
	m_nNodes = iSize;
	m_Nodes.SetSize(m_nNodes);

	// read nodes
	for (int i = 0; i < m_nNodes; i++)
	{
		CMapPathNode &node = m_Nodes[i];

			// read keyvalues
			file.read((char*) &iSize, sizeof(iSize));
			KeyValues &kv = node.kv;
			kv.SetSize(iSize);
			for (int k = 0; k < iSize; k++)
			{
				MDkeyvalue &KeyValue = kv.GetKeyValue(k);
				KeyValue.SerializeRMF(file, FALSE);
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFile - 
//-----------------------------------------------------------------------------
void CMapPath::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo)
{
	int iSize;

	ChunkFileResult_t eResult = pFile->BeginChunk("path");

	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile-WriteKeyValue("name", m_szName);
	}

	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->WriteKeyValue("classname", m_szClass);
	}

	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->WriteKeyValueInt("direction", m_iDirection);
	}

	if (eResult == ChunkFile_Ok)
	{
		for (int i = 0; i < m_nNodes; i++)
		{
			CMapPathNode &node = m_Nodes[i];
			eResult = node.SaveVMF(pFile, pSaveInfo);
		}
	}

	if (eResult == ChunkFile_Ok)
	{
		eResult = pFile->EndChunk();
	}
}
*/