/*
*
*    This program is free software; you can redistribute it and/or modify it
*    under the terms of the GNU General Public License as published by the
*    Free Software Foundation; either version 2 of the License, or (at
*    your option) any later version.
*
*    This program is distributed in the hope that it will be useful, but
*    WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program; if not, write to the Free Software Foundation,
*    Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*    In addition, as a special exception, the author gives permission to
*    link the code of this program with the Half-Life Game Engine ("HL
*    Engine") and Modified Game Libraries ("MODs") developed by Valve,
*    L.L.C ("Valve").  You must obey the GNU General Public License in all
*    respects for all of the code used other than the HL Engine and MODs
*    from Valve.  If you modify this file, you may extend this exception
*    to your version of the file, but you are not obligated to do so.  If
*    you do not wish to do so, delete this exception statement from your
*    version.
*
*/

#include "precompiled.h"

// Internal structures
enum
{
	MAX_GROUP_NAME_LENGTH = 48
};

struct SpewGroup_t
{
	char	m_GroupName[MAX_GROUP_NAME_LENGTH];
	int		m_Level;
};


// Templates to assist in validating pointers:
void _AssertValidReadPtr(void *ptr, int count)
{
#ifdef _WIN32
	Assert(!IsBadReadPtr(ptr, count));
#else
	Assert(ptr);
#endif

}

void _AssertValidWritePtr(void *ptr, int count)
{
#ifdef _WIN32
	Assert(!IsBadWritePtr(ptr, count));
#else
	Assert(ptr);
#endif
}

void _AssertValidReadWritePtr(void *ptr, int count)
{
#ifdef _WIN32
	Assert(!(IsBadWritePtr(ptr, count) || IsBadReadPtr(ptr, count)));
#else
	Assert(ptr);
#endif
}

void AssertValidStringPtr(const char *ptr, int maxchar)
{
#ifdef _WIN32
	Assert(!IsBadStringPtr(ptr, maxchar));
#else
	Assert(ptr);
#endif
}

// Globals
SpewRetval_t DefaultSpewFunc(SpewType_t type, const char *pMsg)
{
	printf("%s", pMsg);
	if (type == SPEW_ASSERT)
		return SPEW_DEBUGGER;
	else if (type == SPEW_ERROR)
		return SPEW_ABORT;
	else
		return SPEW_CONTINUE;
}

static SpewOutputFunc_t s_SpewOutputFunc = DefaultSpewFunc;

static const char *s_pFileName;
static int         s_Line;
static SpewType_t  s_SpewType;

static SpewGroup_t *s_pSpewGroups = 0;
static int          s_GroupCount = 0;
static int          s_DefaultLevel = 0;

// Spew output management.
void SpewOutputFunc(SpewOutputFunc_t func)
{
	s_SpewOutputFunc = func ? func : DefaultSpewFunc;
}

SpewOutputFunc_t GetSpewOutputFunc()
{
	if (s_SpewOutputFunc)
	{
		return s_SpewOutputFunc;
	}
	else
	{
		return DefaultSpewFunc;
	}
}

// Spew functions
void _SpewInfo(SpewType_t type, const char *pFile, int line)
{
	// Only grab the file name. Ignore the path.
	const char *pSlash = strrchr(pFile, '\\');
	const char *pSlash2 = strrchr(pFile, '/');
	if (pSlash < pSlash2) pSlash = pSlash2;

	s_pFileName = pSlash ? pSlash + 1 : pFile;
	s_Line = line;
	s_SpewType = type;
}

SpewRetval_t _SpewMessage(SpewType_t spewType, const char *pMsgFormat, va_list args)
{
	char pTempBuffer[1024];

	// Printf the file and line for warning + assert only...
	int len = 0;
	if ((spewType == SPEW_ASSERT))
	{
		len = sprintf(pTempBuffer, "%s (%d) : ", s_pFileName, s_Line);
	}

	// Create the message....
	len += vsprintf(&pTempBuffer[len], pMsgFormat, args);

	// Add \n for warning and assert
	if ((spewType == SPEW_ASSERT))
	{
		len += sprintf(&pTempBuffer[len], "\n");
	}

	assert(len < 1024); // use normal assert here; to avoid recursion.
	assert(s_SpewOutputFunc);

	// direct it to the appropriate target(s)
	SpewRetval_t ret = s_SpewOutputFunc(spewType, pTempBuffer);
	switch (ret)
	{
	// Put the break into the macro so it would occur in the right place
	//case SPEW_DEBUGGER:
	//	DebuggerBreak();
	//	break;

	case SPEW_ABORT:
		// MessageBox(nullptr, "Error in _SpewMessage", "Error", MB_OK);
		exit(0);
	default:
		break;
	}

	return ret;
}

SpewRetval_t _SpewMessage(const char *pMsgFormat, ...)
{
	va_list args;
	va_start(args, pMsgFormat);
	SpewRetval_t ret = _SpewMessage(s_SpewType, pMsgFormat, args);
	va_end(args);
	return ret;
}

