/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors

This file is part of the OpenJK source code.

OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

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, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#include <memory>
#include "g_local.h"
#include "../Rufl/hstring.h"
#include "qcommon/ojk_saved_game_helper.h"

#define MAX_GTIMERS	16384

typedef struct gtimer_s
{
	hstring id;				// Use handle strings, so that things work after loading
	int time;
	struct gtimer_s *next;	// In either free list or current list
} gtimer_t;

gtimer_t g_timerPool[ MAX_GTIMERS ];
gtimer_t *g_timers[ MAX_GENTITIES ];
gtimer_t *g_timerFreeList;


static int TIMER_GetCount(int num)
{
	gtimer_t *p = g_timers[num];
	int count = 0;

	while (p)
	{
		count++;
		p = p->next;
	}

	return count;
}


/*
-------------------------
TIMER_RemoveHelper

Scans an entities timer list to remove a given
timer from the list and put it on the free list

Doesn't do much error checking, only called below
-------------------------
*/
static void TIMER_RemoveHelper( int num, gtimer_t *timer )
{
	gtimer_t *p = g_timers[num];

	// Special case: first timer in list
	if (p == timer)
	{
		g_timers[num] = g_timers[num]->next;
		p->next = g_timerFreeList;
		g_timerFreeList = p;
		return;
	}

	// Find the predecessor
	while (p->next != timer)
	{
		p = p->next;
	}

	// Rewire
	p->next = p->next->next;
	timer->next = g_timerFreeList;
	g_timerFreeList = timer;
	return;
}




/*
-------------------------
TIMER_Clear
-------------------------
*/

void TIMER_Clear( void )
{
	int i;
	for (i = 0; i < MAX_GENTITIES; i++)
	{
		g_timers[i] = NULL;
	}

	for (i = 0; i < MAX_GTIMERS - 1; i++)
	{
		g_timerPool[i].next = &g_timerPool[i+1];
	}
	g_timerPool[MAX_GTIMERS-1].next = NULL;
	g_timerFreeList = &g_timerPool[0];
}

/*
-------------------------
TIMER_Clear
-------------------------
*/

void TIMER_Clear( int idx )
{
	// rudimentary safety checks, might be other things to check?
	if ( idx >= 0 && idx < MAX_GENTITIES )
	{
		gtimer_t *p = g_timers[idx];

		// No timers at all -> do nothing
		if (!p)
		{
			return;
		}

		// Find the end of this ents timer list
		while (p->next)
		{
			p = p->next;
		}

		// Splice the lists
		p->next = g_timerFreeList;
		g_timerFreeList = g_timers[idx];
		g_timers[idx] = NULL;
		return;
	}
}


/*
-------------------------
TIMER_Save
-------------------------
*/

void TIMER_Save( void )
{
	int			j;
	gentity_t	*ent;

	ojk::SavedGameHelper saved_game(
		::gi.saved_game);

	for ( j = 0, ent = &g_entities[0]; j < MAX_GENTITIES; j++, ent++ )
	{
		unsigned char numTimers = TIMER_GetCount(j);

		if ( !ent->inuse && numTimers)
		{
//			Com_Printf( "WARNING: ent with timers not inuse\n" );
			assert(numTimers);
			TIMER_Clear( j );
			numTimers = 0;
		}

		//Write out the timer information
		saved_game.write_chunk<uint8_t>(
			INT_ID('T', 'I', 'M', 'E'),
			numTimers);

		gtimer_t *p = g_timers[j];
		assert ((numTimers && p) || (!numTimers && !p));

		while(p)
		{
			const char	*timerID = p->id.c_str();
			const int	length = strlen(timerID) + 1;
			const int	time = p->time - level.time;	//convert this back to delta so we can use SET after loading

			assert( length < 1024 );//This will cause problems when loading the timer if longer

			//Write out the id string
			saved_game.write_chunk(
				INT_ID('T', 'M', 'I', 'D'),
				timerID,
				length);

			//Write out the timer data
			saved_game.write_chunk<int32_t>(
				INT_ID('T', 'D', 'T', 'A'),
				time);

			p = p->next;
		}
	}
}

/*
-------------------------
TIMER_Load
-------------------------
*/

