//===========================================================================//
// File:	door.cc                                                             //
// Project: Red Planet                      						   				  //
// Contents: Door subsystem for managing door operation                      //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
//                                                                           //
//---------------------------------------------------------------------------//
// Copyright (C) 1995, Virtual World Entertainment, Inc. All Rights reserved //
//                   	PROPRIETARY AND CONFIDENTIAL                         //
//===========================================================================//

#include <munga.hpp>
#pragma hdrstop

#if !defined(DOOR_HPP)
#	include <door.hpp>
#endif

#if !defined(FILEUTIL_HPP)
#	include <fileutil.hpp>
#endif

#if !defined(BOXSOLID_HPP)
#	include <boxsolid.hpp>
#endif

#if !defined(DOORFRAM_HPP)
#	include <doorfram.hpp>
#endif

#if !defined(APP_HPP)
	#include <app.hpp>
#endif

#if !defined(NOTATION_HPP)
	#include <notation.hpp>
#endif

//#############################################################################
// Shared Data Support
//
Door::SharedData
	Door::DefaultData(
		Door::ClassDerivations,
		Door::MessageHandlers,
		Door::AttributeIndex,
		Door::StateCount
	);

Derivation
	Door::ClassDerivations(
		Subsystem::ClassDerivations,
		"Door"
		);


//#############################################################################
// Messaging Support
//


//#############################################################################
// Attribute Support
//

const Door::IndexEntry
	Door::AttributePointers[]=
{
	{
		Door::CurrentPositionAttributeID,
		"CurrentPosition",
		(Simulation::AttributePointer)&Door::currentPosition
	},
	{
		Door::VideoResourceAttributeID,
		"VideoResource",
		(Simulation::AttributePointer)&Door::videoResource
	},
	{
		Door::PercentOpenAttributeID,
		"PercentOpen",
		(Simulation::AttributePointer)&Door::percentOpen
	},
	{
		Door::CurrentVelocityAttributeID,
		"CurrentVelocity",
		(Simulation::AttributePointer)&Door::currentVelocity
	}
};

Door::AttributeIndexSet
	Door::AttributeIndex(
		ELEMENTS(Door::AttributePointers),
		Door::AttributePointers,
		Subsystem::AttributeIndex
	);

