#include "munga.h"
#pragma hdrstop

#include "dropzone.h"
#include "random.h"
#include "app.h"
#include "hostmgr.h"
#include "nttmgr.h"
#include "notation.h"
#include "namelist.h"
#include "player.h"

#define DOWN_TIME 5.0f

//#############################################################################
//###############################    DropZone    #################################
//#############################################################################

//#############################################################################
// Shared Data Support
//
Derivation* DropZone::GetClassDerivations()
{
	static Derivation classDerivations(Entity::GetClassDerivations(),"DropZone");
	return &classDerivations;
}

DropZone::SharedData
	DropZone::DefaultData(
		DropZone::GetClassDerivations(),
		DropZone::GetMessageHandlers(),
		DropZone::GetAttributeIndex(),
		DropZone::StateCount,
		(Entity::MakeHandler)DropZone::Make
	);

//#############################################################################
// Message Support
//
const Receiver::HandlerEntry
	DropZone::MessageHandlerEntries[]=
{
	MESSAGE_ENTRY(DropZone, AssignDropZone)
};

Receiver::MessageHandlerSet& DropZone::GetMessageHandlers()
{
	static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(DropZone::MessageHandlerEntries), DropZone::MessageHandlerEntries, Entity::GetMessageHandlers());
	return messageHandlers;
}

