// ObjectFile.cpp: implementation of the CObjectFile class.
//
//////////////////////////////////////////////////////////////////////

#include "clientapi.h"
#include "listnode.h"
#include "clientdepotuser.h"
#include "clientdiruser.h"
#include "clientfileuser.h"
#include "filehead.h"
#include "changesorter.h"
#include "filelogcache.h"
#include "p4objects.h"
#include "enginecallback.h"
#include "objectrev.h"

LINK_ENTITY_TO_CLASS( object_file, CObjectFile)
#define FILE_HEIGHT 10.0
#define REV_HEIGHT 2.0

void CObjectFile::Spawn()
{
	intonotfrom = false;
	Precache( );

	switch (ftype)
	{
	default:
	case FTYPE_TEX:
		SET_MODEL( ENT(pev), "models/file/w_file.mdl" );
		break;
	case FTYPE_BIN:
		SET_MODEL( ENT(pev), "models/file/w_fileb.mdl" );
		break;
	case FTYPE_APP:
		SET_MODEL( ENT(pev), "models/file/w_filea.mdl" );
		break;
	case FTYPE_SYM:
		SET_MODEL( ENT(pev), "models/file/w_files.mdl" );
		break;
	}
	UTIL_SetOrigin( pev, pev->origin );
	target = (Vector)pev->origin;  //by default, we're already where we want to be.

	UTIL_SetSize( pev, Vector(-5, -15, 38), Vector( 5, 15, 86) );  //collision box!

	level = 0;

	pev->movetype = MOVETYPE_FLY;
//			pev->sequence = LookupActivity (ACT_IDLE);
//			pev->frame = 0;
//			pev->framerate = 1;
	pev->solid = SOLID_BBOX; 

	SetThink( Think );
	SetTouch( Touch );

	pev->nextthink = gpGlobals->time + 0.5;
}

void CObjectFile::Precache()
{
	PRECACHE_MODEL("models/file/w_file.mdl");
	PRECACHE_MODEL("models/file/w_fileb.mdl");
	PRECACHE_MODEL("models/file/w_filea.mdl");
	PRECACHE_MODEL("models/file/w_files.mdl");
}

