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

#include "stdafx.h"
#include "GameConfig.h"
#include "GlobalFunctions.h"
#include "History.h"
#include "MainFrm.h"
#include "MapCheckDlg.h"
#include "MapDoc.h"
#include "MapEntity.h"
#include "MapSolid.h"
#include "MapWorld.h"
#include "Options.h"
#include "ToolManager.h"
#include "VisGroup.h"
#include "hammer.h"
#include "MapOverlay.h"
#include "Selection.h"

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


// ********
// NOTE: Make sure the order matches g_MapErrorStringIDs below!
// ********
typedef enum
{
	ErrorNoPlayerStart,
	ErrorMixedFace,
	ErrorDuplicatePlanes,
	ErrorMissingTarget,
	ErrorInvalidTexture,
	ErrorSolidStructure,
	ErrorUnusedKeyvalues,
	ErrorEmptyEntity,
	ErrorDuplicateKeys,
	ErrorSolidContents,
	ErrorInvalidTextureAxes,
	ErrorDuplicateFaceIDs,
	ErrorDuplicateNodeIDs,
	ErrorBadConnections,
	ErrorHiddenGroupHiddenChildren,
	ErrorHiddenGroupVisibleChildren,
	ErrorHiddenGroupMixedChildren,
	ErrorHiddenObjectNoVisGroup,
	ErrorHiddenChildOfEntity,
	ErrorIllegallyHiddenObject,
	ErrorKillInputRaceCondition,
	ErrorOverlayFaceList,
} MapErrorType;

// ********
// NOTE: Make sure the order matches MapErrorType above!
// ********
struct
{
	int	m_StrResourceID;
	int m_DescriptionResourceID;
} g_MapErrorStrings[] =
{
	{IDS_NOPLAYERSTART,					IDS_NOPLAYERSTART_DESC},
	{IDS_MIXEDFACES,					IDS_MIXEDFACES_DESC},
	{IDS_DUPLICATEPLANES,				IDS_DUPLICATEPLANES_DESC},
	{IDS_UNMATCHEDTARGET,				IDS_UNMATCHEDTARGET_DESC},
	{IDS_INVALIDTEXTURE,				IDS_INVALIDTEXTURE_DESC},
	{IDS_SOLIDSTRUCTURE,				IDS_SOLIDSTRUCTURE_DESC},
	{IDS_UNUSEDKEYVALUES,				IDS_UNUSEDKEYVALUES_DESC},
	{IDS_EMPTYENTITY,					IDS_EMPTYENTITY_DESC},
	{IDS_DUPLICATEKEYS,					IDS_DUPLICATEKEYS_DESC},
	{IDS_SOLIDCONTENT,					IDS_SOLIDCONTENT_DESC},
	{IDS_INVALIDTEXTUREAXES,			IDS_INVALIDTEXTUREAXES_DESC},
	{IDS_DUPLICATEFACEID,				IDS_DUPLICATEFACEID_DESC},
	{IDS_DUPLICATE_NODE_ID,				IDS_DUPLICATE_NODE_ID_DESC},
	{IDS_BAD_CONNECTIONS,				IDS_BAD_CONNECTIONS_DESC},
	{IDS_HIDDEN_GROUP_HIDDEN_CHILDREN,	IDS_HIDDEN_GROUP_HIDDEN_CHILDREN_DESC},
	{IDS_HIDDEN_GROUP_VISIBLE_CHILDREN, IDS_HIDDEN_GROUP_VISIBLE_CHILDREN_DESC},
	{IDS_HIDDEN_GROUP_MIXED_CHILDREN,	IDS_HIDDEN_GROUP_MIXED_CHILDREN_DESC},
	{IDS_HIDDEN_NO_VISGROUP,			IDS_HIDDEN_NO_VISGROUP_DESC},
	{IDS_HIDDEN_CHILD_OF_ENTITY,		IDS_HIDDEN_CHILD_OF_ENTITY_DESC},
	{IDS_HIDDEN_ILLEGALLY,				IDS_HIDDEN_ILLEGALLY_DESC},
	{IDS_KILL_INPUT_RACE_CONDITION,		IDS_KILL_INPUT_RACE_CONDITION_DESC},
	{IDS_BAD_OVERLAY,					IDS_DAB_OVERLAY_DESC}
};



typedef enum
{
	CantFix,
	NeedsFix,
	Fixed,
} FIXCODE;


struct MapError
{
	CMapClass *pObjects[3];
	MapErrorType Type;
	DWORD dwExtra;
	FIXCODE Fix;
};


//
// Fix functions.
//
static void FixDuplicatePlanes(MapError *pError);
static void FixSolidStructure(MapError *pError);
static void FixInvalidTexture(MapError *pError);
static void FixInvalidTextureAxes(MapError *pError);
static void FixUnusedKeyvalues(MapError *pError);
static void FixEmptyEntity(MapError *pError);
static void FixBadConnections(MapError *pError);
static void FixInvalidContents(MapError *pError);
static void FixDuplicateFaceIDs(MapError *pError);
static void FixDuplicateNodeIDs(MapError *pError);
static void FixMissingTarget(MapError *pError);
void FixHiddenObject(MapError *pError);
static void FixKillInputRaceCondition(MapError *pError);
static void FixOverlayFaceList(MapError *pError);


CMapCheckDlg *s_pDlg = NULL;


BEGIN_MESSAGE_MAP(CMapCheckDlg, CDialog)
	//{{AFX_MSG_MAP(CMapCheckDlg)
	ON_BN_CLICKED(IDC_GO, OnGo)
	ON_LBN_SELCHANGE(IDC_ERRORS, OnSelchangeErrors)
	ON_LBN_DBLCLK(IDC_ERRORS, OnDblClkErrors)
	ON_WM_PAINT()
	ON_BN_CLICKED(IDC_FIX, OnFix)
	ON_BN_CLICKED(IDC_FIXALL, OnFixall)
	ON_WM_DESTROY()
	ON_WM_CLOSE()
	ON_BN_CLICKED(IDC_CHECK_VISIBLE_ONLY, OnCheckVisibleOnly)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