SpewRetval_t _DSpewMessage(const char *pGroupName, int level, const char *pMsgFormat, ...)
{
	if (!IsSpewActive(pGroupName, level))
		return SPEW_CONTINUE;

	va_list args;
	va_start(args, pMsgFormat);
	SpewRetval_t ret = _SpewMessage(s_SpewType, pMsgFormat, args);
	va_end(args);
	return ret;
}

void Msg(const char *pMsgFormat, ...)
{
	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_MESSAGE, pMsgFormat, args);
	va_end(args);
}

void DMsg(const char *pGroupName, int level, const char *pMsgFormat, ...)
{
	if (!IsSpewActive(pGroupName, level))
		return;

	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_MESSAGE, pMsgFormat, args);
	va_end(args);
}

void Warning(const char *pMsgFormat, ...)
{
	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_WARNING, pMsgFormat, args);
	va_end(args);
}

void DWarning(const char *pGroupName, int level, const char *pMsgFormat, ...)
{
	if (!IsSpewActive(pGroupName, level))
		return;

	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_WARNING, pMsgFormat, args);
	va_end(args);
}

void Log(const char *pMsgFormat, ...)
{
	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_LOG, pMsgFormat, args);
	va_end(args);
}

void DLog(const char *pGroupName, int level, const char *pMsgFormat, ...)
{
	if (!IsSpewActive(pGroupName, level))
		return;

	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_LOG, pMsgFormat, args);
	va_end(args);
}

void Error(const char *pMsgFormat, ...)
{
	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_ERROR, pMsgFormat, args);
	va_end(args);
}

// A couple of super-common dynamic spew messages, here for convenience 
// These looked at the "developer" group, print if it's level 1 or higher 
void DevMsg(int level, char const *pMsgFormat, ...)
{
	if (!IsSpewActive("developer", level))
		return;

	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_MESSAGE, pMsgFormat, args);
	va_end(args);
}

void DevWarning(int level, const char *pMsgFormat, ...)
{
	if (!IsSpewActive("developer", level))
		return;

	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_WARNING, pMsgFormat, args);
	va_end(args);
}

void DevLog(int level, const char *pMsgFormat, ...)
{
	if (!IsSpewActive("developer", level))
		return;

	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_LOG, pMsgFormat, args);
	va_end(args);
}

void DevMsg(const char *pMsgFormat, ...)
{
	if (!IsSpewActive("developer", 1))
		return;

	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_MESSAGE, pMsgFormat, args);
	va_end(args);
}

void DevWarning(const char *pMsgFormat, ...)
{
	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_WARNING, pMsgFormat, args);
	va_end(args);
}

void DevLog(const char *pMsgFormat, ...)
{
	va_list args;
	va_start(args, pMsgFormat);
	_SpewMessage(SPEW_LOG, pMsgFormat, args);
	va_end(args);
}

// Find a group, return true if found, false if not. Return in ind the
// index of the found group, or the index of the group right before where the
// group should be inserted into the list to maintain sorted order.
bool FindSpewGroup(const char *pGroupName, int *pInd)
{
	int s = 0;
	if (s_GroupCount)
	{
		int e = (int)(s_GroupCount - 1);
		while (s <= e)
		{
			int m = (s + e) >> 1;
			int cmp = Q_stricmp(pGroupName, s_pSpewGroups[m].m_GroupName);
			if (!cmp)
			{
				*pInd = m;
				return true;
			}

			if (cmp < 0)
				e = m - 1;
			else
				s = m + 1;
		}
	}

	*pInd = s;
	return false;
}

// Sets the priority level for a spew group
void SpewActivate(const char *pGroupName, int level)
{
	Assert(pGroupName);

	// check for the default group first...
	if ((pGroupName[0] == '*') && (pGroupName[1] == '\0'))
	{
		s_DefaultLevel = level;
		return;
	}

	// Normal case, search in group list using binary search.
	// If not found, grow the list of groups and insert it into the
	// right place to maintain sorted order. Then set the level.
	int ind;
	if (!FindSpewGroup(pGroupName, &ind))
	{
		// not defined yet, insert an entry.
		s_GroupCount++;
		if (s_pSpewGroups)
		{
			s_pSpewGroups = (SpewGroup_t *)realloc(s_pSpewGroups, s_GroupCount * sizeof(SpewGroup_t));

			// shift elements down to preserve order
			int numToMove = s_GroupCount - ind - 1;
			memmove(&s_pSpewGroups[ind + 1], &s_pSpewGroups[ind], numToMove * sizeof(SpewGroup_t));
		}
		else
			s_pSpewGroups = (SpewGroup_t *)malloc(s_GroupCount * sizeof(SpewGroup_t));

		Assert(strlen(pGroupName) < MAX_GROUP_NAME_LENGTH);
		strcpy(s_pSpewGroups[ind].m_GroupName, pGroupName);
	}
	s_pSpewGroups[ind].m_Level = level;
}

// Tests to see if a particular spew is active
bool IsSpewActive(const char *pGroupName, int level)
{
	// If we don't find the spew group, use the default level.
	int ind;
	if (FindSpewGroup(pGroupName, &ind))
		return s_pSpewGroups[ind].m_Level >= level;
	else
		return s_DefaultLevel >= level;
}