#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "trains.h"
#include "saverestore.h"

class CPathCorner : public CPointEntity
{
public:
	void Spawn(void);
	void KeyValue(KeyValueData *pkvd);
	float GetDelay(void) { return m_flWait; }

private:
	float m_flWait;
};

LINK_ENTITY_TO_CLASS(path_corner, CPathCorner);

void CPathCorner::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "wait"))
	{
		m_flWait = atof(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else
		CPointEntity::KeyValue(pkvd);
}

void CPathCorner::Spawn(void)
{
}

LINK_ENTITY_TO_CLASS(path_track, CPathTrack);

void CPathTrack::KeyValue(KeyValueData *pkvd)
{
	if (FStrEq(pkvd->szKeyName, "altpath"))
	{
		m_altName = ALLOC_STRING(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else
		CPointEntity::KeyValue(pkvd);
}

void CPathTrack::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
{
	if (m_paltpath)
	{
		int on = !FBitSet(pev->spawnflags, SF_PATH_ALTERNATE);

		if (ShouldToggle(useType, on))
		{
			if (on)
				SetBits(pev->spawnflags, SF_PATH_ALTERNATE);
			else
				ClearBits(pev->spawnflags, SF_PATH_ALTERNATE);
		}
	}
	else
	{
		int on = !FBitSet(pev->spawnflags, SF_PATH_DISABLED);

		if (ShouldToggle(useType, on))
		{
			if (on)
				SetBits(pev->spawnflags, SF_PATH_DISABLED);
			else
				ClearBits(pev->spawnflags, SF_PATH_DISABLED);
		}
	}
}

void CPathTrack::Link(void)
{
	if (!FStringNull(pev->target))
	{
		edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->target));

		if (!FNullEnt(pentTarget))
		{
			m_pnext = CPathTrack::Instance(pentTarget);

			if (m_pnext)
				m_pnext->SetPrevious(this);
		}
		else
			ALERT(at_console, "Dead end link %s\n", STRING(pev->target));
	}

	if (m_altName)
	{
		edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_altName));

		if (!FNullEnt(pentTarget))
		{
			m_paltpath = CPathTrack::Instance(pentTarget);

			if (m_paltpath)
				m_paltpath->SetPrevious(this);
		}
	}
}

void CPathTrack::Spawn(void)
{
	pev->solid = SOLID_TRIGGER;
	UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8));

	m_pnext = NULL;
	m_pprevious = NULL;
}

void CPathTrack::Activate(void)
{
	if (!FStringNull(pev->targetname))
		Link();
}

CPathTrack *CPathTrack::ValidPath(CPathTrack *ppath, int testFlag)
{
	if (!ppath)
		return NULL;

	if (testFlag && FBitSet(ppath->pev->spawnflags, SF_PATH_DISABLED))
		return NULL;

	return ppath;
}

void CPathTrack::Project(CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist)
{
	if (pstart && pend)
	{
		Vector dir = (pend->pev->origin - pstart->pev->origin);
		dir = dir.Normalize();
		*origin = pend->pev->origin + dir * dist;
	}
}

CPathTrack *CPathTrack::GetNext(void)
{
	if (m_paltpath && FBitSet(pev->spawnflags, SF_PATH_ALTERNATE) && !FBitSet(pev->spawnflags, SF_PATH_ALTREVERSE))
		return m_paltpath;

	return m_pnext;
}

CPathTrack *CPathTrack::GetPrevious(void)
{
	if (m_paltpath && FBitSet(pev->spawnflags, SF_PATH_ALTERNATE) && FBitSet(pev->spawnflags, SF_PATH_ALTREVERSE))
		return m_paltpath;

	return m_pprevious;
}

void CPathTrack::SetPrevious(CPathTrack *pprev)
{
	if (pprev && !FStrEq(STRING(pprev->pev->targetname), STRING(m_altName)))
		m_pprevious = pprev;
}

CPathTrack *CPathTrack::LookAhead(Vector *origin, float dist, int move)
{
	float originalDist = dist;
	CPathTrack *pcurrent = this;
	Vector currentPos = *origin;

	if (dist < 0)
	{
		dist = -dist;

		while (dist > 0)
		{
			Vector dir = pcurrent->pev->origin - currentPos;
			float length = dir.Length();

			if (!length)
			{
				if (!ValidPath(pcurrent->GetPrevious(), move))
				{
					if (!move)
						Project(pcurrent->GetNext(), pcurrent, origin, dist);

					return NULL;
				}

				pcurrent = pcurrent->GetPrevious();
			}
			else if (length > dist)
			{
				*origin = currentPos + (dir * (dist / length));
				return pcurrent;
			}
			else
			{
				dist -= length;
				currentPos = pcurrent->pev->origin;
				*origin = currentPos;

				if (!ValidPath(pcurrent->GetPrevious(), move))
					return NULL;

				pcurrent = pcurrent->GetPrevious();
			}
		}

		*origin = currentPos;
		return pcurrent;
	}
	else
	{
		while (dist > 0)
		{
			if (!ValidPath(pcurrent->GetNext(), move))
			{
				if (!move)
					Project(pcurrent->GetPrevious(), pcurrent, origin, dist);

				return NULL;
			}

			Vector dir = pcurrent->GetNext()->pev->origin - currentPos;
			float length = dir.Length();

			if (!length && !ValidPath(pcurrent->GetNext()->GetNext(), move))
			{
				if (dist == originalDist)
					return NULL;

				return pcurrent;
			}

			if (length > dist)
			{
				*origin = currentPos + (dir * (dist / length));
				return pcurrent;
			}
			else
			{
				dist -= length;
				currentPos = pcurrent->GetNext()->pev->origin;
				pcurrent = pcurrent->GetNext();
				*origin = currentPos;
			}
		}

		*origin = currentPos;
	}

	return pcurrent;
}

CPathTrack *CPathTrack::Nearest(Vector origin)
{
	Vector delta = origin - pev->origin;
	delta.z = 0;
	float minDist = delta.Length();
	CPathTrack *pnearest = this;
	CPathTrack *ppath = GetNext();
	int deadCount = 0;

	while (ppath && ppath !=this)
	{
		deadCount++;

		if (deadCount > 9999)
		{
			ALERT(at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname));
			return NULL;
		}

		delta = origin - ppath->pev->origin;
		delta.z = 0;
		float dist = delta.Length();

		if (dist < minDist)
		{
			minDist = dist;
			pnearest = ppath;
		}

		ppath = ppath->GetNext();
	}

	return pnearest;
}

CPathTrack *CPathTrack::Instance(edict_t *pent)
{
	if (FClassnameIs(pent, "path_track"))
		return (CPathTrack *)GET_PRIVATE(pent);

	return NULL;
}