void CObjectFile :: Think( void )
{
	if (!IsInWorld())
	{
		parent->disown( this );
		UTIL_Remove( this );
		return;
	}
	if (UTIL_VecUpdate( &(pev->origin), target )) {
		pev->nextthink = gpGlobals->time + 0.05;
		UTIL_SetSize( pev, Vector(-5, -15, 38), Vector( 5, 15, 86) );  //collision box!
	}
	else
	{
		pev->nextthink = gpGlobals->time + 1.5;
	}
	if (cache)
	{
		if (alldone)
		{
			/* now that they've all been spawned we can run around setting up pointers */
			ListNode* scan = revs.head;
			CObjectRev* obj;
			FileRevArrow* arrow;
			while (scan != NULL)
			{
				obj = (CObjectRev*) scan->ent;
				if (obj == NULL) break;
				arrow = obj->revobj->fromarrows;
				while (arrow)
				{
					if (arrow->ptr->ent) 
					{
						obj->AddArrow((CObjectRev*)arrow->ptr->ent, arrow->type, arrow->contrib);
					}
					arrow = arrow->next;
				}
				scan = scan->next;
			}
			delete cache;
			cache = NULL;
			pev->nextthink = gpGlobals->time + 0.1;
			return;
		}
		if (revsmade > MAXRESULTS)
		{
			alldone = true;
			pev->nextthink = gpGlobals->time;
			return;
		}
		if (hwork == cache->main)
		{
			if (rwork != NULL)
			{
				CObjectRev* orev = GetClassPtr( (CObjectRev *) NULL);
				revsmade++;
				orev->type = rwork->type;
				revs.Append(orev);
				rwork->ent = orev;
				orev->revobj = rwork;
				int xcoord = cache->changes->GetPos(rwork->change);
				orev->pev->classname = MAKE_STRING("object_rev");
				orev->Spawn();
				orev->pev->origin = pev->origin + Vector(-xcoord, 0, 0);
				orev->target = target - Vector(0,0,100);
				orev->target.x -= xcoord*15;
				orev->target.z += (FILE_HEIGHT - 5.0);
				orev->ismain = 127;
				orev->istext = rwork->istext;
				orev->filerev = hwork->name;
				orev->filerev.Append("#");
				orev->filerev.Append(&(rwork->rev));
				orev->parent = this;
				orev->pev->nextthink = gpGlobals->time;
				rwork = rwork->next;
				pev->nextthink = gpGlobals->time + 0.1;
				return;
			} 
			else //start on the "from" list
			{
				hwork = cache->from;
				if (hwork) rwork = hwork->head;
				ycoord = 1;
				intonotfrom = false;
				pev->nextthink = gpGlobals->time;
				return;
			}
		}
		else
		{
			if (!intonotfrom)
			{
				if (hwork)
				{
					if (rwork != NULL)
					{
						CObjectRev* orev = GetClassPtr( (CObjectRev *) NULL);
						revsmade++;
						orev->type = rwork->type;
						revs.Append(orev);
						rwork->ent = orev;
						orev->revobj = rwork;
						int xcoord = cache->changes->GetPos(rwork->change);
						orev->pev->classname = MAKE_STRING("object_rev");
						orev->Spawn();
						orev->pev->origin = pev->origin + Vector(-xcoord, ycoord, 0);
						orev->target = target - Vector(0,0,100);
						orev->target.x -= xcoord*15;
						orev->target.y += ycoord*20;
						orev->target.z += (zcoord - RANDOM_FLOAT(0.0, REV_HEIGHT));
						orev->ismain = 0;
						orev->istext = rwork->istext;
						orev->filerev = hwork->name;
						orev->filerev.Append("#");
						orev->filerev.Append(&(rwork->rev));
						orev->parent = this;
						orev->pev->nextthink = gpGlobals->time;
						rwork = rwork->next;
						pev->nextthink = gpGlobals->time + 0.1;
						return;
					} 
					else //next FileHead
					{
						hwork = hwork->next;
						ycoord++;
						if (hwork) rwork = hwork->head;
						zcoord = RANDOM_FLOAT(-5.0, FILE_HEIGHT - 5.0);
						pev->nextthink = gpGlobals->time;
						return;
					}
				}
				else //on to the intos
				{
					hwork = cache->into;
					intonotfrom = true;
					if (hwork) rwork = hwork->head;
					ycoord = 1;
					pev->nextthink = gpGlobals->time;
					return;
				}
			}
			else //are we on the intos?
			{
				if (hwork)
				{
					if (rwork != NULL)
					{
						CObjectRev* orev = GetClassPtr( (CObjectRev *) NULL);
						revsmade++;
						orev->type = rwork->type;
						revs.Append(orev);
						rwork->ent = orev;
						orev->revobj = rwork;
						int xcoord = cache->changes->GetPos(rwork->change);
						orev->pev->classname = MAKE_STRING("object_rev");
						orev->Spawn();
						orev->pev->origin = pev->origin + Vector(-xcoord, ycoord, 0);
						orev->target = target - Vector(0,0,100);
						orev->target.x -= xcoord*15;
						orev->target.y -= ycoord*20;
						orev->target.z += (zcoord - RANDOM_FLOAT(0.0, REV_HEIGHT));
						orev->ismain = 0;
						orev->istext = rwork->istext;
						orev->filerev = hwork->name;
						orev->filerev.Append("#");
						orev->filerev.Append(&(rwork->rev));
						orev->parent = this;
						orev->pev->nextthink = gpGlobals->time;
						rwork = rwork->next;
						pev->nextthink = gpGlobals->time + 0.1;
						return;
					} 
					else //next FileHead
					{
						hwork = hwork->next;
						ycoord++;
						if (hwork) rwork = hwork->head;
						else alldone = true;
						zcoord = RANDOM_FLOAT(-5.0, FILE_HEIGHT - 5.0);
						pev->nextthink = gpGlobals->time;
						return;
					}
				}
				else //No "into" files?
				{
					alldone = true;
					return;
				}
			}
		}
	}
}