//#############################################################################
// Model Support
//
void
	Door::ReadUpdateRecord(Simulation::UpdateRecord *message)
{
	Check(this);
	Check_Pointer(message);
	Subsystem::ReadUpdateRecord(message);
	UpdateRecord* record = (UpdateRecord*) message;

	percentOpen = record->percentOpen;
	switch (GetSimulationState())
	{
	case Opening:
	case Closing:
		phaseTimeRemaining = travelTime;
		break;
	case Opened:
	case Closed:
		phaseTimeRemaining = deadTime;
		break;
	}
//	DEBUG_STREAM << GetEntity()->GetEntityID() << " door updated to state "
//	 << GetSimulationState() << " @ "
//	 << application->GetSecondsRemainingInGame() << endl;
	MoveCollisionVolume(percentOpen);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Door::WriteUpdateRecord(Simulation::UpdateRecord *record, int update_model)
{
	Check(this);
	Check_Pointer(record);

	Subsystem::WriteUpdateRecord(record, update_model);

	UpdateRecord *update = (UpdateRecord*)record;
	update->percentOpen = percentOpen;
	update->recordLength = sizeof(*update);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Door::SlideDoor(Scalar time_slice)
{
	Check(this);

	//
	//------------------------------------------------------------
	// Advance the clock, then branch based upon our current state
	//------------------------------------------------------------
	//
	int new_state;
	if (time_slice > 1.0f)
	{
		Check_Fpu();
		return;
	}
	phaseTimeRemaining -= time_slice;
	Scalar percent_open;
	switch (GetSimulationState())
	{

	//
	//------------------------------------------------------------------------
	// If the door is not done opening, set its new position, otherwise branch
	// to the opened state
	//------------------------------------------------------------------------
	//
	case Opening:
Door_Opening:
		new_state = Opening;
		if (phaseTimeRemaining > 0.0f)
		{
			percent_open = 1.0f - phaseTimeRemaining/travelTime;
		}
		else
		{
			phaseTimeRemaining += deadTime;
//			DEBUG_STREAM << GetEntity()->GetEntityID() << " door opened @ "
//			 << application->GetSecondsRemainingInGame() << endl;
			goto Door_Opened;
		}
		currentVelocity.Subtract(
			worldExtent,
			GetEntity()->localOrigin.linearPosition
		);
		currentVelocity /= travelTime;
		Check_Fpu();
		break;

	//
	//-------------------------------------------------------------
	// If the door is ready to start closing, jump to closing state
	//-------------------------------------------------------------
	//
	case Opened:
Door_Opened:
		new_state = Opened;
		if (phaseTimeRemaining <= 0.0f)
		{
			phaseTimeRemaining += travelTime;
//			DEBUG_STREAM << GetEntity()->GetEntityID() << " door closing @ "
//			 << application->GetSecondsRemainingInGame() << endl;
			goto Door_Closing;
		}
		percent_open = 1.0f;
		currentVelocity = Vector3D::Identity;
		Check_Fpu();
		break;

	//
	//------------------------------------------------------------------------
	// If the door is not done closing, set its new position, otherwise branch
	// to the closed state
	//------------------------------------------------------------------------
	//
	case DefaultState:
		phaseTimeRemaining = travelTime;
//		DEBUG_STREAM << GetEntity()->GetEntityID() << " door default @ "
//		 << application->GetSecondsRemainingInGame() << endl;
	case Closing:
Door_Closing:
		new_state = Closing;
		if (phaseTimeRemaining > 0.0f)
		{
			percent_open = phaseTimeRemaining/travelTime;
		}
		else
		{
			phaseTimeRemaining += deadTime;
//			DEBUG_STREAM << GetEntity()->GetEntityID() << " door closed @ "
//			 << application->GetSecondsRemainingInGame() << endl;
			goto Door_Closed;
		}
		currentVelocity.Subtract(
			GetEntity()->localOrigin.linearPosition,
			worldExtent
		);
		currentVelocity /= travelTime;
		Check_Fpu();
		break;

	//
	//-------------------------------------------------------------
	// If the door is ready to start opening, jump to opening state
	//-------------------------------------------------------------
	//
	case Closed:
Door_Closed:
		new_state = Closed;
		if (phaseTimeRemaining <= 0.0f)
		{
			phaseTimeRemaining += travelTime;
//			DEBUG_STREAM << GetEntity()->GetEntityID() << " door opening @ "
//			 << application->GetSecondsRemainingInGame() << endl;
			goto Door_Opening;
		}
		percent_open = 0.0f;
		currentVelocity = Vector3D::Identity;
		Check_Fpu();
		break;

	}

	//
	//-------------------------------------------------
	// Move the collision volumes and set the new state
	//-------------------------------------------------
	//
	MoveCollisionVolume(percent_open);
	SetSimulationState(new_state);
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Door::MoveCollisionVolume(Scalar percent_open)
{
	Check(this);

	//
	//---------------------------------------------------------
	// If the door hasn't moved, don't bother updating anything
	//---------------------------------------------------------
	//
	Entity *entity = GetEntity();
	Check(entity);

	if (
		GetSimulationState() != simulationState.GetOldState()
		 && entity->GetInstance() == Entity::MasterInstance
		 && entity->IsDynamic()
	)
	{
		ForceUpdate();
	}
	if (percent_open == percentOpen)
	{
		return;
	}

	//
	//-----------------------------------
	// Otherwise, lerp the local position
	//-----------------------------------
	//
	percentOpen = percent_open;
	currentPosition.Lerp(Point3D::Identity, localExtent, percentOpen);

	//
	//-----------------------------------------------------------
	// Now, lerp the world position and set the collision volumes
	//-----------------------------------------------------------
	//
	Point3D world;
	world.Lerp(
		GetEntity()->localOrigin.linearPosition,
		worldExtent,
		percentOpen
	);

	BoxedSolid* temp = collisionTemplates;
	BoxedSolid* vol = collisionVolumes;
	while (temp)
	{
		Check(temp);
		Check(vol);

		vol->minX = temp->minX + world.x;
		vol->maxX = temp->maxX + world.x;
		vol->minY = temp->minY + world.y;
		vol->maxY = temp->maxY + world.y;
		vol->minZ = temp->minZ + world.z;
		vol->maxZ = temp->maxZ + world.z;

		vol = vol->GetNextSolid();
		temp = temp->GetNextSolid();
	}
	Verify(!vol);
	Check_Fpu();
}

//#############################################################################
// Constructer/Destructor Support
//

Door::Door(
	DoorFrame *entity,
	int subsystem_ID,
	SubsystemResource	*subsystem_resource
):
	Subsystem(entity, subsystem_ID, subsystem_resource, DefaultData)
{
	Check_Pointer(this);
	Check(entity);
	Check_Pointer(subsystem_resource);

	//
	// GetStream data NOTE::This must be in the same order as below!!!
	//
	localExtent = subsystem_resource->motionExtent;
	travelTime = subsystem_resource->travelTime;
	deadTime = subsystem_resource->deadTime;

	//
	// Initialize variables
	//
	phaseTimeRemaining = 0.0f;
	currentPosition = Point3D::Identity;

	SetPerformance(&Door::SlideDoor);
	SetSimulationState(DefaultState);

	collisionVolumeCount = 0;
	collisionTemplates = NULL;
	collisionVolumes = NULL;
	percentOpen = -1.0f;

	//
	//-------------------------------------
	// Establish the worldspace coordinates
	//-------------------------------------
	//
	worldExtent.Multiply(localExtent, entity->localToWorld);
	Origin local_origin = entity->localOrigin;
	local_origin.linearPosition = Point3D::Identity;

	//
	//------------------------------
	// Read in the collision volumes
	//------------------------------
	//
	Check(application);
	ResourceFile *res_file = application->GetResourceFile();
	Check(res_file);
	ResourceDescription *res =
		res_file->FindResourceDescription(subsystem_resource->collisionID);
	Check(res);
	res->Lock();
	BoxedSolidResource* box =
		(BoxedSolidResource*)res->resourceAddress;
	Check_Pointer(box);
	collisionVolumeCount = res->resourceSize / sizeof(BoxedSolidResource);

	for (int i=0; i<collisionVolumeCount; ++i)
	{
		BoxedSolidResource world_box,local_box;
		world_box.Instance(*box,entity->localOrigin);
		local_box.Instance(*box,local_origin);

		collisionTemplates =
			BoxedSolid::MakeBoxedSolid(&local_box, this, collisionTemplates);
		Register_Object(collisionTemplates);

		collisionVolumes =
			BoxedSolid::MakeBoxedSolid(&world_box, this, collisionVolumes);
		Register_Object(collisionVolumes);

		++box;
	}
	res->Unlock();

	MoveCollisionVolume(0.0f);
	Check(this);
}

Door::~Door()
{
	BoxedSolid *box = collisionTemplates;
	while (box)
	{
		BoxedSolid *next_box = box->GetNextSolid();
		Unregister_Object(box);
		delete box;
		box = next_box;
	}
	box = collisionVolumes;
	while (box)
	{
		BoxedSolid *next_box = box->GetNextSolid();
		Unregister_Object(box);
		delete box;
		box = next_box;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//	CreateStreamedSubsystem
//
Logical
	Door::CreateStreamedSubsystem(
		NotationFile					*model_file,
      const char						*model_name,
		const char*						subsystem_name,
		SubsystemResource				*subsystem_resource,
		NotationFile					*subsystem_file,
		const ResourceDirectories 	*directories,
		ResourceFile					*res_file
	)
{
	if (
		!Subsystem::CreateStreamedSubsystem(
			model_file,
         model_name,
			subsystem_name,
			subsystem_resource,
			subsystem_file,
			directories
		)
	)
	{
		return False;
	}

	subsystem_resource->subsystemModelSize = sizeof(*subsystem_resource);
	subsystem_resource->classID = RegisteredClass::DoorClassID;

	//
	// Get the motion Extent
	//
	const char *extent_data;
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"MotionExtent",
			&extent_data
		)
	)
	{
		cerr << subsystem_name << " missing MotionExtent!!\n";
		return -1;
	}
	sscanf(
		extent_data,
		"%f %f %f",
		&subsystem_resource->motionExtent.x,
		&subsystem_resource->motionExtent.y,
		&subsystem_resource->motionExtent.z
	);

	//
	// Get the travelTime
	//

	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"TravelTime",
			&subsystem_resource->travelTime
		)
	)
	{
		cerr << subsystem_name << " missing TravelTime!\n";
		return -1;
	}

	//
	//	Read in the Deadtime
	//
	if(
		!subsystem_file->GetEntry(
			subsystem_name,
			"DeadTime",
			&subsystem_resource->deadTime
		)
	)
	{
		cerr << subsystem_name << " missing DeadTime!\n";
		return False;
	}

	const char* collision_file;
	if (
		!subsystem_file->GetEntry(
			subsystem_name,
			"Collision",
			&collision_file
		)
	)
	{
		cerr << subsystem_name << " missing Collision!\n";
		return False;
	}
	subsystem_resource->collisionID =
		BoxedSolidResource::CreateBoxedSolidStream(
			collision_file,
			res_file,
			directories
		);

	return True;
}

Logical
	Door::TestInstance() const
{
	return IsDerivedFrom(ClassDerivations);
}

