//========= Copyright © 1996-2003, Valve LLC, All rights reserved. ============
//
// Purpose: Implements a helper that manages a single keyvalue of type "side"
//			or "sidelist" for the entity that is its parent.
//
//=============================================================================

#include "stdafx.h"
#include "HelperInfo.h"
#include "materialsystem/IMesh.h"
#include "MapClass.h"
#include "MapSolid.h"
#include "MapWorld.h"			// For the world's face ID functions.
#include "MapSideList.h"
#include "Material.h"
#include "Render3D.h"


IMPLEMENT_MAPCLASS(CMapSideList);


//-----------------------------------------------------------------------------
// Purpose: Factory function. Used for creating a CMapSideList from a set
//			of string parameters from the FGD file.
// Input  : pInfo - Pointer to helper info class which gives us information
//				about how to create the class.
// Output : Returns a pointer to the class, NULL if an error occurs.
//-----------------------------------------------------------------------------
CMapClass *CMapSideList::CreateMapSideList(CHelperInfo *pHelperInfo, CMapEntity *pParent)
{
	CMapSideList *pSideList = NULL;

	POSITION pos;
	const char *pszParam = pHelperInfo->GetFirstParameter(pos);
	if (pszParam != NULL)
	{
		pSideList = new CMapSideList(pszParam);
	}
	else
	{
		pSideList = new CMapSideList("sides");
	}

	return(pSideList);
}


//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CMapSideList::CMapSideList(void)
{
}


//-----------------------------------------------------------------------------
// Purpose: Constructor with key name.
//-----------------------------------------------------------------------------
CMapSideList::CMapSideList(char const *pszKeyName)
{
	strcpy(m_szKeyName, pszKeyName);
}