//-----------------------------------------------------------------------------
// Visibility check
//-----------------------------------------------------------------------------
inline bool IsCheckVisible( CMapClass *pClass )
{
	return (Options.general.bCheckVisibleMapErrors == FALSE) || pClass->IsVisible();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::CheckForProblems(CWnd *pwndParent)
{
	if (!s_pDlg)
	{
		s_pDlg = new CMapCheckDlg;
		s_pDlg->Create(IDD, pwndParent);
	}

	if (!s_pDlg->DoCheck())
	{
		// Found problems.
		s_pDlg->ShowWindow(SW_SHOW);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pParent - 
//-----------------------------------------------------------------------------
CMapCheckDlg::CMapCheckDlg(CWnd *pParent)
	: CDialog(CMapCheckDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CMapCheckDlg)
	m_bCheckVisible = FALSE;
	//}}AFX_DATA_INIT

	m_bCheckVisible = Options.general.bCheckVisibleMapErrors;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pDX - 
//-----------------------------------------------------------------------------
void CMapCheckDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);

	//{{AFX_DATA_MAP(CMapCheckDlg)
	DDX_Control(pDX, IDC_FIXALL, m_cFixAll);
	DDX_Control(pDX, IDC_FIX, m_Fix);
	DDX_Control(pDX, IDC_GO, m_Go);
	DDX_Control(pDX, IDC_DESCRIPTION, m_Description);
	DDX_Control(pDX, IDC_ERRORS, m_Errors);
	DDX_Check(pDX, IDC_CHECK_VISIBLE_ONLY, m_bCheckVisible);
	//}}AFX_DATA_MAP

	if ( pDX->m_bSaveAndValidate )
	{
		Options.general.bCheckVisibleMapErrors = m_bCheckVisible;
	}
}


//-----------------------------------------------------------------------------
// Checkbox indicating whether we should check visible errors
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnCheckVisibleOnly()
{
	UpdateData( TRUE );
	DoCheck();
}


//-----------------------------------------------------------------------------
// Purpose: Selects the current error objects and centers the views on it.
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnGo() 
{
	GotoSelectedErrors();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::GotoSelectedErrors()
{
	int iSel = m_Errors.GetCurSel();
	if (iSel == LB_ERR)
	{
		return;
	}

	ToolManager()->SetTool(TOOL_POINTER);

	CMapObjectList Objects;
	for (int i = 0; i < m_Errors.GetCount(); i++)
	{
		if (m_Errors.GetSel(i) > 0)
		{
			MapError *pError = (MapError *)m_Errors.GetItemDataPtr(i);
			if (pError)
			{
				Objects.AddToTail(pError->pObjects[0]);
			}
		}
	}

	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();

	pDoc->SelectObjectList(&Objects);
	pDoc->CenterViewsOnSelection();
}


//-----------------------------------------------------------------------------
// Purpose: Fixes all the selected errors.
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnFix() 
{
	int iSel = m_Errors.GetCurSel();
	if (iSel == LB_ERR)
	{
		return;
	}

	UpdateBox ub;
	CMapObjectList Objects;
	ub.Objects = &Objects;

	for (int i = 0; i < m_Errors.GetCount(); i++)
	{
		if (m_Errors.GetSel(i) > 0)
		{
			MapError *pError = (MapError *)m_Errors.GetItemDataPtr(i);
			if (pError)
			{
				Fix(pError, ub);
			}
		}
	}

	OnSelchangeErrors();
	CMapDoc::GetActiveMapDoc()->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pError - 
//			ub - 
//-----------------------------------------------------------------------------
void CMapCheckDlg::Fix(MapError *pError, UpdateBox &ub)
{
	CMapDoc::GetActiveMapDoc()->SetModifiedFlag();

	if (pError->Fix != NeedsFix)
	{
		// should never get here because this button is supposed 
		//  to be disabled if the error cannot be fixed
		return;
	}

	//
	// Expand the bounds of the update region to include the broken objects.
	//
	for (int i = 0; i < 2; i++)
	{
		if (!pError->pObjects[i])
		{
			continue;
		}

		ub.Objects->AddToTail(pError->pObjects[i]);

		Vector mins;
		Vector maxs;
		pError->pObjects[i]->GetRender2DBox(mins, maxs);
		ub.Box.UpdateBounds(mins, maxs);
	}

	//
	// Perform the fix.
	//
	switch (pError->Type)
	{
		case ErrorDuplicatePlanes:
		{
			FixDuplicatePlanes(pError);
			break;
		}
		case ErrorDuplicateFaceIDs:
		{
			FixDuplicatePlanes(pError);
			break;
		}
		case ErrorDuplicateNodeIDs:
		{
			FixDuplicateNodeIDs(pError);
			break;
		}
		case ErrorMissingTarget:
		{
			FixMissingTarget(pError);
			break;
		}
		case ErrorSolidStructure:
		{
			FixSolidStructure(pError);
			break;
		}
		case ErrorSolidContents:
		{
			FixInvalidContents(pError);
			break;
		}
		case ErrorInvalidTexture:
		{
			FixInvalidTexture(pError);
			break;
		}
		case ErrorInvalidTextureAxes:
		{
			FixInvalidTextureAxes(pError);
			break;
		}
		case ErrorUnusedKeyvalues:
		{
			FixUnusedKeyvalues(pError);
			break;
		}
		case ErrorBadConnections:
		{
			FixBadConnections(pError);
			break;
		}
		case ErrorEmptyEntity:
		{
			FixEmptyEntity(pError);
			break;
		}
		case ErrorHiddenGroupVisibleChildren:
		case ErrorHiddenGroupMixedChildren:
		case ErrorHiddenGroupHiddenChildren:
		case ErrorHiddenObjectNoVisGroup:
		case ErrorHiddenChildOfEntity:
		case ErrorIllegallyHiddenObject:
		{
			FixHiddenObject(pError);
			break;
		}
		case ErrorKillInputRaceCondition:
		{
			FixKillInputRaceCondition(pError);
			break;
		}
		case ErrorOverlayFaceList:
		{
			FixOverlayFaceList( pError );
			break;
		}
	}

	pError->Fix = Fixed;

	//
	// Expand the bounds of the update region to include the fixed objects.
	//
	for (int i = 0; i < 2; i++)
	{
		if (!pError->pObjects[i])
		{
			continue;
		}

		Vector mins;
		Vector maxs;
		pError->pObjects[i]->GetRender2DBox(mins, maxs);
		ub.Box.UpdateBounds(mins, maxs);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnFixall() 
{
	int iSel = m_Errors.GetCurSel();
	if (iSel == LB_ERR)
	{
		return;
	}

	MapError *pError = (MapError *) m_Errors.GetItemDataPtr(iSel);

	if (pError->Fix == CantFix)
	{
		// should never get here because this button is supposed 
		//  to be disabled if the error cannot be fixed
		return;
	}

	UpdateBox ub;
	CMapObjectList Objects;
	ub.Objects = &Objects;

	// For every selected error...
	for (int i = 0; i < m_Errors.GetCount(); i++)
	{
		if (m_Errors.GetSel(i) > 0)
		{
			pError = (MapError *)m_Errors.GetItemDataPtr(i);
			if ((pError) && (pError->Fix == NeedsFix))
			{
				// Find and fix every error of the same type.
				for (int j = 0; j < m_Errors.GetCount(); j++)
				{
					MapError *pError2 = (MapError *)m_Errors.GetItemDataPtr(j);
					if ((pError2->Type != pError->Type) || (pError2->Fix != NeedsFix))
					{
						continue;
					}

					Fix(pError2, ub);
				}
			}
		}
	}

	OnSelchangeErrors();
	CMapDoc::GetActiveMapDoc()->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnSelchangeErrors() 
{
	// change description to match error
	int iSel = m_Errors.GetCurSel();

	if(iSel == LB_ERR)
	{
		m_Fix.EnableWindow(FALSE);
		m_cFixAll.EnableWindow(FALSE);
		m_Go.EnableWindow(FALSE);
	}

	CString str;
	MapError *pError;
	pError = (MapError*) m_Errors.GetItemDataPtr(iSel);

	// Figure out which error string we're using.
	int iErrorStr = (int)pError->Type;
	iErrorStr = clamp( iErrorStr, 0, ARRAYSIZE( g_MapErrorStrings ) - 1 );
	Assert( iErrorStr == (int)pError->Type );
	
	str.LoadString(g_MapErrorStrings[iErrorStr].m_DescriptionResourceID);
	m_Description.SetWindowText(str);

	m_Go.EnableWindow(pError->pObjects[0] != NULL);

	// set state of fix button
	m_Fix.EnableWindow(pError->Fix == NeedsFix);
	m_cFixAll.EnableWindow(pError->Fix != CantFix);

	// set text of fix button
	switch (pError->Fix)
	{
		case NeedsFix:
			m_Fix.SetWindowText("&Fix");
			break;
		case CantFix:
			m_Fix.SetWindowText("Can't fix");
			break;
		case Fixed:
			m_Fix.SetWindowText("(fixed)");
			break;
	}

	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();

	pDoc->GetSelection()->SetMode(selectObjects);
	
	if (pError->pObjects[0])
	{
		pDoc->SelectObject(pError->pObjects[0], scClear|scSelect|scSaveChanges );
	}
	else
	{
		pDoc->SelectObject(NULL, scClear|scSaveChanges );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnDblClkErrors() 
{
	GotoSelectedErrors();	
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::KillErrorList()
{
	// delete items in list.. their data ptrs are allocated objects
	int iSize = m_Errors.GetCount();
	for(int i = 0; i < iSize; i++)
	{
		MapError *pError = (MapError*) m_Errors.GetItemDataPtr(i);
		delete pError;
	}

	m_Errors.ResetContent();
}


//-----------------------------------------------------------------------------
// Purpose: Builds the listbox string for the given error and adds it to the list.
//-----------------------------------------------------------------------------
static void AddErrorToListBox(CListBox *pList, MapError *pError)
{
	CString str;

	// Figure out which error string we're using.
	int iErrorStr = (int)pError->Type;
	iErrorStr = clamp( iErrorStr, 0, ARRAYSIZE( g_MapErrorStrings ) - 1 );
	Assert( iErrorStr == (int)pError->Type );
	
	str.LoadString(g_MapErrorStrings[iErrorStr].m_StrResourceID);

	if (str.Find('%') != -1)
	{
		if (pError->Type == ErrorUnusedKeyvalues)
		{
			// dwExtra has the name of the string in it
			CString str2 = str;
			CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0];
			str.Format(str2, pEntity->GetClassName(), pError->dwExtra);
		}
		else
		{
			CString str2 = str;
			str.Format(str2, pError->dwExtra);
		}
	}

	int iIndex = pList->AddString(str);
	pList->SetItemDataPtr(iIndex, (PVOID)pError);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pList - 
//			Type - 
//			dwExtra - 
//			... - 
//-----------------------------------------------------------------------------
static void AddError(CListBox *pList, MapErrorType Type, DWORD dwExtra, ...)
{
	MapError *pError = new MapError;
	memset(pError, 0, sizeof(MapError));

	pError->Type = Type;
	pError->dwExtra = dwExtra;
	pError->Fix = CantFix;

	va_list vl;
	va_start(vl, dwExtra);

	//
	// Get the object pointer from the variable argument list.
	//
	switch (Type)
	{
		case ErrorNoPlayerStart:
		{
			// no objects.
			break;
		}

		case ErrorMixedFace:
		case ErrorMissingTarget:
		case ErrorDuplicatePlanes:
		case ErrorDuplicateFaceIDs:
		case ErrorDuplicateNodeIDs:
		case ErrorSolidStructure:
		case ErrorSolidContents:
		case ErrorInvalidTexture:
		case ErrorUnusedKeyvalues:
		case ErrorBadConnections:
		case ErrorEmptyEntity:
		case ErrorDuplicateKeys:
		case ErrorInvalidTextureAxes:
		case ErrorHiddenGroupHiddenChildren:
		case ErrorHiddenGroupVisibleChildren:
		case ErrorHiddenGroupMixedChildren:
		case ErrorHiddenObjectNoVisGroup:
		case ErrorHiddenChildOfEntity:
		case ErrorIllegallyHiddenObject:
		case ErrorOverlayFaceList:
		{
			pError->pObjects[0] = va_arg(vl, CMapClass *);
			break;
		}

		case ErrorKillInputRaceCondition:
		{
			pError->pObjects[0] = va_arg(vl, CMapClass *);
			pError->dwExtra = (DWORD)va_arg(vl, CEntityConnection *);
			break;
		}
	}

	//
	// Set the can fix flag.
	//
	switch (Type)
	{
		case ErrorSolidContents:
		case ErrorDuplicatePlanes:
		case ErrorDuplicateFaceIDs:
		case ErrorDuplicateNodeIDs:
		case ErrorSolidStructure:
		case ErrorInvalidTexture:
		case ErrorUnusedKeyvalues:
		case ErrorMissingTarget:
		case ErrorBadConnections:
		case ErrorEmptyEntity:
		case ErrorDuplicateKeys:
		case ErrorInvalidTextureAxes:
		case ErrorHiddenGroupHiddenChildren:
		case ErrorHiddenGroupVisibleChildren:
		case ErrorHiddenGroupMixedChildren:
		case ErrorHiddenObjectNoVisGroup:
		case ErrorHiddenChildOfEntity:
		case ErrorIllegallyHiddenObject:
		case ErrorKillInputRaceCondition:
		case ErrorOverlayFaceList:
		{
			pError->Fix = NeedsFix;
			break;
		}
	}

	va_end(vl);

	AddErrorToListBox(pList, pError);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pObject - 
//			DWORD - 
// Output : 
//-----------------------------------------------------------------------------
static BOOL FindPlayer(CMapEntity *pObject, DWORD)
{
	if ( !IsCheckVisible( pObject ) )
		return TRUE;

	if (pObject->IsPlaceholder() && pObject->IsClass("info_player_start"))
	{
		return(FALSE);
	}
	return(TRUE);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pList - 
//			pWorld - 
//-----------------------------------------------------------------------------
static void CheckRequirements(CListBox *pList, CMapWorld *pWorld)
{
	// ensure there's a player start .. 
	if (pWorld->EnumChildren((ENUMMAPCHILDRENPROC)FindPlayer, 0, MAPCLASS_TYPE(CMapEntity)))
	{
		// if rvl is !0, it was not stopped prematurely.. which means there is 
		// NO player start.
		AddError(pList, ErrorNoPlayerStart, 0);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pSolid - 
//			pList - 
// Output : 
//-----------------------------------------------------------------------------
static BOOL _CheckMixedFaces(CMapSolid *pSolid, CListBox *pList)
{
	if ( !IsCheckVisible( pSolid ) )
		return TRUE;

	// run thru faces..
	int iFaces = pSolid->GetFaceCount();
	int iSolid = 2;	// start off ambivalent
	int i;
	for(i = 0; i < iFaces; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);

		char ch = pFace->texture.texture[0];
		if((ch == '*' && iSolid == 1) || (ch != '*' && iSolid == 0))
		{
			break;
		}
		else iSolid = (ch == '*') ? 0 : 1;
	}

	if(i == iFaces)	// all ok
		return TRUE;

	// NOT ok
	AddError(pList, ErrorMixedFace, 0, pSolid);

	return TRUE;
}


static void CheckMixedFaces(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckMixedFaces, (DWORD)pList, MAPCLASS_TYPE(CMapSolid));
}


//-----------------------------------------------------------------------------
// Purpose: Returns true if there is another node entity in the world with the
//			same node ID as the given entity.
// Input  : pNode - 
//			pWorld - 
//-----------------------------------------------------------------------------
bool FindDuplicateNodeID(CMapEntity *pNode, CMapWorld *pWorld)
{
	if ( !IsCheckVisible( pNode ) )
		return false;

	EnumChildrenPos_t pos;
	CMapClass *pChild = pWorld->GetFirstDescendent(pos);
	while (pChild != NULL)
	{
		CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pChild);
		if (pEntity && IsCheckVisible( pEntity ) && (pEntity != pNode) && pEntity->IsNodeClass())
		{
			int nNodeID1 = pNode->GetNodeID();
			int nNodeID2 = pEntity->GetNodeID();
			if ((nNodeID1 != 0) && (nNodeID2 != 0) && (nNodeID1 == nNodeID2))
			{
				return true;
			}
		}
		
		pChild = pWorld->GetNextDescendent(pos);
	}

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Checks for node entities with the same node ID.
//-----------------------------------------------------------------------------
static void CheckDuplicateNodeIDs(CListBox *pList, CMapWorld *pWorld)
{
	EnumChildrenPos_t pos;
	CMapClass *pChild = pWorld->GetFirstDescendent(pos);
	while (pChild != NULL)
	{
		CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pChild);
		if (pEntity && pEntity->IsNodeClass())
		{
			if (FindDuplicateNodeID(pEntity, pWorld))
			{
				AddError(pList, ErrorDuplicateNodeIDs, (DWORD)pWorld, pEntity);
			}
		}
		
		pChild = pWorld->GetNextDescendent(pos);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Checks for faces with identical face normals in this solid object.
// Input  : pSolid - Solid to check for duplicate faces.
// Output : Returns TRUE if the face contains at least one duplicate face,
//			FALSE if the solid contains no duplicate faces.
//-----------------------------------------------------------------------------
BOOL DoesContainDuplicates(CMapSolid *pSolid)
{
	int iFaces = pSolid->GetFaceCount();
	for (int i = 0; i < iFaces; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);
		Vector& pts1 = pFace->plane.normal;

		for (int j = 0; j < iFaces; j++)
		{
			// Don't check self.
			if (j == i)
			{
				continue;
			}

			CMapFace *pFace2 = pSolid->GetFace(j);
			Vector& pts2 = pFace2->plane.normal;

			if (pts1 == pts2)
			{
				return(TRUE);
			}
		}
	}
	
	return(FALSE);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pSolid - 
//			pList - 
// Output : 
//-----------------------------------------------------------------------------
static BOOL _CheckDuplicatePlanes(CMapSolid *pSolid, CListBox *pList)
{
	if ( !IsCheckVisible( pSolid ) )
		return TRUE;

	if (DoesContainDuplicates(pSolid))
	{
		AddError(pList, ErrorDuplicatePlanes, 0, pSolid);
	}

	return(TRUE);
}


static void CheckDuplicatePlanes(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckDuplicatePlanes, (DWORD)pList, MAPCLASS_TYPE(CMapSolid));
}


struct FindDuplicateFaceIDs_t
{
	CMapFaceList All;					// Collects all the face IDs in this map.
	CMapFaceList Duplicates;			// Collects the duplicate face IDs in this map.
};


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pSolid - 
//			pData - 
// Output : Returns TRUE to continue enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckDuplicateFaceIDs(CMapSolid *pSolid, FindDuplicateFaceIDs_t *pData)
{
	if ( !IsCheckVisible( pSolid ) )
		return TRUE;

	int nFaceCount = pSolid->GetFaceCount();
	for (int i = 0; i < nFaceCount; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);
		if (pData->All.FindFaceID(pFace->GetFaceID()) != -1)
		{
			if (pData->Duplicates.FindFaceID(pFace->GetFaceID()) != -1)
			{
				pData->Duplicates.AddToTail(pFace);
			}
		}
		else
		{
			pData->All.AddToTail(pFace);
		}
	}

	return(TRUE);
}


//-----------------------------------------------------------------------------
// Purpose: Reports errors for all faces with duplicate face IDs.
// Input  : pList - 
//			pWorld -  
//-----------------------------------------------------------------------------
static void CheckDuplicateFaceIDs(CListBox *pList, CMapWorld *pWorld)
{
	FindDuplicateFaceIDs_t Lists;
	Lists.All.SetGrowSize(128);
	Lists.Duplicates.SetGrowSize(128);

	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckDuplicateFaceIDs, (DWORD)&Lists, MAPCLASS_TYPE(CMapSolid));

	for (int i = 0; i < Lists.Duplicates.Count(); i++)
	{
		CMapFace *pFace = Lists.Duplicates.Element(i);
		AddError(pList, ErrorDuplicateFaceIDs, (DWORD)pFace, (CMapSolid *)pFace->GetParent());
	}
}


//-----------------------------------------------------------------------------
// Checks if a particular target is valid.
//-----------------------------------------------------------------------------
static void CheckValidTarget(CMapEntity *pEntity, const char *pFieldName, const char *pTargetName, CListBox *pList, bool bCheckClassNames)
{
	if (!pTargetName)
		return;

	// These procedural names are always assumed to exist.
	if (!stricmp(pTargetName, "!activator") || !stricmp(pTargetName, "!caller") || !stricmp(pTargetName, "!player") || !stricmp(pTargetName, "!self"))
		return;

	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();

	// Search by name first.
	CMapEntityList Found;
	bool bFound = pDoc->FindEntitiesByName(Found, pTargetName, (Options.general.bCheckVisibleMapErrors == TRUE));
	if (!bFound && bCheckClassNames)
	{
		// Not found, search by classname.
		bFound = pDoc->FindEntitiesByClassName(Found, pTargetName, (Options.general.bCheckVisibleMapErrors == TRUE));
	}

	if (!bFound)
	{
		// No dice, flag it as an error.
		AddError(pList, ErrorMissingTarget, (DWORD)pFieldName, pEntity);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Checks the given entity for references by name to nonexistent entities.
// Input  : pEntity - 
//			pList - 
// Output : Returns TRUE to keep enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckMissingTargets(CMapEntity *pEntity, CListBox *pList)
{
	if ( !IsCheckVisible( pEntity ) )
		return TRUE;

	GDclass *pClass = pEntity->GetClass();
	if (!pClass)
	{
		// Unknown class -- just check for target references.
		static char *pszTarget = "target";
		const char *pszValue = pEntity->GetKeyValue(pszTarget);
		CheckValidTarget(pEntity, pszTarget, pszValue, pList, false);
	}
	else
	{
		// Known class -- check all target_destination and target_name_or_class keyvalues.
		for (int i = 0; i < pClass->GetVariableCount(); i++)
		{
			GDinputvariable *pVar = pClass->GetVariableAt(i);
			if ((pVar->GetType() != ivTargetDest) && (pVar->GetType() != ivTargetNameOrClass))
				continue;

			const char *pszValue = pEntity->GetKeyValue(pVar->GetName());
			CheckValidTarget(pEntity, pVar->GetName(), pszValue, pList, (pVar->GetType() == ivTargetNameOrClass));
		}
	}

	return TRUE;
}


static void CheckMissingTargets(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckMissingTargets, (DWORD)pList, MAPCLASS_TYPE(CMapEntity));
}


//-----------------------------------------------------------------------------
// Purpose: Determines whether a solid is good or bad.
// Input  : pSolid - Solid to check.
//			pList - List box into which to place errors.
// Output : Always returns TRUE to continue enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckSolidIntegrity(CMapSolid *pSolid, CListBox *pList)
{
	if ( !IsCheckVisible( pSolid ) )
		return TRUE;

	CCheckFaceInfo cfi;
	int nFaces = pSolid->GetFaceCount();
	for (int i = 0; i < nFaces; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);

		//
		// Reset the iPoint member so results from previous faces don't carry over.
		//
		cfi.iPoint = -1;

		//
		// Check the face.
		//
		if (!pFace->CheckFace(&cfi))
		{
			AddError(pList, ErrorSolidStructure, 0, pSolid);
			break;
		}
	}

	return(TRUE);
}


static void CheckSolidIntegrity(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckSolidIntegrity, (DWORD)pList, MAPCLASS_TYPE(CMapSolid));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pSolid - 
//			pList - 
// Output : 
//-----------------------------------------------------------------------------
static BOOL _CheckSolidContents(CMapSolid *pSolid, CListBox *pList)
{
	if ( !IsCheckVisible( pSolid ) )
		return TRUE;

	CCheckFaceInfo cfi;
	int nFaces = pSolid->GetFaceCount();
	CMapFace *pFace = pSolid->GetFace(0);
	DWORD dwContents = pFace->texture.q2contents;

	for (int i = 1; i < nFaces; i++)
	{
		pFace = pSolid->GetFace(i);
		if (pFace->texture.q2contents == dwContents)
		{
			continue;
		}
		AddError(pList, ErrorSolidContents, 0, pSolid);
		break;
	}

	return TRUE;
}


static void CheckSolidContents(CListBox *pList, CMapWorld *pWorld)
{
	if (CMapDoc::GetActiveMapDoc() && CMapDoc::GetActiveMapDoc()->GetGame() && CMapDoc::GetActiveMapDoc()->GetGame()->mapformat == mfQuake2)
	{
		pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckSolidContents, (DWORD)pList, MAPCLASS_TYPE(CMapSolid));
	}
}

//-----------------------------------------------------------------------------
// Purpose: Determines if there are any invalid textures or texture axes on any
//			face of this solid. Adds an error message to the list box for each
//			error found.
// Input  : pSolid - Solid to check.
//			pList - Pointer to the error list box.
// Output : Returns TRUE.
//-----------------------------------------------------------------------------
static BOOL _CheckInvalidTextures(CMapSolid *pSolid, CListBox *pList)
{
	if ( !IsCheckVisible( pSolid ) )
		return TRUE;

	int nFaces = pSolid->GetFaceCount();
	for(int i = 0; i < nFaces; i++)
	{
		const CMapFace *pFace = pSolid->GetFace(i);

		IEditorTexture *pTex = pFace->GetTexture();
		if (pTex->IsDummy())
		{
			AddError(pList, ErrorInvalidTexture, (DWORD)pFace->texture.texture, pSolid);
			return TRUE;
		}

		if (!pFace->IsTextureAxisValid())
		{
			AddError(pList, ErrorInvalidTextureAxes, i, pSolid);
			return(TRUE);
		}
	}
	
	return(TRUE);
}


static void CheckInvalidTextures(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckInvalidTextures, (DWORD)pList, MAPCLASS_TYPE(CMapSolid));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pEntity - 
//			pList - 
// Output : 
//-----------------------------------------------------------------------------
static BOOL _CheckUnusedKeyvalues(CMapEntity *pEntity, CListBox *pList)
{
	if ( !IsCheckVisible( pEntity ) )
		return TRUE;

	if (!pEntity->IsClass() || pEntity->IsClass("multi_manager"))
	{
		return(TRUE);	// can't check if no class associated
	}

	GDclass *pClass = pEntity->GetClass();

	for (int i = pEntity->GetFirstKeyValue(); i != pEntity->GetInvalidKeyValue(); i=pEntity->GetNextKeyValue( i ) )
	{
		if (pClass->VarForName(pEntity->GetKey(i)) == NULL)
		{
			AddError(pList, ErrorUnusedKeyvalues, (DWORD)pEntity->GetKey(i), pEntity);
			return(TRUE);
		}
	}
	
	return(TRUE);
}


static void CheckUnusedKeyvalues(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckUnusedKeyvalues, (DWORD)pList, MAPCLASS_TYPE(CMapEntity));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pEntity - 
//			pList - 
// Output : 
//-----------------------------------------------------------------------------
static BOOL _CheckEmptyEntities(CMapEntity *pEntity, CListBox *pList)
{
	if ( !IsCheckVisible( pEntity ) )
		return TRUE;

	if(!pEntity->IsPlaceholder() && !pEntity->GetChildCount())
	{
		AddError(pList, ErrorEmptyEntity, (DWORD)pEntity->GetClassName(), pEntity);
	}
	
	return(TRUE);
}


static void CheckEmptyEntities(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckEmptyEntities, (DWORD)pList, MAPCLASS_TYPE(CMapEntity));
}


//-----------------------------------------------------------------------------
// Purpose: Checks the entity for bad I/O connections.
// Input  : pEntity - the entity to check
//			pList - list box that tracks the errors
// Output : Returns TRUE to keep enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckBadConnections(CMapEntity *pEntity, CListBox *pList)
{
	if ( !IsCheckVisible( pEntity ) )
		return TRUE;

	if (CEntityConnection::ValidateOutputConnections(pEntity, (Options.general.bCheckVisibleMapErrors == TRUE)) == CONNECTION_BAD)
	{
		AddError(pList, ErrorBadConnections, (DWORD)pEntity->GetClassName(), pEntity);
	}

	// TODO: Check for a "Kill" input with the same output, target, and delay as another input. This
	//		 creates a race condition in the game where the order of arrival is not guaranteed
	//int nConnCount = pEntity->Connections_GetCount();
	//for (int i = 0; i < nConnCount; i++)
	//{
	//	CEntityConnection *pConn = pEntity->Connections_Get(i);
	//	if (!stricmp(pConn->GetInputName(), "kill"))
	//	{
	//	}
	//}
	
	return TRUE;
}


static void CheckBadConnections(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckBadConnections, (DWORD)pList, MAPCLASS_TYPE(CMapEntity));
}


static bool HasVisGroupHiddenChildren(CMapClass *pObject)
{
	const CMapObjectList *pChildren = pObject->GetChildren();

	FOR_EACH_OBJ( *pChildren, pos )
	{
		if (!pChildren->Element(pos)->IsVisGroupShown())
			return true;
	}

	return false;
}


static bool HasVisGroupShownChildren(CMapClass *pObject)
{
	const CMapObjectList *pChildren = pObject->GetChildren();
	FOR_EACH_OBJ( *pChildren, pos )
	{
		if (pChildren->Element(pos)->IsVisGroupShown())
			return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Makes sure that the visgroup assignments are valid.
//-----------------------------------------------------------------------------
static BOOL _CheckVisGroups(CMapClass *pObject, CListBox *pList)
{
	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();

	// dvs: FIXME: not working yet, revisit
	// Entities cannot have hidden children.
	//CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pObject);
	//if (pEntity && HasVisGroupHiddenChildren(pEntity))
	//{
	//	AddError(pList, ErrorHiddenChildOfEntity, 0, pEntity);
	//	return TRUE;
	//}

	// Check the validity of any object that claims to be hidden by visgroups.
	if (!pObject->IsVisGroupShown())
	{
		// Groups cannot be hidden by visgroups.
		if (pObject->IsGroup())
		{
			bool bHidden = HasVisGroupHiddenChildren(pObject);
			bool bVisible = HasVisGroupShownChildren(pObject);

			if (bHidden && !bVisible)
			{
				AddError(pList, ErrorHiddenGroupHiddenChildren, 0, pObject);
			}
			else if (!bHidden && bVisible)
			{
				AddError(pList, ErrorHiddenGroupVisibleChildren, 0, pObject);
			}
			else
			{
				AddError(pList, ErrorHiddenGroupMixedChildren, 0, pObject);
			}

			return TRUE;
		}

		// Check for unanticipated objects that are hidden but forbidden from visgroup membership.
		if (!pDoc->VisGroups_ObjectCanBelongToVisGroup(pObject))
		{
			AddError(pList, ErrorIllegallyHiddenObject, 0, pObject);
			return TRUE;
		}
		
		// Hidden objects must belong to at least one visgroup.
		if (pObject->GetVisGroupCount() == 0)
		{
			AddError(pList, ErrorHiddenObjectNoVisGroup, 0, pObject);
			return TRUE;
		}
	}

	return TRUE;
}


static void CheckVisGroups(CListBox *pList, CMapWorld *pWorld)
{
	pWorld->EnumChildrenRecurseGroupsOnly((ENUMMAPCHILDRENPROC)_CheckVisGroups, (DWORD)pList);
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static BOOL _CheckOverlayFaceList( CMapEntity *pEntity, CListBox *pList )
{
	if ( !IsCheckVisible( pEntity ) )
		return TRUE;

	const CMapObjectList *pChildren = pEntity->GetChildren();

	FOR_EACH_OBJ( *pChildren, pos )
	{
		CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) );
		if ( pOverlay )
		{
			// Check to see if the overlay has assigned faces.
			if ( pOverlay->GetFaceCount() <= 0 )
			{
				AddError( pList, ErrorOverlayFaceList, 0, pEntity );
				return TRUE;
			}
		}
	}

	return TRUE;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static void CheckOverlayFaceList( CListBox *pList, CMapWorld *pWorld )
{
	pWorld->EnumChildren( ( ENUMMAPCHILDRENPROC )_CheckOverlayFaceList, ( DWORD )pList, MAPCLASS_TYPE( CMapEntity ));
}

//
// ** FIX FUNCTIONS
//
static void FixDuplicatePlanes(MapError *pError)
{
	// duplicate planes in pObjects[0]
	// run thru faces..

	CMapSolid *pSolid = (CMapSolid*) pError->pObjects[0];

ReStart:
	int iFaces = pSolid->GetFaceCount();
	for(int i = 0; i < iFaces; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);
		Vector& pts1 = pFace->plane.normal;
		for (int j = 0; j < iFaces; j++)
		{
			// Don't check self
			if (j == i)
			{
				continue;
			}

			CMapFace *pFace2 = pSolid->GetFace(j);
			Vector& pts2 = pFace2->plane.normal;
			if (pts1 == pts2)
			{
				pSolid->DeleteFace(j);
				goto ReStart;
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Repairs an invalid solid.
// Input  : pError - Contains information about the error.
//-----------------------------------------------------------------------------
static void FixSolidStructure(MapError *pError)
{
	CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];

	//
	// First make sure all the faces are good.
	//
	int nFaces = pSolid->GetFaceCount();
	for (int i = nFaces - 1; i >= 0; i--)
	{
		CMapFace *pFace = pSolid->GetFace(i);
		if (!pFace->CheckFace(NULL))
		{
			pFace->Fix();
		}
		//
		// If the face has no points, just remove it from the solid.
		//
		if (pFace->GetPointCount() == 0)
		{
			pSolid->DeleteFace(i);
		}
	}

	//
	// Rebuild the solid from the planes.
	//
	pSolid->CreateFromPlanes();
	pSolid->PostUpdate(Notify_Changed);
}


LPCTSTR GetDefaultTextureName(); // dvs: BAD!


//-----------------------------------------------------------------------------
// Purpose: Replaces any missing textures with the default texture.
// Input  : pError - 
//-----------------------------------------------------------------------------
static void FixInvalidTexture(MapError *pError)
{
	CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];

	int nFaces = pSolid->GetFaceCount();
	for (int i = 0; i < nFaces; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);
		if (pFace != NULL)
		{
			IEditorTexture *pTex = pFace->GetTexture();
			if (pTex != NULL)
			{
				if (pTex->IsDummy())
				{
					pFace->SetTexture(GetDefaultTextureName());
				}
			}
		}
	}
}


static void FixInvalidTextureAxes(MapError *pError)
{
	CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];

	int nFaces = pSolid->GetFaceCount();
	for (int i = 0; i < nFaces; i++)
	{
		CMapFace *pFace = pSolid->GetFace(i);
		if (!pFace->IsTextureAxisValid())
		{
			pFace->InitializeTextureAxes(Options.GetTextureAlignment(), INIT_TEXTURE_FORCE | INIT_TEXTURE_AXES);
		}
	}
}


static void FixInvalidContents(MapError *pError)
{
	CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];

	CMapFace *pFace = pSolid->GetFace(0);
	DWORD dwContents = pFace->texture.q2contents;

	int nFaces = pSolid->GetFaceCount();
	for (int i = 1; i < nFaces; i++)
	{
		pFace = pSolid->GetFace(i);
		pFace->texture.q2contents = dwContents;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Fixes duplicate face IDs by assigning the face a unique ID within
//			the world.
// Input  : pError - Holds the world and the face that is in error.
//-----------------------------------------------------------------------------
static void FixDuplicateFaceIDs(MapError *pError)
{
	CMapWorld *pWorld = (CMapWorld *)pError->pObjects[0];
	CMapFace *pFace = (CMapFace *)pError->dwExtra;

	pFace->SetFaceID(pWorld->FaceID_GetNext());
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pError - 
//-----------------------------------------------------------------------------
static void FixUnusedKeyvalues(MapError *pError)
{
	CMapEntity *pEntity = (CMapEntity*) pError->pObjects[0];

	GDclass *pClass = pEntity->GetClass();
	if (!pClass)
	{
		return;
	}

	int iNext;
	for ( int i=pEntity->GetFirstKeyValue(); i != pEntity->GetInvalidKeyValue(); i = iNext )
	{
		iNext = pEntity->GetNextKeyValue( i );
		if (pClass->VarForName(pEntity->GetKey(i)) == NULL)
		{
			pEntity->DeleteKeyValue(pEntity->GetKey(i));
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Removes any bad connections from the entity associated with the error.
// Input  : pError - 
//-----------------------------------------------------------------------------
static void FixBadConnections(MapError *pError)
{
	CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0];
	CEntityConnection::FixBadConnections(pEntity, (Options.general.bCheckVisibleMapErrors == TRUE));
}


//-----------------------------------------------------------------------------
// Purpose: Fixes a race condition caused by a Kill input being triggered at the
//			same instant as another input.
// Input  : pError - 
//-----------------------------------------------------------------------------
static void FixKillInputRaceCondition(MapError *pError)
{
	CEntityConnection *pConn = (CEntityConnection *)pError->pObjects[1];

	// Delay the Kill command so that it arrives after the other command,
	// solving the race condition.
	pConn->SetDelay(pConn->GetDelay() + 0.01);
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pError - 
//-----------------------------------------------------------------------------
static void FixOverlayFaceList( MapError *pError )
{
	CMapEntity *pEntity = static_cast<CMapEntity*>( pError->pObjects[0] );
	if ( !pEntity )
		return;

	const CMapObjectList *pChildren = pEntity->GetChildren();

	FOR_EACH_OBJ( *pChildren, pos )
	{
		CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) );
		if ( pOverlay )
		{
			// Destroy itself.
			CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
			pDoc->RemoveObjectFromWorld( pEntity, true );
			GetHistory()->KeepForDestruction( pEntity );
			return;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pError - 
//-----------------------------------------------------------------------------
static void FixEmptyEntity(MapError *pError)
{
	CMapClass *pKillMe = pError->pObjects[0];

	if (pKillMe->GetParent() != NULL)
	{
		GetHistory()->KeepForDestruction(pKillMe);
		pKillMe->GetParent()->RemoveChild(pKillMe);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Fixes duplicate node IDs by assigning the entity a unique node ID.
// Input  : pError - Holds the world and the entity that is in error.
//-----------------------------------------------------------------------------
static void FixDuplicateNodeIDs(MapError *pError)
{
	CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0];
	pEntity->AssignNodeID();
}


//-----------------------------------------------------------------------------
// Purpose: Clears a bad target reference from the given entity.
// Input  : pError - 
//-----------------------------------------------------------------------------
static void FixMissingTarget(MapError *pError)
{
	CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0];
	const char *pszKey = (const char *)pError->dwExtra;
	pEntity->SetKeyValue(pszKey, NULL);
}


//-----------------------------------------------------------------------------
// Purpose: Fix a an invalid visgroup state. This is either:
//			1) A group that is hidden
//			2) An object that is hidden but not in any visgroups
//-----------------------------------------------------------------------------
void FixHiddenObject(MapError *pError)
{
	CMapClass *pObject = pError->pObjects[0];

	// Tweak the object's visgroup state directly to avoid changing the
	// hidden/shown state of the object's children.
	pObject->m_bVisGroupShown = true;
	pObject->m_bVisGroupAutoShown = true;
	pObject->m_VisGroups.RemoveAll();

	// Create a new visgroup to out the objects in (for hiding or inspection/deletion).
	CMapObjectList Objects;
	Objects.AddToTail(pObject);
	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();

	if ((pError->Type == ErrorHiddenGroupHiddenChildren) ||
		(pError->Type == ErrorHiddenObjectNoVisGroup))
	{
		// The objects aren't in the compile, so just hide them.
		pDoc->VisGroups_CreateNamedVisGroup(Objects, "_hidden by Check for Problems", true, false);
	}
	else if (pError->Type == ErrorIllegallyHiddenObject)
	{
		// Do nothing, the object is now shown.
	}
	else
	{
		// ErrorHiddenGroupVisibleChildren
		// ErrorHiddenGroupMixedChildren
		// ErrorHiddenChildOfEntity

		// The objects either ARE in the compile, or they can't be hidden in a visgroup.
		// Don't hide them, just stick them in a visgroup for inspection
		pDoc->VisGroups_CreateNamedVisGroup(Objects, "found by Check for Problems", false, false);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Checks the map for problems. Returns true if the map is okay,
//			false if problems were found.
//-----------------------------------------------------------------------------
bool CMapCheckDlg::DoCheck(void)
{
	CMapWorld *pWorld = GetActiveWorld();

	// Clear error list
	KillErrorList();

	// Map validation
	CheckRequirements(&m_Errors, pWorld);

	// Solid validation
	CheckMixedFaces(&m_Errors, pWorld);
	//CheckDuplicatePlanes(&m_Errors, pWorld);
	CheckDuplicateFaceIDs(&m_Errors, pWorld);
	CheckDuplicateNodeIDs(&m_Errors, pWorld);
	CheckSolidIntegrity(&m_Errors, pWorld);
	CheckSolidContents(&m_Errors, pWorld);
	CheckInvalidTextures(&m_Errors, pWorld);

	// Entity validation
	CheckUnusedKeyvalues(&m_Errors, pWorld);
	CheckEmptyEntities(&m_Errors, pWorld);
	CheckMissingTargets(&m_Errors, pWorld);
	CheckBadConnections(&m_Errors, pWorld);

	CheckVisGroups(&m_Errors, pWorld);

	CheckOverlayFaceList(&m_Errors, pWorld);

	if (!m_Errors.GetCount())
	{
		AfxMessageBox("No errors were found.");
		EndDialog(IDOK);
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnOK()
{
	DestroyWindow();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnClose()
{
	DestroyWindow();
}


//-----------------------------------------------------------------------------
// Purpose: Called when our window is being destroyed.
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnDestroy()
{
	delete this;
	s_pDlg = NULL;
}