void CObjectFile :: Touch( CBaseEntity* Thing )
{ 
	CBasePlayer *pl;
	CBaseEntity *be = CBaseEntity::Instance(Thing->pev->owner);

	if (Thing->IsPlayer()) pl = (CBasePlayer*) Thing;
	else if (be->IsPlayer()) pl = (CBasePlayer*) be;
	else return;
	if (pl->m_flNextP4Time > gpGlobals->time) return;
	pl->m_flNextP4Time = gpGlobals->time + 0.5;

	if (Thing->IsExpand()) {
		Expand( pl );
		return;
	}

	if (Thing->IsLong()) {
		Long( pl );
		return;
	}
	pl->m_flNextP4Time = gpGlobals->time + 0.2;
	UTIL_PrintHud(pl, path.Text());
}

void CObjectFile::Long( CBasePlayer* pl )
{
	ClientFileUser ui;
	ui.logflag = TRUE;
	ClientApi client;
	if (P4PORT.Length()) client.SetPort(P4PORT.Text());
	Error e;
	StrBuf msg = StrBuf();

	client.Init( &e );
	if (e.GetSeverity()) {
		e.Fmt(&msg);
		UTIL_FatalHud(pl, msg.Text());
		pl->m_flNextP4Time = gpGlobals->time + 20.0; //20 second delay on connect error
		return;
	}
	
	char* args[1];
	args[0] = path.Text();

	client.SetArgv(1, args);
	client.Run( "filelog", &ui );

	client.Final( &e );
	if (e.GetSeverity()) {
		e.Fmt(&msg);
		UTIL_WarningHud(pl, msg.Text());
	}

	UTIL_PrintHud( pl, ui.filelog.Text());
}

void CObjectFile::Expand( CBasePlayer* pl )
{
	if (level == 1) return;
	StrBuf hudmsg = StrBuf();
	hudmsg.Append("Displaying history of ");
	hudmsg.Append(path.Text());
	hudmsg.Append("...");
	UTIL_PrintHud( pl, hudmsg.Text());
	//killKids(this);

	newLevel(1);
	parent->killKids(this);

	ClientApi client;
	Error e;
	if (P4PORT.Length()) client.SetPort(P4PORT.Text());
	cache = new FileLogCache(path, &client, &e);
	if (e.IsFatal())
	{
		StrBuf msg;
		e.Fmt(&msg);
		UTIL_FatalHud(pl, msg.Text());
		pl->m_flNextP4Time = gpGlobals->time + 20.0;
		delete cache;
		cache = NULL;
		return;
	}

	/* create an entity for each one - don't set up "from" pointers yet*/
	if (cache->main == NULL) return;
	hwork = cache->main;
	rwork = hwork->head;
	alldone = false;
	pev->nextthink = gpGlobals->time;
	/* From here the "Think" function will take care of everything. */
}

void CObjectFile :: newLevel( short newlev )
{
	if (newlev > 0)
	{
		Vector parvec = parent->targvec();
		target.x = parvec.x;
		target.y = parvec.y;
	}
	target.z += (newlev - level)*100;
	level = newlev;
	parent->newLevel(level+1);
}

void CObjectFile :: disown ( CBaseEntity *Target ) //this does the OPPOSITE of killKids!!!
{
	revs.EKill(Target);
}

void CObjectFile :: killKids( CBaseEntity *Caller )
{
	bool callerFlag = false;
	CBaseEntity* kid = revs.EPop();
	while (kid != NULL)
	{
		if (kid == Caller) 
		{
			callerFlag = TRUE;
			kid = revs.EPop();
			continue;
		}
		kid -> killKids(this);
		UTIL_Remove(kid);
		kid = revs.EPop();
	}
	if (callerFlag) revs.Append(Caller);
}

CObjectFile::CObjectFile()
{

}

/*
CObjectFile::~CObjectFile()
{

}
*/