/*
*
*   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
*
*/

#include "precompiled.h"

TYPEDESCRIPTION CTriggerRandom::m_SaveData[] =
{
	DEFINE_FIELD(CTriggerRandom, m_uiTargetsUse, FIELD_INTEGER),
	DEFINE_ARRAY(CTriggerRandom, m_iszTargets, FIELD_STRING, MAX_TR_TARGETS),
	DEFINE_FIELD(CTriggerRandom, m_bActive, FIELD_CHARACTER),
	DEFINE_FIELD(CTriggerRandom, m_flMinDelay, FIELD_FLOAT),
	DEFINE_FIELD(CTriggerRandom, m_flMaxDelay, FIELD_FLOAT)
};

LINK_ENTITY_TO_CLASS(trigger_random, CTriggerRandom, CCSTriggerRandom)
LINK_ENTITY_TO_CLASS(trigger_random_time, CTriggerRandom, CCSTriggerRandom)		// Obsolete: use trigger_random with Timed flag
LINK_ENTITY_TO_CLASS(trigger_random_unique, CTriggerRandom, CCSTriggerRandom)	// Obsolete: use trigger_ranom with Random flag. Unique Trigger Random. Randomly selects an unused trigger.

IMPLEMENT_SAVERESTORE(CTriggerRandom, CBaseDelay)

void CTriggerRandom::Spawn()
{
	m_bActive = (pev->spawnflags & SF_RANDOM_STARTON) == SF_RANDOM_STARTON;

	if (FClassnameIs(pev, "trigger_random_time"))
	{
		pev->spawnflags |= SF_RANDOM_TIMED;
	}
	else if (FClassnameIs(pev, "trigger_random_unique"))
	{
		pev->spawnflags |= SF_RANDOM_UNIQUE;

		if (pev->spawnflags & SF_RANDOM_STARTON)
		{
			pev->spawnflags &= ~SF_RANDOM_STARTON;
			pev->spawnflags |= SF_RANDOM_REUSABLE;
		}
	}

	if (pev->spawnflags & SF_RANDOM_TIMED)
	{
		pev->nextthink = gpGlobals->time + RandomDelay();
		SetThink(&CTriggerRandom::RandomThink);
	}

	if (pev->spawnflags & SF_RANDOM_UNIQUE)
	{
		InitUnique();
	}
}

void CTriggerRandom::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "target_count"))
	{
		m_uiTargetsUse = Q_atoi(pkvd->szValue);

		if (m_uiTargetsUse >= MAX_TR_TARGETS)
		{
			m_uiTargetsUse = MAX_TR_TARGETS;
		}

		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "min_delay"))
	{
		m_flMinDelay = Q_atof(pkvd->szValue);

		if (m_flMaxDelay > 0 && m_flMinDelay > m_flMaxDelay)
		{
			SWAP(m_flMinDelay, m_flMaxDelay);
		}

		pkvd->fHandled = TRUE;
	}
	else if (FStrEq(pkvd->szKeyName, "max_delay"))
	{
		m_flMaxDelay = Q_atof(pkvd->szValue);

		if (m_flMinDelay > 0 && m_flMaxDelay < m_flMinDelay)
		{
			SWAP(m_flMinDelay, m_flMaxDelay);
		}

		pkvd->fHandled = TRUE;
	}
	else
	{
		if (FStrnEq(pkvd->szKeyName, "target", sizeof("target") - 1))
		{
			char *pszTargetName = nullptr;
			int iTargetsCount = strtoul(&pkvd->szKeyName[sizeof("target") - 1], &pszTargetName, 10);
			if (iTargetsCount < MAX_TR_TARGETS && pszTargetName && pszTargetName[0] == '\0')
			{
				m_iszTargets[iTargetsCount] = ALLOC_STRING(pkvd->szValue);
				pkvd->fHandled = TRUE;
				return;
			}
		}

		CBaseDelay::KeyValue(pkvd);
	}
}

void CTriggerRandom::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	if (pev->spawnflags & SF_RANDOM_TIMED)
	{
		m_bActive ^= true;

		pev->nextthink = gpGlobals->time + RandomDelay();
		SetThink(&CTriggerRandom::RandomThink);

		return;
	}

	Fire(pActivator);
}

void CTriggerRandom::RandomThink()
{
	if (m_bActive)
	{
		Fire(this);

		if (pev->spawnflags & SF_RANDOM_ONCE)
		{
			m_bActive = false;
		}
	}

	if (m_flMinDelay <= 0 || m_flMaxDelay <= 0)
	{
		m_bActive = false;
		return;
	}

	pev->nextthink = RandomDelay() + gpGlobals->time;
}

void CTriggerRandom::InitUnique()
{
	m_uiTargetsFired = 0;
	Q_memset(m_bActiveTargets, 0, MAX_TR_TARGETS);
}

void CTriggerRandom::Fire(CBaseEntity *pActivator)
{
	string_t iszSelectTarget = iStringNull;
	if (pev->spawnflags & SF_RANDOM_UNIQUE)
	{
		if (m_uiTargetsFired >= m_uiTargetsUse)
		{
			if (pev->spawnflags & SF_RANDOM_REUSABLE)
			{
				InitUnique();
				ALERT(at_aiconsole, "%s(%s): all targets fired; reusable mode on; resetting state\n", pev->classname.str(), pev->targetname.str());
			}
			else
			{
				// no re-usable
				return;
			}
		}

		unsigned int iRandomTarget = 0;
		const int MAX_SELECT_ATTEMPT = 256;

		for (int iSelect = 0; iSelect < MAX_SELECT_ATTEMPT; iSelect++)
		{
			iRandomTarget = RANDOM_LONG(0, m_uiTargetsUse - 1);

			if (!m_bActiveTargets[iRandomTarget])
				break;
		}

		// if queue is busy, try select first free target
		if (m_bActiveTargets[iRandomTarget])
		{
			ALERT(at_aiconsole, "%s(%s): random selection failed, selecting first free target\n", pev->classname.str(), pev->targetname.str());

			for (iRandomTarget = 0; iRandomTarget < m_uiTargetsUse; iRandomTarget++)
			{
				if (!m_bActiveTargets[iRandomTarget])
					break;
			}
		}

		iszSelectTarget = m_iszTargets[iRandomTarget];
		m_bActiveTargets[iRandomTarget] = true;
		m_uiTargetsFired++;
	}
	else
	{
		const int MAX_SELECT_ATTEMPT = 10;
		for (int iSelect = 0; iSelect < MAX_SELECT_ATTEMPT; iSelect++)
		{
			iszSelectTarget = m_iszTargets[RANDOM_LONG(0, m_uiTargetsUse - 1)];

			// free target
			if (!iszSelectTarget.IsNull())
			{
				break;
			}
		}
	}

	FireTargets(iszSelectTarget, pActivator, this, USE_TOGGLE, 0);
}

float CTriggerRandom::RandomDelay()
{
	return RANDOM_FLOAT(m_flMinDelay, m_flMaxDelay);
}