//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CMapSideList::~CMapSideList(void)
{
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pszValue - 
//			pWorld - The world object that we are contained in.
//-----------------------------------------------------------------------------
void CMapSideList::BuildFaceListForValue(char const *pszValue, CMapWorld *pWorld)
{
	CMapFaceList NewFaces;
	pWorld->FaceID_StringToFaceLists(&NewFaces, NULL, pszValue);

	//
	// Detach from the faces that are not in the new list. Go
	// in reverse order since we are removing items as we go.
	//
	if (m_Faces.Count() > 0)
	{
		for (int i = m_Faces.Count() - 1; i >= 0; i--)
		{
			CMapFace *pFace = m_Faces.Element(i);
			ASSERT(pFace != NULL);
			if ((pFace != NULL) && (NewFaces.Find(pFace) == -1))
			{
				CMapSolid *pSolid = (CMapSolid *)pFace->GetParent();
				UpdateDependency(pSolid, NULL);
				m_Faces.FastRemove(i);
			}
		}
	}

	//
	// Attach to the faces that are not in the old list.
	//
	for (int i = 0; i < NewFaces.Count(); i++)
	{
		CMapFace *pFace = NewFaces.Element(i);
		ASSERT(pFace != NULL);

		if ((pFace != NULL) && (m_Faces.Find(pFace) == -1))
		{
			CMapSolid *pSolid = (CMapSolid *)pFace->GetParent();
			UpdateDependency(NULL, pSolid);
			m_Faces.AddToTail(pFace);
		}
	}

	CalcBounds();
}


//-----------------------------------------------------------------------------
// Purpose: Calculates our bounds.
// Input  : bFullUpdate - 
//-----------------------------------------------------------------------------
void CMapSideList::CalcBounds(BOOL bFullUpdate)
{
	//
	// We're just a point in the 2D view because we don't render there.
	//
	m_Render2DBox.ResetBounds();
	m_Render2DBox.UpdateBounds(m_Origin);

	//
	// Our culling bounds includes the endpoints of all the lines we draw when
	// our parent entity is selected.
	//
	m_CullBox.ResetBounds();
	m_CullBox.UpdateBounds(m_Origin);

	for (int i = 0; i < m_Faces.Count(); i++)
	{
		CMapFace *pFace = m_Faces.Element(i);

		Vector Center;
		pFace->GetCenter(Center);
		m_CullBox.UpdateBounds(Center);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns a copy of this object.
// Input  : bUpdateDependencies - Whether the new object should link to any
//				other objects in the world when it copies pointers.
//-----------------------------------------------------------------------------
CMapClass *CMapSideList::Copy(bool bUpdateDependencies)
{
	CMapSideList *pCopy = new CMapSideList;
	if (pCopy != NULL)
	{
		pCopy->CopyFrom(this, bUpdateDependencies);
	}
	return(pCopy);
}


//-----------------------------------------------------------------------------
// Purpose: Turns us into an exact copy of the given object.
// Input  : pFrom - Object to copy.
// Input  : bUpdateDependencies - Whether we should link to any other objects
//				in the world when we copy pointers.
//-----------------------------------------------------------------------------
CMapClass *CMapSideList::CopyFrom(CMapClass *pOther, bool bUpdateDependencies)
{
	CMapSideList *pFrom = dynamic_cast <CMapSideList *>(pOther);
	ASSERT(pFrom != NULL);

	CMapClass::CopyFrom(pOther, bUpdateDependencies);

	strcpy(m_szKeyName, pFrom->m_szKeyName);
	m_Faces = pFrom->m_Faces;

	if (bUpdateDependencies)
	{
		for (int i = 0; i < m_Faces.Count(); i++)
		{
			CMapFace *pFace = m_Faces.Element(i);
			CMapSolid *pSolid = (CMapSolid *)pFace->GetParent();
			UpdateDependency(pSolid, NULL);
		}
	}

	return(this);
}


//-----------------------------------------------------------------------------
// Purpose: Searches for a face with the given unique ID in the list of objects.
//			FIXME: should this be in CMapObjectList?
// Input  : nFaceID - Face ID to search for.
//			List - List of objects to search.
// Output : Returns the face with the given ID if it was found, NULL if not.
//-----------------------------------------------------------------------------
CMapFace *CMapSideList::FindFaceIDInList(int nFaceID, CMapObjectList &List)
{
	POSITION pos = List.GetHeadPosition();
	while (pos != NULL)
	{
		//
		// If this object is a solid, look for the face there.
		//
		CMapClass *pObject = List.GetNext(pos);
		CMapSolid *pSolid = dynamic_cast <CMapSolid *>(pObject);
		if (pSolid != NULL)
		{
			CMapFace *pFace = pSolid->FindFaceID(nFaceID);
			if (pFace != NULL)
			{
				return(pFace);
			}
		}

		//
		// Check all of this object's solid children.
		//
		EnumChildrenPos_t pos2;
		CMapClass *pChild = pObject->GetFirstDescendent(pos2);
		while (pChild != NULL)
		{
			pSolid = dynamic_cast <CMapSolid *>(pChild);
			if (pSolid != NULL)
			{
				CMapFace *pFace = pSolid->FindFaceID(nFaceID);
				if (pFace != NULL)
				{
					return(pFace);
				}
			}	

			pChild = pObject->GetNextDescendent(pos2);
		}
	}

	return(NULL);
}


//-----------------------------------------------------------------------------
// Purpose: Returns the total approximate memory consumed by this object.
//-----------------------------------------------------------------------------
size_t CMapSideList::GetSize(void)
{
	return(sizeof(this) + m_Faces.Count() * sizeof(CMapFace *));
}


//-----------------------------------------------------------------------------
// Purpose: Notification that we have just been cloned. Fix up our clone's
//			face list based on the the objects that were cloned with it.
// Input  : pClone - 
//			OriginalList - 
//			NewList - 
//-----------------------------------------------------------------------------
void CMapSideList::OnClone(CMapClass *pCloneObject, CMapWorld *pWorld, CMapObjectList &OriginalList, CMapObjectList &NewList)
{
	ReplaceFacesInCopy((CMapSideList *)pCloneObject, OriginalList, NewList);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pCopy - 
//			pSourceWorld - 
//			pDestWorld - 
//			OriginalList - 
//			NewList - 
//-----------------------------------------------------------------------------
void CMapSideList::OnPaste(CMapClass *pCopyObject, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, CMapObjectList &OriginalList, CMapObjectList &NewList)
{
	CMapSideList *pCopy = (CMapSideList *)pCopyObject;

	//
	// HACK: This is kinda nasty. Build a list of face IDs from our parent keyvalue so
	// we can remap them to faces in the clipboard.
	//
	CMapEntity *pParent = dynamic_cast <CMapEntity *>(pCopy->GetParent());
	const char *pszValue = pParent->GetKeyValue(m_szKeyName);
	if (pszValue != NULL)
	{
		char szVal[KEYVALUE_MAX_VALUE_LENGTH];
		strcpy(szVal, pszValue);

		char *psz = strtok(szVal, " ");
		while (psz != NULL)
		{
			//
			// The substring should now be a single face ID. Get the corresponding
			// face and add it to the list.
			//
			int nFaceID = atoi(psz);
			CMapFace *pFace = FindFaceIDInList(nFaceID, OriginalList);
			if (pFace != NULL)
			{
				pCopy->m_Faces.AddToTail(pFace);
			}

			//
			// Get the next substring.
			//
			psz = strtok(NULL, " ");
		}
	}

	//
	// Now replace all faces with their new counterparts, as in Clone.
	//
	ReplaceFacesInCopy(pCopy, OriginalList, NewList);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pObject - 
//			eNotifyType - 
//-----------------------------------------------------------------------------
void CMapSideList::OnNotifyDependent(CMapClass *pObject, Notify_Dependent_t eNotifyType)
{
	if (eNotifyType == Notify_Removed)
	{
		//
		// Check for a solid that we refer to via face ID going away.
		//
		CMapSolid *pSolid = dynamic_cast<CMapSolid *>(pObject);
		if ((pSolid != NULL) && (m_Faces.Count() > 0))
		{
			//
			// Remove faces from our list that are in this solid.
			// Do it backwards so we can remove them as we go. Also, add
			// the face IDs to our list of lost IDs so that we can reacquire
			// the face in our list if the solid comes back later.
			//
			for (int i = m_Faces.Count() - 1; i >= 0; i--)
			{
				CMapFace *pFace = m_Faces.Element(i);
				if (pFace != NULL)
				{
					CMapSolid *pParent = (CMapSolid *)pFace->GetParent();
					if (pParent == pSolid)
					{
						m_LostFaceIDs.AddToTail(pFace->GetFaceID());
						m_Faces.FastRemove(i);
					}
				}
			}
		
			//
			// Submit the updated face list to our parent entity.
			//
			UpdateParentKey();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : key - 
//			value - 
//-----------------------------------------------------------------------------
void CMapSideList::OnParentKeyChanged(char const *pszKey, char const *pszValue)
{
	CMapWorld *pWorld = GetWorldObject(this);
	if (pWorld == NULL)
	{
		// We're probably being copied into the clipboard.
		return;
	}

	//
	// Update our face list if the key we care about is changing.
	//
	if (!stricmp(pszKey, m_szKeyName))
	{
		BuildFaceListForValue(pszValue, pWorld);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called just after this object has been removed from the world so
//			that it can unlink itself from other objects in the world.
// Input  : pWorld - The world that we were just removed from.
//			bNotifyChildren - Whether we should forward notification to our children.
//-----------------------------------------------------------------------------
void CMapSideList::OnRemoveFromWorld(CMapWorld *pWorld, bool bNotifyChildren)
{
	CMapClass::OnRemoveFromWorld(pWorld, bNotifyChildren);

	for (int i = 0; i < m_Faces.Count(); i++)
	{
		CMapFace *pFace = m_Faces.Element(i);
		CMapSolid *pSolid = (CMapSolid *)pFace->GetParent();
		UpdateDependency(pSolid, NULL);
	}
	
	m_Faces.RemoveAll();
}


//-----------------------------------------------------------------------------
// Purpose: Called after the entire map has been loaded. This allows the object
//			to perform any linking with other map objects or to do other operations
//			that require all world objects to be present.
// Input  : pWorld - The world that we are in.
//-----------------------------------------------------------------------------
void CMapSideList::PostloadWorld(CMapWorld *pWorld)
{
	CMapClass::PostloadWorld(pWorld);

	//
	// Generate our list of faces from our parent's keyvalue.
	//
	CMapEntity *pEntity = dynamic_cast<CMapEntity *>(Parent);
	if (pEntity != NULL)
	{
		const char *pszValue = pEntity->GetKeyValue(m_szKeyName);
		if (pszValue != NULL)
		{
			BuildFaceListForValue(pszValue, pWorld);
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : List - 
//-----------------------------------------------------------------------------
void CMapSideList::RemoveFacesNotInList(CMapObjectList &List)
{
	if (m_Faces.Count() > 0)
	{
		for (int i = m_Faces.Count() - 1; i >= 0; i--)
		{
			CMapFace *pFace = m_Faces.Element(i);

			if (FindFaceIDInList(pFace->GetFaceID(), List) == NULL)
			{
				CMapSolid *pSolid = (CMapSolid *)pFace->GetParent();
				UpdateDependency(pSolid, NULL);
				m_Faces.FastRemove(i);
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called from OnClone and OnPaste. Replaces references (in the
//			cloned object) to faces in the original list of objects with references
//			to corresponding faces in the new list of objects.
// Input  : pClone - 
//			OriginalList - 
//			NewList - 
//-----------------------------------------------------------------------------
void CMapSideList::ReplaceFacesInCopy(CMapSideList *pCopy, CMapObjectList &OriginalList, CMapObjectList &NewList)
{
	POSITION pos1 = OriginalList.GetHeadPosition();
	POSITION pos2 = NewList.GetHeadPosition();
	while (pos1 != NULL)
	{
		ASSERT(pos2 != NULL);

		CMapClass *pOriginal = OriginalList.GetNext(pos1);
		CMapClass *pNew = NewList.GetNext(pos2);

		if (pOriginal != this)
		{
			//
			// Check to see if these two objects are solids.
			//
			CMapSolid *pOrigSolid = dynamic_cast <CMapSolid *>(pOriginal);
			if (pOrigSolid != NULL)
			{
				CMapSolid *pNewSolid = dynamic_cast <CMapSolid *>(pNew);
				ASSERT(pNewSolid != NULL);

				if (pNewSolid != NULL)
				{
					pCopy->ReplaceSolidFaces(pOrigSolid, pNewSolid);
				}
			}

			//
			// Check all of these objects' children.
			//
			EnumChildrenPos_t e1;
			EnumChildrenPos_t e2;

			CMapClass *pOrigChild = pOriginal->GetFirstDescendent(e1);
			CMapClass *pNewChild = pNew->GetFirstDescendent(e2);

			while (pOrigChild != NULL)
			{
				ASSERT(pNewChild != NULL);

				pOrigSolid = dynamic_cast <CMapSolid *>(pOrigChild);
				if (pOrigSolid != NULL)
				{
					CMapSolid *pNewSolid = dynamic_cast <CMapSolid *>(pNewChild);
					ASSERT(pNewSolid != NULL);

					if (pNewSolid != NULL)
					{
						pCopy->ReplaceSolidFaces(pOrigSolid, pNewSolid);
					}
				}

				pOrigChild = pOriginal->GetNextDescendent(e1);
				pNewChild = pNew->GetNextDescendent(e2);
			}
		}
	}
	
	//
	// Update the keyvalue in the copy's parent entity.
	//
	pCopy->UpdateParentKey();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pRender - Interface to use for rendering.
//-----------------------------------------------------------------------------
void CMapSideList::Render2D(CRender2D *pRender)
{
}


//-----------------------------------------------------------------------------
// Purpose: Renders us in the 3D view.
// Input  : pRender - Interface to use for rendering.
//-----------------------------------------------------------------------------
void CMapSideList::Render3D(CRender3D *pRender)
{
	if (Parent->IsSelected())
	{
		//
		// Draw lines from us to the center of all faces in the list.
		//
		pRender->SetRenderMode(RENDER_MODE_WIREFRAME);

		CMeshBuilder meshBuilder;
		IMesh *pMesh = MaterialSystemInterface()->GetDynamicMesh();

		meshBuilder.Begin(pMesh, MATERIAL_LINES, m_Faces.Count());

		for (int i = 0; i < m_Faces.Count(); i++)
		{
			CMapFace *pFace = m_Faces.Element(i);

			Vector Center;
			pFace->GetCenter(Center);

			unsigned char color[3];
			color[0] = SELECT_EDGE_RED; 
			color[1] = SELECT_EDGE_GREEN;
			color[2] = SELECT_EDGE_BLUE;

			meshBuilder.Color3ubv( color );
			meshBuilder.Position3f(m_Origin.x, m_Origin.y, m_Origin.z);
			meshBuilder.AdvanceVertex();

			meshBuilder.Color3ubv( color );
			meshBuilder.Position3f(Center.x, Center.y, Center.z);
			meshBuilder.AdvanceVertex();
		}

		meshBuilder.End();
		pMesh->Draw();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called from OnClone and OnPaste, updates references to face IDs 
//			in the one solid with references to corresponding face IDs in
//			another solid.
// Input  : pOrigSolid - Solid with faces to find.
//			pNewSolid - Solid with faces to replace with.
// Output : Returns true if it replaced at least one face.
//-----------------------------------------------------------------------------
bool CMapSideList::ReplaceSolidFaces(CMapSolid *pOrigSolid, CMapSolid *pNewSolid)
{
	bool bDidSomething = false;
	for (int i = 0; i < pOrigSolid->GetFaceCount(); i++)
	{
		CMapFace *pFace = pOrigSolid->GetFace(i);

		int nIndex = m_Faces.FindFaceID(pFace->GetFaceID());
		if (nIndex != -1)
		{
			//
			// Replace the element in our face list and unlink
			// us from the original solid, relinking us to the new solid.
			//
			m_Faces.Element(nIndex) = pNewSolid->GetFace(i);
			UpdateDependency(pOrigSolid, pNewSolid);
			bDidSomething = true;
		}
	}

	return(bDidSomething);
}


//-----------------------------------------------------------------------------
// Purpose: Something happened in the world that requires us to refresh our
//			dependencies. Try to reacquire face IDs in our deleted faces list.
// Input  : pWorld - 
//-----------------------------------------------------------------------------
void CMapSideList::UpdateDependencies(CMapWorld *pWorld, CMapClass *pObject)
{
	CMapClass::UpdateDependencies(pWorld, pObject);

	//
	// See if it is a solid that holds faces in our lost faces list.
	//
	CMapSolid *pSolid = dynamic_cast <CMapSolid *>(pObject);
	if ((pSolid != NULL) && (m_LostFaceIDs.Count() > 0))
	{
		//
		// Walk the list backwards so we can remove as we go.
		//
		for (int i = m_LostFaceIDs.Count() - 1; i >= 0; i--)
		{
			int nFaceID = m_LostFaceIDs.Element(i);

			CMapFace *pFace = pSolid->FindFaceID(nFaceID);
			if (pFace != NULL)
			{
				if (m_Faces.Find(pFace) == -1)
				{
					m_Faces.AddToTail(pFace);
				}

				//
				// We've reacquired the face, so it's no longer lost.
				//
				m_LostFaceIDs.FastRemove(i);
			}
		}

		UpdateParentKey();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Builds a new value string for our parent's facelist key from our
//			current list of faces and submits the keyvalue to our parent for
//			storage.
//-----------------------------------------------------------------------------
void CMapSideList::UpdateParentKey(void)
{
	char szValue[KEYVALUE_MAX_VALUE_LENGTH];
	CMapWorld::FaceID_FaceListsToString(szValue, sizeof(szValue), &m_Faces, NULL);

	CMapEntity *pEntity = dynamic_cast<CMapEntity *>(Parent);
	if (pEntity != NULL)
	{
		pEntity->NotifyChildKeyChanged(this, m_szKeyName, szValue);
	}
}


