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

#include "fgdlib/GameData.h" // FGDLIB: eliminate dependency
#include "fgdlib/GDClass.h"

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


//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
GDclass::GDclass(void)
{
	m_nVariables = 0;
	m_bBase = false;
	m_bSolid = false;
	m_bBase = false;
	m_bSolid = false;
	m_bModel = false;
	m_bMove = false;
	m_bKeyFrame = false;
	m_bPoint = false;
	m_bNPC = false;
	m_bFilter = false;

	m_bHalfGridSnap = false;

	m_bGotSize = false;
	m_bGotColor = false;

	m_rgbColor.r = 220;
	m_rgbColor.g = 30;
	m_rgbColor.b = 220;
	m_rgbColor.a = 0;

	m_pszDescription = NULL;

	for (int i = 0; i < 3; i++)
	{
		m_bmins[i] = -8;
		m_bmaxs[i] = 8;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Destructor. Frees variable and helper lists.
//-----------------------------------------------------------------------------
GDclass::~GDclass(void)
{
	//
	// Free variables.
	//
	int nCount = m_Variables.Count();
	for (int i = 0; i < nCount; i++)
	{
		GDinputvariable *pvi = m_Variables.Element(i);
		delete pvi;
	}
	m_Variables.RemoveAll();

	//
	// Free helpers.
	//
	nCount = m_Helpers.Count();
	for (int i = 0; i < nCount; i++)
	{
		CHelperInfo *pHelper = m_Helpers.Element(i);
		delete pHelper;
	}
	m_Helpers.RemoveAll();

	//
	// Free inputs.
	//
	nCount = m_Inputs.Count();
	for (int i = 0; i < nCount; i++)
	{
		CClassInput *pInput = m_Inputs.Element(i);
		delete pInput;
	}
	m_Inputs.RemoveAll();

	//
	// Free outputs.
	//
	nCount = m_Outputs.Count();
	for (int i = 0; i < nCount; i++)
	{
		CClassOutput *pOutput = m_Outputs.Element(i);
		delete pOutput;
	}
	m_Outputs.RemoveAll();

	delete m_pszDescription;
}


//-----------------------------------------------------------------------------
// Purpose: Adds the base class's variables to our variable list. Acquires the
//			base class's bounding box and color, if any.
// Input  : pszBase - Name of base class to add.
//-----------------------------------------------------------------------------
void GDclass::AddBase(GDclass *pBase)
{
	int iBaseIndex;
	Parent->ClassForName(pBase->GetName(), &iBaseIndex);

	//
	// Add variables from base - update variable table
	//
	for (int i = 0; i < pBase->GetVariableCount(); i++)
	{
		GDinputvariable *pVar = pBase->GetVariableAt(i);
		AddVariable(pVar, pBase, iBaseIndex, i);
	}

	//
	// Add inputs from the base.
	// UNDONE: need to use references to inputs & outputs to conserve memory
	//
	int nCount = pBase->GetInputCount();
	for (int i = 0; i < nCount; i++)
	{
		CClassInput *pInput = pBase->GetInput(i);

		CClassInput *pNew = new CClassInput;
		*pNew = *pInput;
		AddInput(pNew);
	}

	//
	// Add outputs from the base.
	//
	nCount = pBase->GetOutputCount();
	for (int i = 0; i < nCount; i++)
	{
		CClassOutput *pOutput = pBase->GetOutput(i);

		CClassOutput *pNew = new CClassOutput;
		*pNew = *pOutput;
		AddOutput(pNew);
	}

	//
	// If we don't have a bounding box, try to get the base's box.
	//
	if (!m_bGotSize)
	{
		if (pBase->GetBoundBox(m_bmins, m_bmaxs))
		{
			m_bGotSize = true;
		}
	}

	//
	// If we don't have a color, use the base's color.
	//
	if (!m_bGotColor)
	{
		m_rgbColor = pBase->GetColor();
		m_bGotColor = true;
	}	
}


//-----------------------------------------------------------------------------
// Purpose: Adds the given GDInputVariable to this GDClass's list of variables.
// Input  : pVar - 
//			pBase - 
//			iBaseIndex - 
//			iVarIndex - 
// Output : Returns TRUE if the pVar pointer was copied directly into this GDClass,
//			FALSE if not. If this function returns TRUE, pVar should not be
//			deleted by the caller.
//-----------------------------------------------------------------------------
BOOL GDclass::AddVariable(GDinputvariable *pVar, GDclass *pBase, int iBaseIndex, int iVarIndex)
{
	int iThisIndex;
	GDinputvariable *pThisVar = VarForName(pVar->GetName(), &iThisIndex);
	
	//
	// Check to see if we are overriding an existing variable definition.
	//
	if (pThisVar != NULL)
	{
		//
		// Same name, different type. Flag this as an error.
		//
		if (pThisVar->GetType() != pVar->GetType())
		{
			return(false);
		}

		GDinputvariable *pAddVar;
		bool bReturn;

		//
		// Check to see if we need to combine a choices/flags array.
		//
		if (pVar->GetType() == ivFlags || pVar->GetType() == ivChoices)
		{
			//
			// Combine two variables' flags into a new variable. Add the new
			// variable to the local variable list and modify the old variable's
			// position in our variable map to reflect the new local variable.
			// This way, we can have multiple inheritance.
			//
			GDinputvariable *pNewVar = new GDinputvariable;

			*pNewVar = *pVar;
			pNewVar->Merge(*pThisVar);

			pAddVar = pNewVar;
			bReturn = false;
		}
		else
		{
			pAddVar = pVar;
			bReturn = true;
		}

		if (m_VariableMap[iThisIndex][0] == -1)
		{
			//
			// "pThisVar" is a leaf variable - we can remove since it is overridden.
			//
			int nIndex = m_Variables.Find(pThisVar);
			Assert(nIndex != -1);
			delete pThisVar;

			m_Variables.Element(nIndex) = pAddVar;

			//
			// No need to modify variable map - we just replaced
			// the pointer in the local variable list.
			//
		}
		else
		{
			//
			// "pThisVar" was declared in a base class - we can replace the reference in
			// our variable map with the new variable.
			//
			m_VariableMap[iThisIndex][0] = iBaseIndex;

			if (iBaseIndex == -1)
			{
				m_Variables.AddToTail(pAddVar);
				m_VariableMap[iThisIndex][1] = m_Variables.Count() - 1;
			}
			else
			{
				m_VariableMap[iThisIndex][1] = iVarIndex;
			}
		}

		return(bReturn);
	}
	
	//
	// New variable.
	//
	if (iBaseIndex == -1)
	{
		//
		// Variable declared in the leaf class definition - add it to the list.
		//
		m_Variables.AddToTail(pVar);
	}

	//
	// Too many variables already declared in this class definition - abort.
	//
	if (m_nVariables == GD_MAX_VARIABLES)
	{
		//CUtlString str;
		//str.Format("Too many gamedata variables for class \"%s\"", m_szName);
		//AfxMessageBox(str);

		return(false);
	}

	//
	// Add the variable to our list.
	//
	m_VariableMap[m_nVariables][0] = iBaseIndex;
	m_VariableMap[m_nVariables][1] = iVarIndex;
	++m_nVariables;
	
	//
	// We added the pointer to our list of items (see Variables.AddToTail, above) so
	// we must return true here.
	//
	return(true);
}


//-----------------------------------------------------------------------------
// Finds an input by name.
//-----------------------------------------------------------------------------
CClassInput *GDclass::FindInput(const char *szName)
{
	int nCount = GetInputCount();
	for (int i = 0; i < nCount; i++)
	{
		CClassInput *pInput = GetInput(i);
		if (!stricmp(pInput->GetName(), szName))
		{
			return(pInput);
		}
	}

	return(NULL);
}


//-----------------------------------------------------------------------------
// Finds an output by name.
//-----------------------------------------------------------------------------
CClassOutput *GDclass::FindOutput(const char *szName)
{
	int nCount = GetOutputCount();
	for (int i = 0; i < nCount; i++)
	{
		CClassOutput *pOutput = GetOutput(i);
		if (!stricmp(pOutput->GetName(), szName))
		{
			return(pOutput);
		}
	}

	return(NULL);
}


//-----------------------------------------------------------------------------
// Purpose: Gets the mins and maxs of the class's bounding box as read from the
//			FGD file. This controls the onscreen representation of any entities
//			derived from this class.
// Input  : pfMins - Receives minimum X, Y, and Z coordinates for the class.
//			pfMaxs - Receives maximum X, Y, and Z coordinates for the class.
// Output : Returns TRUE if this class has a specified bounding box, FALSE if not.
//-----------------------------------------------------------------------------
BOOL GDclass::GetBoundBox(Vector& pfMins, Vector& pfMaxs)
{
	if (m_bGotSize)
	{
		pfMins[0] = m_bmins[0];
		pfMins[1] = m_bmins[1];
		pfMins[2] = m_bmins[2];

		pfMaxs[0] = m_bmaxs[0];
		pfMaxs[1] = m_bmaxs[1];
		pfMaxs[2] = m_bmaxs[2];
	}

	return(m_bGotSize);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CHelperInfo *GDclass::GetHelper(int nIndex)
{
	return m_Helpers.Element(nIndex);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CClassInput *GDclass::GetInput(int nIndex)
{
	return m_Inputs.Element(nIndex);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CClassOutput *GDclass::GetOutput(int nIndex)
{
	return m_Outputs.Element(nIndex);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : tr - 
//			pGD - 
// Output : Returns TRUE if worth continuing, FALSE otherwise.	
//-----------------------------------------------------------------------------
BOOL GDclass::InitFromTokens(TokenReader& tr, GameData *pGD)
{
	Parent = pGD;

	//
	// Initialize VariableMap
	//
	for (int i = 0; i < GD_MAX_VARIABLES; i++)
	{
		m_VariableMap[i][0] = -1;
		m_VariableMap[i][1] = -1;
	}

	//
	// Parse all specifiers (base, size, color, etc.)
	//
	if (!ParseSpecifiers(tr))
	{
		return(FALSE);
	}

	//
	// Specifiers should be followed by an "="
	//
	if (!GDSkipToken(tr, OPERATOR, "="))
	{
		return(FALSE);
	}

	//
	// Parse the class name.
	//
	if (!GDGetToken(tr, m_szName, sizeof(m_szName), IDENT))
	{
		return(FALSE);
	}

	//
	// Check next operator - if ":", we have a description - if "[",
	// we have no description.
	//
	char szToken[MAX_TOKEN];
	if ((tr.PeekTokenType(szToken,sizeof(szToken)) == OPERATOR) && IsToken(szToken, ":"))
	{
		// Skip ":"
		tr.NextToken(szToken, sizeof(szToken));

		//
		// Free any existing description and set the pointer to NULL so that GDGetToken
		// allocates memory for us.
		//
		delete m_pszDescription;
		m_pszDescription = NULL;

		// Load description
		if (!GDGetTokenDynamic(tr, &m_pszDescription, STRING))
		{
			return(FALSE);
		}
	}

	//
	// Opening square brace.
	//
	if (!GDSkipToken(tr, OPERATOR, "["))
	{
		return(FALSE);
	}

	//
	// Get class variables.
	//
	if (!ParseVariables(tr))
	{
		return(FALSE);
	}

	//
	// Closing square brace.
	//
	if (!GDSkipToken(tr, OPERATOR, "]"))
	{
		return(FALSE);
	}

	return(TRUE);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &tr - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseBase(TokenReader &tr)
{
	char szToken[MAX_TOKEN];

	while (1)
	{
		if (!GDGetToken(tr, szToken, sizeof(szToken), IDENT))
		{
			return(false);
		}

		//
		// Find base class in list of classes.
		//
		GDclass *pBase = Parent->ClassForName(szToken);
		if (pBase == NULL)
		{
			GDError(tr, "undefined base class '%s", szToken);
			return(false);
		}

		AddBase(pBase);

		if (!GDGetToken(tr, szToken, sizeof(szToken), OPERATOR))
		{
			return(false);
		}

		if (IsToken(szToken, ")"))
		{
			break;
		}
		else if (!IsToken(szToken, ","))
		{
			GDError(tr, "expecting ',' or ')', but found %s", szToken);
			return(false);
		}
	}

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &tr - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseColor(TokenReader &tr)
{
	char szToken[MAX_TOKEN];

	//
	// Red.
	//
	if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER))
	{
		return(false);
	}
	BYTE r = atoi(szToken);

	//
	// Green.
	//
	if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER))
	{
		return(false);
	}
	BYTE g = atoi(szToken);

	//
	// Blue.
	//
	if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER))
	{
		return(false);
	}
	BYTE b = atoi(szToken);

	m_rgbColor.r = r;
	m_rgbColor.g = g;
	m_rgbColor.b = b;
	m_rgbColor.a = 0;

	m_bGotColor = true;

	if (!GDGetToken(tr, szToken, sizeof(szToken), OPERATOR, ")"))
	{
		return(false);
	}
	
	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: Parses a helper from the FGD file. Helpers are of the following format:
//
//			<helpername>(<parameter 0> <parameter 1> ... <parameter n>)
//
//			When this function is called, the helper name has already been parsed.
// Input  : tr - Tokenreader to use for parsing.
//			pszHelperName - Name of the helper being declared.
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseHelper(TokenReader &tr, char *pszHelperName)
{
	char szToken[MAX_TOKEN];

	CHelperInfo *pHelper = new CHelperInfo;
	pHelper->SetName(pszHelperName);

	bool bCloseParen = false;
	while (!bCloseParen)
	{
		trtoken_t eType = tr.PeekTokenType(szToken,sizeof(szToken));

		if (eType == OPERATOR)
		{
			if (!GDGetToken(tr, szToken, sizeof(szToken), OPERATOR))
			{
				delete pHelper;
				return(false);
			}

			if (IsToken(szToken, ")"))
			{
				bCloseParen = true;
			}
			else if (IsToken(szToken, "="))
			{
				delete pHelper;
				return(false);
			}
		}
		else
		{
			if (!GDGetToken(tr, szToken, sizeof(szToken), eType))
			{
				delete pHelper;
				return(false);
			}
			else
			{
				pHelper->AddParameter(szToken);
			}
		}
	}

	m_Helpers.AddToTail(pHelper);

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &tr - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseSize(TokenReader &tr)
{
	char szToken[MAX_TOKEN];

	//
	// Mins.
	//
	for (int i = 0; i < 3; i++)
	{
		if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER))
		{
			return(false);
		}

		m_bmins[i] = (float)atof(szToken);
	}

	if (tr.PeekTokenType(szToken,sizeof(szToken)) == OPERATOR && IsToken(szToken, ","))
	{
		//
		// Skip ","
		//
		tr.NextToken(szToken, sizeof(szToken));

		//
		// Get maxes.
		//
		for (int i = 0; i < 3; i++)
		{
			if (!GDGetToken(tr, szToken, sizeof(szToken), INTEGER))
			{
				return(false);
			}
			m_bmaxs[i] = (float)atof(szToken);
		}
	}
	else
	{
		//
		// Split mins across origin.
		//
		for (int i = 0; i < 3; i++)
		{
			float div2 = m_bmins[i] / 2;
			m_bmaxs[i] = div2;
			m_bmins[i] = -div2;
		}
	}

	m_bGotSize = true;

	if (!GDGetToken(tr, szToken, sizeof(szToken), OPERATOR, ")"))
	{
		return(false);
	}

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &tr - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseSpecifiers(TokenReader &tr)
{
	char szToken[MAX_TOKEN];

	while (tr.PeekTokenType() == IDENT)
	{
		tr.NextToken(szToken, sizeof(szToken));

		//
		// Handle specifiers that don't have any parens after them.
		//
		if (IsToken(szToken, "halfgridsnap"))
		{
			m_bHalfGridSnap = true;
		}
		else
		{
			//
			// Handle specifiers require parens after them.
			//
			if (!GDSkipToken(tr, OPERATOR, "("))
			{
				return(false);
			}

			if (IsToken(szToken, "base"))
			{
				if (!ParseBase(tr))
				{
					return(false);
				}
			}
			else if (IsToken(szToken, "size"))
			{
				if (!ParseSize(tr))
				{
					return(false);
				}
			}
			else if (IsToken(szToken, "color"))
			{
				if (!ParseColor(tr))
				{
					return(false);
				}
			}
			else if (!ParseHelper(tr, szToken))
			{
				return(false);
			}
		}
	}

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: Reads an input using a given token reader. If the input is
//			read successfully, the input is added to this class. If not, a
//			parsing failure is returned.
// Input  : tr - Token reader to use for parsing.
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseInput(TokenReader &tr)
{
	char szToken[MAX_TOKEN];

	if (!GDGetToken(tr, szToken, sizeof(szToken), IDENT, "input"))
	{
		return(false);
	}

	CClassInput *pInput = new CClassInput;

	bool bReturn = ParseInputOutput(tr, pInput);
	if (bReturn)
	{
		AddInput(pInput);
	}
	else
	{
		delete pInput;
	}

	return(bReturn);
}


//-----------------------------------------------------------------------------
// Purpose: Reads an input or output using a given token reader.
// Input  : tr - Token reader to use for parsing.
//			pInputOutput - Input or output to fill out.
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseInputOutput(TokenReader &tr, CClassInputOutputBase *pInputOutput)
{
	char szToken[MAX_TOKEN];

	//
	// Read the name.
	//
	if (!GDGetToken(tr, szToken, sizeof(szToken), IDENT))
	{
		return(false);
	}

	pInputOutput->SetName(szToken);

	//
	// Read the type.
	//
	if (!GDGetToken(tr, szToken, sizeof(szToken), OPERATOR, "("))
	{
		return(false);
	}

	if (!GDGetToken(tr, szToken, sizeof(szToken), IDENT))
	{
		return(false);
	}

	InputOutputType_t eType = pInputOutput->SetType(szToken);
	if (eType == iotInvalid)
	{
		GDError(tr, "bad input/output type '%s'", szToken);
		return(false);
	}

	if (!GDGetToken(tr, szToken, sizeof(szToken), OPERATOR, ")"))
	{
		return(false);
	}

	//
	// Check the next operator - if ':', we have a description.
	//
	if ((tr.PeekTokenType(szToken,sizeof(szToken)) == OPERATOR) && (IsToken(szToken, ":")))
	{
		//
		// Skip the ":".
		//
		tr.NextToken(szToken, sizeof(szToken));

		//
		// Read the description.
		//
		char *pszDescription;
		if (!GDGetTokenDynamic(tr, &pszDescription, STRING))
		{
			return(false);
		}

		pInputOutput->SetDescription(pszDescription);
	}

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: Reads an output using a given token reader. If the output is
//			read successfully, the output is added to this class. If not, a
//			parsing failure is returned.
// Input  : tr - Token reader to use for parsing.
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseOutput(TokenReader &tr)
{
	char szToken[MAX_TOKEN];

	if (!GDGetToken(tr, szToken, sizeof(szToken), IDENT, "output"))
	{
		return(false);
	}

	CClassOutput *pOutput = new CClassOutput;

	bool bReturn = ParseInputOutput(tr, pOutput);
	if (bReturn)
	{
		AddOutput(pOutput);
	}
	else
	{
		delete pOutput;
	}

	return(bReturn);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &tr - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool GDclass::ParseVariables(TokenReader &tr)
{
	while (1)
	{
		char szToken[MAX_TOKEN];

		if (tr.PeekTokenType(szToken,sizeof(szToken)) == OPERATOR)
		{
			break;
		}

		if (!stricmp(szToken, "input"))
		{
			if (!ParseInput(tr))
			{
				return(false);
			}

			continue;
		}

		if (!stricmp(szToken, "output"))
		{
			if (!ParseOutput(tr))
			{
				return(false);
			}

			continue;
		}

		if (!stricmp(szToken, "key"))
		{
			GDGetToken(tr, szToken, sizeof(szToken));
		}

		GDinputvariable * var = new GDinputvariable;
		if (!var->InitFromTokens(tr))
		{
			delete var;
			return(false);
		}

		int nDupIndex;
		GDinputvariable *pDupVar = VarForName(var->GetName(), &nDupIndex);
		
		// check for duplicate variable definitions
		if (pDupVar)
		{
			// Same name, different type.
			if (pDupVar->GetType() != var->GetType())
			{
				char szError[_MAX_PATH];

				sprintf(szError, "%s: Variable '%s' is multiply defined with different types.", GetName(), var->GetName());
				GDError(tr, szError);
			}
		}

		if (!AddVariable(var, this, -1, m_Variables.Count()))
		{
			delete var;
		}
	}

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : iIndex - 
// Output : GDinputvariable *
//-----------------------------------------------------------------------------
GDinputvariable *GDclass::GetVariableAt(int iIndex)
{
	if ( iIndex < 0 || iIndex >= m_nVariables )
		return NULL;

	if (m_VariableMap[iIndex][0] == -1)
	{
		return m_Variables.Element(m_VariableMap[iIndex][1]);
	}

	// find var's owner
	GDclass *pVarClass = Parent->GetClass(m_VariableMap[iIndex][0]);

	// find var in pVarClass
	return pVarClass->GetVariableAt(m_VariableMap[iIndex][1]);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
GDinputvariable *GDclass::VarForName(const char *pszName, int *piIndex)
{
	for(int i = 0; i < GetVariableCount(); i++)
	{
		GDinputvariable *pVar = GetVariableAt(i);
		if(!strcmpi(pVar->GetName(), pszName))
		{
			if(piIndex)
				piIndex[0] = i;
			return pVar;
		}
	}

	return NULL;
}

void GDclass::GetHelperForGDVar( GDinputvariable *pVar, CUtlVector<const char *> *pszHelperName )
{
	const char *pszName = pVar->GetName();
	for( int i = 0; i < GetHelperCount(); i++ )
	{
		CHelperInfo *pHelper = GetHelper( i );
		int nParamCount = pHelper->GetParameterCount();
		for ( int j = 0; j < nParamCount; j++ )
		{
			if ( !strcmpi( pszName, pHelper->GetParameter( j ) ) )
			{
				pszHelperName->AddToTail(pHelper->GetName());
			}
		}
	}
}