/*
** dthinker.cpp
** Implements the base class for almost anything in a level that might think
**
**---------------------------------------------------------------------------
** Copyright 1998-2005 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#include "dthinker.h"
#include "stats.h"
#include "p_local.h"
#include "statnums.h"
#include "i_system.h"
#include "doomerrors.h"


static cycle_t ThinkCycles;
extern cycle_t BotSupportCycles;
extern cycle_t BotWTG;

IMPLEMENT_CLASS (DThinker)

static Node *NextToThink;

List DThinker::Thinkers[MAX_STATNUM+1];
List DThinker::FreshThinkers[MAX_STATNUM+1];
bool DThinker::bSerialOverride = false;

void DThinker::SerializeAll (FArchive &arc, bool hubLoad)
{
	Node *node;
	DThinker *thinker;
	BYTE stat;
	int statcount;
	int i;

	// Save lists of thinkers, but not by storing the first one and letting
	// the archiver catch the rest. (Which leads to buttloads of recursion
	// and makes the file larger.) Instead, we explicitly save each thinker
	// in sequence. When restoring an archive, we also have to maintain
	// the thinker lists here instead of relying on the archiver to do it
	// for us.

	if (arc.IsStoring ())
	{
		for (statcount = i = 0; i <= MAX_STATNUM; i++)
		{
			if (!Thinkers[i].IsEmpty ())
			{
				statcount++;
			}
		}
		arc << statcount;
		for (i = 0; i <= MAX_STATNUM; i++)
		{
			node = Thinkers[i].Head;
			if (node->Succ != NULL)
			{
				stat = i;
				arc << stat;
				do
				{
					thinker = static_cast<DThinker *> (node);
					arc << thinker;
					node = node->Succ;
				} while (node->Succ != NULL);
				thinker = NULL;
				arc << thinker;		// Save a final NULL for this list
			}
		}
	}
	else
	{
		if (hubLoad)
			DestroyMostThinkers ();
		else
			DestroyAllThinkers ();

		// Prevent the constructor from inserting thinkers into a list.
		bSerialOverride = true;

		try
		{
			arc << statcount;
			while (statcount > 0)
			{
				arc << stat << thinker;
				while (thinker != NULL)
				{
					Thinkers[stat].AddTail (thinker);
					arc << thinker;
				}
				statcount--;
			}
		}
		catch (class CDoomError &)
		{
			bSerialOverride = false;
			DestroyAllThinkers ();
			throw;
		}
		bSerialOverride = false;
	}
}

DThinker::DThinker (int statnum) throw()
{
	if (bSerialOverride)
	{ // The serializer will insert us into the right list
		Succ = NULL;
		return;
	}

	ObjectFlags |= OF_JustSpawned;
	if ((unsigned)statnum > MAX_STATNUM)
	{
		statnum = MAX_STATNUM;
	}
	FreshThinkers[statnum].AddTail (this);
}

DThinker::~DThinker ()
{
	if (Succ != NULL)
	{
		Remove ();
	}
}

void DThinker::Destroy ()
{
	if (this == NextToThink)
		NextToThink = Succ;

	if (Succ != NULL)
	{
		Remove ();
		Succ = NULL;
	}
	Super::Destroy ();
}

void DThinker::PostBeginPlay ()
{
}

void DThinker::ChangeStatNum (int statnum)
{
	if ((unsigned)statnum > MAX_STATNUM)
	{
		statnum = MAX_STATNUM;
	}
	Remove ();
	if ((ObjectFlags & OF_JustSpawned) && statnum >= STAT_FIRST_THINKING)
	{
		FreshThinkers[statnum].AddTail (this);
	}
	else
	{
		Thinkers[statnum].AddTail (this);
	}
}

// Destroy every thinker
void DThinker::DestroyAllThinkers ()
{
	int i;

	DObject::BeginFrame ();
	for (i = 0; i <= MAX_STATNUM; i++)
	{
		if (i != STAT_TRAVELLING)
		{
			DestroyThinkersInList (Thinkers[i].Head);
			DestroyThinkersInList (FreshThinkers[i].Head);
		}
	}
	DObject::EndFrame ();
}

// Destroy all thinkers except for player-controlled actors
// Players are simply removed from the list of thinkers and
// will be added back after serialization is complete.
void DThinker::DestroyMostThinkers ()
{
	int i;

	DObject::BeginFrame ();
	for (i = 0; i <= MAX_STATNUM; i++)
	{
		if (i != STAT_TRAVELLING)
		{
			DestroyMostThinkersInList (Thinkers[i], i);
			DestroyMostThinkersInList (FreshThinkers[i], i);
		}
	}
	DObject::EndFrame ();
}

void DThinker::DestroyThinkersInList (Node *node)
{
	while (node->Succ != NULL)
	{
		Node *next = node->Succ;
		static_cast<DThinker *> (node)->Destroy ();
		node = next;
	}
}

void DThinker::DestroyMostThinkersInList (List &list, int stat)
{
	if (stat != STAT_PLAYER)
	{
		DestroyThinkersInList (list.Head);
	}
	else
	{
		Node *node = list.Head;
		while (node->Succ != NULL)
		{
			Node *next = node->Succ;
			node->Remove ();
			node = next;
		}
	}
}

void DThinker::RunThinkers ()
{
	int i, count;

	ThinkCycles = BotSupportCycles = BotWTG = 0;

	clock (ThinkCycles);

	// Tick every thinker left from last time
	for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i)
	{
		TickThinkers (&Thinkers[i], NULL);
	}

	// Keep ticking the fresh thinkers until there are no new ones.
	do
	{
		count = 0;
		for (i = STAT_FIRST_THINKING; i <= MAX_STATNUM; ++i)
		{
			count += TickThinkers (&FreshThinkers[i], &Thinkers[i]);
		}
	} while (count != 0);

	unclock (ThinkCycles);
}

int DThinker::TickThinkers (List *list, List *dest)
{
	int count = 0;
	Node *node = list->Head;

	while (node->Succ != NULL)
	{
		++count;
		NextToThink = node->Succ;
		DThinker *thinker = static_cast<DThinker *> (node);
		if (thinker->ObjectFlags & OF_JustSpawned)
		{
			thinker->ObjectFlags &= ~OF_JustSpawned;
			if (dest != NULL)
			{ // Move thinker from this list to the destination list
				node->Remove ();
				dest->AddTail (node);
			}
			thinker->PostBeginPlay ();
		}
		else if (dest != NULL)
		{ // Move thinker from this list to the destination list
			I_Error ("There is a thinker in the fresh list that has already ticked.\n");
		}

		if (!(thinker->ObjectFlags & OF_MassDestruction))
		{ // Only tick thinkers not scheduled for destruction
			thinker->Tick ();
		}
		node = NextToThink;
	}
	return count;
}

void DThinker::Tick ()
{
}

FThinkerIterator::FThinkerIterator (TypeInfo *type, int statnum)
{
	if ((unsigned)statnum > MAX_STATNUM)
	{
		m_Stat = STAT_FIRST_THINKING;
		m_SearchStats = true;
	}
	else
	{
		m_Stat = statnum;
		m_SearchStats = false;
	}
	m_ParentType = type;
	m_CurrThinker = DThinker::Thinkers[m_Stat].Head;
	m_SearchingFresh = false;
}

FThinkerIterator::FThinkerIterator (TypeInfo *type, int statnum, DThinker *prev)
{
	if ((unsigned)statnum > MAX_STATNUM)
	{
		m_Stat = STAT_FIRST_THINKING;
		m_SearchStats = true;
	}
	else
	{
		m_Stat = statnum;
		m_SearchStats = false;
	}
	m_ParentType = type;
	if (prev == NULL || prev->Succ == NULL)
	{
		Reinit ();
	}
	else
	{
		m_CurrThinker = prev->Succ;
		m_SearchingFresh = false;
	}
}

void FThinkerIterator::Reinit ()
{
	m_CurrThinker = DThinker::Thinkers[m_Stat].Head;
	m_SearchingFresh = false;
}

DThinker *FThinkerIterator::Next ()
{
	do
	{
		do
		{
			while (m_CurrThinker->Succ)
			{
				DThinker *thinker = static_cast<DThinker *> (m_CurrThinker);
				if (thinker->IsKindOf (m_ParentType))
				{
					m_CurrThinker = m_CurrThinker->Succ;
					return thinker;
				}
				m_CurrThinker = m_CurrThinker->Succ;
			}
			if ((m_SearchingFresh = !m_SearchingFresh))
			{
				m_CurrThinker = DThinker::FreshThinkers[m_Stat].Head;
			}
		} while (m_SearchingFresh);
		if (m_SearchStats)
		{
			m_Stat++;
			if (m_Stat > MAX_STATNUM)
			{
				m_Stat = STAT_FIRST_THINKING;
			}
		}
		m_CurrThinker = DThinker::Thinkers[m_Stat].Head;
		m_SearchingFresh = false;
	} while (m_SearchStats && m_Stat != STAT_FIRST_THINKING);
	return NULL;
}

ADD_STAT (think, out)
{
	sprintf (out, "Think time = %04.1f ms",
		SecondsPerCycle * (double)ThinkCycles * 1000);
}
