/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 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.

	Warzone 2100 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 Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
 * Group.c
 *
 * Link droids together into a group for AI etc.
 *
 */

#include "lib/framework/frame.h"
#include "objects.h"
#include "group.h"
#include "orderdef.h"

#include "multiplay.h"

// sizes for the group heap
#define GRP_HEAP_INIT	45
#define GRP_HEAP_EXT	15

// heap for the droid group structure
OBJ_HEAP	*psGrpHeap;


// initialise the group system
BOOL grpInitialise(void)
{
	if (!HEAP_CREATE(&psGrpHeap, sizeof(DROID_GROUP), GRP_HEAP_INIT, GRP_HEAP_EXT))
	{
		return FALSE;
	}

	return TRUE;
}


// shutdown the group system
void grpShutDown(void)
{
	HEAP_DESTROY(psGrpHeap);
}


// create a new group
BOOL grpCreate(DROID_GROUP	**ppsGroup)
{
	if (!HEAP_ALLOC(psGrpHeap, (void*) ppsGroup))
	{
		return FALSE;
	}

	(*ppsGroup)->type = GT_NORMAL;
	(*ppsGroup)->refCount = 0;
	(*ppsGroup)->psList = NULL;
	(*ppsGroup)->psCommander = NULL;
	memset(&(*ppsGroup)->sRunData, 0, sizeof(RUN_DATA));

	return TRUE;
}


// add a droid to a group
void grpJoin(DROID_GROUP *psGroup, DROID *psDroid)
{
	psGroup->refCount += 1;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"grpJoin: invalid group pointer" );

	// if psDroid == NULL just increase the refcount don't add anything to the list
	if (psDroid != NULL)
	{
		if (psGroup->psList && psDroid->player != psGroup->psList->player)
		{
			ASSERT( FALSE,"grpJoin: Cannot have more than one players droids in a group" );
			return;
		}

		if (psDroid->psGroup != NULL)
		{
			grpLeave(psDroid->psGroup, psDroid);
		}

		psDroid->psGroup = psGroup;

		if (psDroid->droidType == DROID_TRANSPORTER)
		{
			ASSERT( (psGroup->type == GT_NORMAL),
				"grpJoin: Cannot have two transporters in a group" );
			psGroup->type = GT_TRANSPORTER;
			psDroid->psGrpNext = psGroup->psList;
			psGroup->psList = psDroid;
		}
		else if ((psDroid->droidType == DROID_COMMAND) &&
				 (psGroup->type != GT_TRANSPORTER))
		{
			ASSERT( (psGroup->type == GT_NORMAL) && (psGroup->psCommander == NULL),
				"grpJoin: Cannot have two command droids in a group" );
			psGroup->type = GT_COMMAND;
			psGroup->psCommander = psDroid;
		}
		else
		{
			psDroid->psGrpNext = psGroup->psList;
			psGroup->psList = psDroid;
		}
	}
}


// add a droid to a group at the end of the list
void grpJoinEnd(DROID_GROUP *psGroup, DROID *psDroid)
{
	DROID		*psPrev, *psCurr;

	psGroup->refCount += 1;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"grpJoin: invalid group pointer" );

	// if psDroid == NULL just increase the refcount don't add anything to the list
	if (psDroid != NULL)
	{
		if (psGroup->psList && psDroid->player != psGroup->psList->player)
		{
			ASSERT( FALSE,"grpJoin: Cannot have more than one players droids in a group" );
			return;
		}

		if (psDroid->psGroup != NULL)
		{
			grpLeave(psDroid->psGroup, psDroid);
		}

		psDroid->psGroup = psGroup;

		if (psDroid->droidType == DROID_COMMAND)
		{
			ASSERT( (psGroup->type == GT_NORMAL) && (psGroup->psCommander == NULL),
				"grpJoin: Cannot have two command droids in a group" );
			psGroup->type = GT_COMMAND;
			psGroup->psCommander = psDroid;
		}
		else
		{
			// add the droid to the end of the list
			psPrev = NULL;
			psDroid->psGrpNext = NULL;
			for(psCurr = psGroup->psList; psCurr; psCurr=psCurr->psGrpNext)
			{
				psPrev = psCurr;
			}
			if (psPrev != NULL)
			{
				psPrev->psGrpNext = psDroid;
			}
			else
			{
				psGroup->psList = psDroid;
			}
		}
	}
}