//#############################################################################
// Construction and Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DropZone::DropZone(
	DropZone::MakeMessage *creation_message,
	DropZone::SharedData &virtual_data
):
	Entity(creation_message, virtual_data)
{
	Check_Pointer(this);
	Check_Pointer(creation_message);

	SetValidFlag();
	dropZoneCount = 0;
	dropZones = NULL;

	//
	//--------------------------------------------------------------
	// Name the drop zone, and enter the zone in the drop zone group
	//--------------------------------------------------------------
	//
	Str_Copy(dropZoneName, creation_message->dropZoneName, sizeof(dropZoneName));
	EntityManager *entity_manager = application->GetEntityManager();
	Check(entity_manager);
	EntityGroup* drop_zones = entity_manager->UseGroup("DropZones");
	Check(drop_zones);
	drop_zones->Add(this);

	//
	//-----------------------------------------------------------------------
	// Now, interpret the data to create the appropriate number of drop zones
	//-----------------------------------------------------------------------
	//
	MemoryStream
		stream(
			creation_message,
			creation_message->messageLength,
			sizeof(*creation_message)
		);
	dropZoneCount = creation_message->dropZoneCount;
	Verify(dropZoneCount > 0);
	dropZones = new Origin[dropZoneCount];
	Register_Pointer(dropZones);
	lastUsageTime = new Time[dropZoneCount];
	Register_Pointer(lastUsageTime);
	lastUsedBy = new EntityID[dropZoneCount];
	Register_Pointer(lastUsedBy);
	lastDeathCount = new int[dropZoneCount];
	Register_Pointer(lastDeathCount);

	for (int i=0; i<dropZoneCount; ++i)
	{
		dropZones[i] = *(Origin*)stream.GetPointer();
		lastUsageTime[i] = Time::Null;
		lastUsedBy[i] = EntityID::Null;
		lastDeathCount[i] = -3;
		stream.AdvancePointer(sizeof(Origin));
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DropZone*
	DropZone::Make(DropZone::MakeMessage *creation_message)
{
	return new DropZone(creation_message);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DropZone::~DropZone()
{
	if (dropZones)
	{
		Unregister_Pointer(dropZones);
		delete[] dropZones;
		Unregister_Pointer(lastUsageTime);
		delete[] lastUsageTime;
		Unregister_Pointer(lastUsedBy);
		delete[] lastUsedBy;
		Unregister_Pointer(lastDeathCount);
		delete[] lastDeathCount;
	}
}

//#############################################################################
// Dropzone assignment
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	DropZone::IsAvailable(int index)
{
	Check(this);
	Verify(index >= 0 && index < dropZoneCount);
	Check(application);

	return
		!lastUsageTime[index].ticks
		 || Now() - lastUsageTime[index] > DOWN_TIME
		 	&& application->GetApplicationState() == Application::RunningMission;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	DropZone::AssignDropZoneMessageHandler(AssignDropZoneMessage* message)
{
	Check(this);
	Check_Pointer(message);

	Check(application);
	HostManager *host = application->GetHostManager();
	Check(host);
	Entity *entity = host->GetEntityPointer(message->requestingEntity);
	Check(entity);

	//
	//---------------------------------------------------------------------
	// If we have allocated a dropzone within the last ten seconds for this
	// player, resend the data
	//---------------------------------------------------------------------
	//
	Origin *drop_zone = NULL;
	int
		lowest, highest, remaining, i;
	for (i=0; i<dropZoneCount; ++i)
	{
		if (
			!IsAvailable(i)
			 && lastUsedBy[i] == message->requestingEntity
			 && lastDeathCount[i] == message->deathCount
		)
		{
			lastUsageTime[i] = Now();
			drop_zone = &dropZones[i];
			goto Found_One;
		}
	}

	//
	//------------------------------------------------------------------------
	// Figure out how many drop zones are available.  If none are, repost this
	// message to ourself
	//------------------------------------------------------------------------
	//
	lowest = dropZoneCount;
	highest = -1;
	remaining = 0;
	for (i=0; i<dropZoneCount; ++i)
	{
		if (IsAvailable(i))
		{
			++remaining;
			if (i < lowest)
			{
				lowest = i;
			}
			if (i > highest)
			{
				highest = i;
			}
		}
	}

	//
	//-----------------------------------------------------
	// Now, randomly look about until we find one available
	//-----------------------------------------------------
	//
	highest = highest - lowest + 1;
	while (remaining)
	{
		i = lowest + Random(highest);
		Verify(i < dropZoneCount && i >= 0);
		if (IsAvailable(i))
		{
			//
			//-------------------------------------------------------------------
			// Check to see if vehicle attached to a player is within 2 meters of
			// this drop zone.  If so, mark it unavailable for 3 seconds
			//-------------------------------------------------------------------
			//
			EntityManager *entity_mgr = application->GetEntityManager();
			Check(entity_mgr);
			EntityGroup *player_group = entity_mgr->FindGroup("Players");
			if (
				player_group
				 && application->GetApplicationState()
				 		== Application::RunningMission
			)
			{
				Player *player;
				ChainIteratorOf<Node*> iterator(player_group->groupMembers);
				while ((player = (Player*)iterator.ReadAndNext()) != NULL)
				{
					Check(player);
					Entity *vehicle = player->GetPlayerVehicle();
					if (vehicle && player != entity)
					{
						Check(vehicle);
						Vector3D distance;
						distance.Subtract(
							vehicle->localOrigin.linearPosition,
							dropZones[i].linearPosition
						);
						if (distance.LengthSquared() <= 4.0f)
						{
							lastUsedBy[i] = NULL;
							lastUsageTime[i] = Now();
							lastUsageTime[i] -= DOWN_TIME - 1.0f;
							--remaining;
							if (i == lowest)
							{
								++lowest;
								--highest;
							}
							else if (i == lowest + highest - 1)
							{
								--highest;
							}
							break;
						}
					}
				}

				//
				//----------------------------------------------------------
				// If someone was in this spot, keep looking for another one
				//----------------------------------------------------------
				//
				if (player)
				{
					continue;
				}
			}

			lastUsageTime[i] = Now();
			drop_zone = &dropZones[i];
			lastUsedBy[i] = message->requestingEntity;
			lastDeathCount[i] = message->deathCount;
			remaining = 0;
		}
	}


	//
	//----------------------------------------------------------------
	// If no drop_zone could be found, repost the message to ourselves
	//----------------------------------------------------------------
	//
	if (!drop_zone)
	{
		Time when=Now();
		when += 0.1f;
		application->Post(MaxEventPriority, this, message, when);
		return;
	}

	//
	//--------------------------------------------------------------------
	// Send a message back to the requesting object what sent this message
	//--------------------------------------------------------------------
	//
Found_One:
	ReplyMessage
		reply(
			message->replyMessageID,
			sizeof(ReplyMessage),
			*drop_zone,
			message->deathCount
		);
	entity->Dispatch(&reply);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	DropZone::CreateMakeMessage(
		MakeMessage *creation_message,
		const char* model_name,
		const char* resource_name,
		NotationFile *map_file
	)
{
	Check(creation_message);
	Check(map_file);
	Check_Pointer(model_name);
	Check_Pointer(resource_name);

	if (!Entity::CreateMakeMessage(creation_message, map_file, NULL))
	{
		return False;
	}

	creation_message->classToCreate = RegisteredClass::DropZoneClassID;
	creation_message->instanceFlags = MapFlag|TrappedFlag;

	//
	//-----------------------------------------------
	// Count up the number of drop zones in our model
	//-----------------------------------------------
	//
	Enumeration save_modes = map_file->GetModes();
	map_file->SetMode(NotationFile::CleanListMode);

	NameList *zone_list =
		map_file->MakeEntryList(model_name, "dropzone");
	Register_Object(zone_list);
	creation_message->dropZoneCount = zone_list->EntryCount();
	creation_message->messageLength =
		sizeof(*creation_message)
		 + creation_message->dropZoneCount*sizeof(Origin);
	if (!creation_message->dropZoneCount)
	{
		std::cout << "Error: " << model_name << " hasn't specified any drop zones!\n";
Dump_And_Die:
		Unregister_Object(zone_list);
		delete zone_list;
		map_file->PutModes(save_modes);
		return False;
	}

	//
	//---------------------------------------------
	// Allocate a buffer to hold the drop zone data
	//---------------------------------------------
	//
	MemoryStream
		stream(
			creation_message,
			creation_message->messageLength,
			sizeof(*creation_message)
		);

	//
	//----------------------------
	// zero out the drop zone name
	//----------------------------
	//
	for (int ii=0; ii<sizeof(creation_message->dropZoneName); ii++)
	{
		creation_message->dropZoneName[ii] = '\0';
	}

	Str_Copy(
		creation_message->dropZoneName,
		resource_name,
		sizeof(creation_message->dropZoneName)
	);

	//
	//-----------------
	// Read in the data
	//-----------------
	//
	NameList::Entry *drop_zone = zone_list->GetFirstEntry();
	while (drop_zone)
	{
		Point3D translation;
		EulerAngles tmp_rotation;

		const char* zone_data = drop_zone->GetChar();
		sscanf(
			zone_data,
			"%f %f %f %f %f %f",
			&translation.x,
			&translation.y,
			&translation.z,
			&tmp_rotation.pitch,
			&tmp_rotation.yaw,
			&tmp_rotation.roll
		);

		((Origin*)stream.GetPointer())->linearPosition = translation;
		((Origin*)stream.GetPointer())->angularPosition = tmp_rotation;
		stream.AdvancePointer(sizeof(Origin));
		drop_zone = drop_zone->GetNextEntry();
	}

	Unregister_Object(zone_list);
	delete zone_list;
	map_file->PutModes(save_modes);

	return True;
}

Logical
	DropZone::TestInstance() const
{
	return IsDerivedFrom(*GetClassDerivations());
}