void TIMER_Load( void )
{
	int j;
	gentity_t	*ent;

	ojk::SavedGameHelper saved_game(
		::gi.saved_game);

	for ( j = 0, ent = &g_entities[0]; j < MAX_GENTITIES; j++, ent++ )
	{
		unsigned char numTimers = 0;

		saved_game.read_chunk<uint8_t>(
			INT_ID('T', 'I', 'M', 'E'),
			numTimers);

		//Read back all entries
		for ( int i = 0; i < numTimers; i++ )
		{
			int		time = 0;
			char	tempBuffer[1024];	// Still ugly. Setting ourselves up for 007 AUF all over again. =)

			assert (sizeof(g_timers[0]->time) == sizeof(time) );//make sure we're reading the same size as we wrote

			//Read the id string and time
			saved_game.read_chunk(
				INT_ID('T', 'M', 'I', 'D'));

			const char* sg_buffer_data = static_cast<const char*>(
				saved_game.get_buffer_data());

			int sg_buffer_size = saved_game.get_buffer_size();

			if (sg_buffer_size < 0 || static_cast<size_t>(sg_buffer_size) >= sizeof(tempBuffer))
			{
				::G_Error("invalid length for TMID string in saved game: %d\n", sg_buffer_size);
			}

			std::uninitialized_copy_n(
				sg_buffer_data,
				sg_buffer_size,
				tempBuffer);

			tempBuffer[sg_buffer_size] = '\0';

			saved_game.read_chunk<int32_t>(
				INT_ID('T', 'D', 'T', 'A'),
				time);

			//this is odd, we saved all the timers in the autosave, but not all the ents are spawned yet from an auto load, so skip it
			if (ent->inuse)
			{	//Restore it
				TIMER_Set(ent, tempBuffer, time);
			}
		}
	}
}


static gtimer_t *TIMER_GetNew(int num, const char *identifier)
{
	assert(num < ENTITYNUM_MAX_NORMAL);//don't want timers on NONE or the WORLD
	gtimer_t *p = g_timers[num];

	// Search for an existing timer with this name
	while (p)
	{
		if (p->id == identifier)
		{ // Found it
			return p;
		}

		p = p->next;
	}

	// No existing timer with this name was found, so grab one from the free list
	if (!g_timerFreeList)
	{//oh no, none free!
		assert(g_timerFreeList);
		return NULL;
	}

	p = g_timerFreeList;
	g_timerFreeList = g_timerFreeList->next;
	p->next = g_timers[num];
	g_timers[num] = p;
	return p;
}


gtimer_t *TIMER_GetExisting(int num, const char *identifier)
{
	gtimer_t *p = g_timers[num];

	while (p)
	{
		if (p->id == identifier)
		{ // Found it
			return p;
		}

		p = p->next;
	}

	return NULL;
}



/*
-------------------------
TIMER_Set
-------------------------
*/

void TIMER_Set( gentity_t *ent, const char *identifier, int duration )
{
	assert(ent->inuse);
	gtimer_t *timer = TIMER_GetNew(ent->s.number, identifier);

	if (timer)
	{
		timer->id	= identifier;
		timer->time = level.time + duration;
	}
}

/*
-------------------------
TIMER_Get
-------------------------
*/

int	TIMER_Get( gentity_t *ent, const char *identifier )
{
	gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier);

	if (!timer)
	{
		return -1;
	}

	return timer->time;
}

/*
-------------------------
TIMER_Done
-------------------------
*/

qboolean TIMER_Done( gentity_t *ent, const char *identifier )
{
	gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier);

	if (!timer)
	{
		return qtrue;
	}

	return (qboolean)(timer->time < level.time);
}

/*
-------------------------
TIMER_Done2

Returns false if timer has been
started but is not done...or if
timer was never started
-------------------------
*/

qboolean TIMER_Done2( gentity_t *ent, const char *identifier, qboolean remove )
{
	gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier);
	qboolean res;

	if (!timer)
	{
		return qfalse;
	}

	res = (qboolean)(timer->time < level.time);

	if (res && remove)
	{
		// Put it back on the free list
		TIMER_RemoveHelper(ent->s.number, timer);
	}

	return res;
}

/*
-------------------------
TIMER_Exists
-------------------------
*/
qboolean TIMER_Exists( gentity_t *ent, const char *identifier )
{
	return (qboolean)(TIMER_GetExisting(ent->s.number, identifier) != NULL);
}



/*
-------------------------
TIMER_Remove
Utility to get rid of any timer
-------------------------
*/
void TIMER_Remove( gentity_t *ent, const char *identifier )
{
	gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier);

	if (!timer)
	{
		return;
	}

	// Put it back on the free list
	TIMER_RemoveHelper(ent->s.number, timer);
}

/*
-------------------------
TIMER_Start
-------------------------
*/

qboolean TIMER_Start( gentity_t *self, const char *identifier, int duration )
{
	if ( TIMER_Done( self, identifier ) )
	{
		TIMER_Set( self, identifier, duration );
		return qtrue;
	}
	return qfalse;
}