// remove a droid from a group
void grpLeave(DROID_GROUP *psGroup, DROID *psDroid)
{
	DROID	*psPrev, *psCurr;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"grpLeave: invalid group pointer" );

	if (   (psDroid != NULL )
		&& (psDroid->psGroup != psGroup) )
	{
		ASSERT( FALSE, "grpLeave: droid group does not match" );
		return;
	}


	psGroup->refCount -= 1;

	// if psDroid == NULL just decrease the refcount don't remove anything from the list
	if (psDroid != NULL &&
		(psDroid->droidType != DROID_COMMAND ||
		 psGroup->type != GT_COMMAND))
	{
		psPrev = NULL;
		for(psCurr = psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
		{
			if (psCurr == psDroid)
			{
				break;
			}
			psPrev = psCurr;
		}
		ASSERT( psCurr != NULL, "grpLeave: droid not found" );
		if (psCurr != NULL)
		{
			if (psPrev)
			{
				psPrev->psGrpNext = psCurr->psGrpNext;
			}
			else
			{
				psGroup->psList = psGroup->psList->psGrpNext;
			}
		}
	}

	if (psDroid)
	{
		psDroid->psGroup = NULL;
		psDroid->psGrpNext = NULL;

		if ((psDroid->droidType == DROID_COMMAND) &&
			(psGroup->type == GT_COMMAND))
		{
			psGroup->type = GT_NORMAL;
			psGroup->psCommander = NULL;
		}
		else if ((psDroid->droidType == DROID_TRANSPORTER) &&
				 (psGroup->type == GT_TRANSPORTER))
		{
			psGroup->type = GT_NORMAL;
		}
	}

	// free the group structure if necessary
	if (psGroup->refCount <= 0)
	{
		HEAP_FREE(psGrpHeap, psGroup);
	}
}

// count the members of a group
SDWORD grpNumMembers(DROID_GROUP *psGroup)
{
	DROID	*psCurr;
	SDWORD	num;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"grpNumMembers: invalid droid group" );

	num = 0;
	for(psCurr = psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
	{
		num += 1;
	}

	return num;
}


// remove all droids from a group
void grpReset(DROID_GROUP *psGroup)
{
	DROID	*psCurr, *psNext;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"grpReset: invalid droid group" );

	for(psCurr = psGroup->psList; psCurr; psCurr = psNext)
	{
		psNext = psCurr->psGrpNext;
		grpLeave(psGroup, psCurr);
	}
}

/* Give a group an order */
//void orderGroupBase(DROID_GROUP *psGroup, DROID_ORDER_DATA *psData)
//{
//	DROID *psCurr;
//	BOOL usedgrouporder=FALSE;

//	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
//		"orderGroupBase: invalid droid group" );
//
//#ifndef PSX
//	if (bMultiPlayer && SendGroupOrder(	psGroup, psData->x,	psData->y,	psData->psObj) )
//	{	// turn off multiplay messages,since we've send a group one instead.
//		bMultiPlayer =FALSE;
//		usedgrouporder = TRUE;
//	}
//#endif
//
//	for (psCurr = psGroup->psList; psCurr; psCurr=psCurr->psGrpNext)
//	{
//		orderDroidBase(psCurr, psData);
//	}

//#ifndef PSX
//	if( usedgrouporder)
//	{
//		bMultiPlayer = TRUE;
//	}
//#endif
//}

/* Give a group an order */
void orderGroup(DROID_GROUP *psGroup, DROID_ORDER order)
{
	DROID *psCurr;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"orderGroup: invalid droid group" );

	for (psCurr = psGroup->psList; psCurr; psCurr=psCurr->psGrpNext)
	{
		orderDroid(psCurr, order);
	}
}

/* Give a group of droids an order */
void orderGroupLoc(DROID_GROUP *psGroup, DROID_ORDER order, UDWORD x, UDWORD y)
{
	DROID	*psCurr;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"orderGroupLoc: invalid droid group" );

	if(bMultiPlayer)
	{
		SendGroupOrderGroup(psGroup,order,x,y,NULL);
		bMultiPlayer = FALSE;

		for(psCurr=psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
		{
			orderDroidLoc(psCurr, order, x,y);
		}

		bMultiPlayer = TRUE;
	}
	else
	{
		for(psCurr=psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
		{
			orderDroidLoc(psCurr, order, x,y);
		}
	}
}


/* Give a group of droids an order */
void orderGroupObj(DROID_GROUP *psGroup, DROID_ORDER order, BASE_OBJECT *psObj)
{
	DROID	*psCurr;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"orderGroupObj: invalid droid group" );

	if(bMultiPlayer)
	{
		SendGroupOrderGroup(psGroup,order,0,0,psObj);
		bMultiPlayer = FALSE;

		for(psCurr = psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
		{
			orderDroidObj(psCurr, order, psObj);
		}

		bMultiPlayer = TRUE;
	}
	else
	{
		for(psCurr = psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
		{
			orderDroidObj(psCurr, order, psObj);
		}
	}
}

/* set the secondary state for a group of droids */
void grpSetSecondary(DROID_GROUP *psGroup, SECONDARY_ORDER sec, SECONDARY_STATE state)
{
	DROID	*psCurr;

	ASSERT( PTRVALID(psGroup, sizeof(DROID_GROUP)),
		"grpSetSecondary: invalid droid group" );

	for(psCurr = psGroup->psList; psCurr; psCurr = psCurr->psGrpNext)
	{
		secondarySetState(psCurr, sec, state);
	}
}
