#include "munga.h"
#pragma hdrstop

#include "cammgr.h"
#include "mission.h"
#include "app.h"
#include "notation.h"
#include "namelist.h"

//##########################################################################
//#####################    CameraInstanceManager     #######################
//##########################################################################

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstanceManager::CameraInstanceManager() :
	cameraList(NULL)
{
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Initialize local member data
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	cameraIterator = new SChainIteratorOf<CameraInstance*>(cameraList);
	Register_Object(cameraIterator);
	InitializeCameraInstances();
	Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstanceManager::~CameraInstanceManager()
{
	DeleteCameraList();
	Unregister_Object(cameraIterator);
	delete cameraIterator;
	cameraIterator = NULL;
	Check_Fpu();
}

//##########################################################################
// CameraInstance Creation and Deletion Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	CameraInstanceManager::FindUniqueCameraID()
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Finds a cameraID that has been freed up or the next sequential number
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
	SChainIteratorOf<CameraInstance*> iterator(cameraList);
	CameraInstance *camera_instance;
	int  camera_ID = 0;
	Logical ID_exists = False;

	while(1)
	{
		//
		//~~~~~~~~~~~~~~~~~~~~
		// Reset the iterator
		//~~~~~~~~~~~~~~~~~~~~
		//
		iterator.First();
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Iterate through the list of all cameraInstances for every ID Chosen
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		while ( (camera_instance = iterator.ReadAndNext()) != NULL)
		{
			if(camera_ID == camera_instance->GetCameraID())
			{
				ID_exists = True;
				break;
			}
		}
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// If this cameraID not already used return the ID
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		if(!ID_exists)
		{
			break;
		}
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Otherwise increment the ID and reset the boolean
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		++camera_ID;
		ID_exists = False;
	}

	Check_Fpu();
	return camera_ID;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstanceManager::SetCamera(
		const Origin &new_origin,
		CameraInstance *camera_instance
	)
{

	if(!camera_instance)
	{
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// No camera passed as an argument use cameraIterator
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		camera_instance = cameraIterator->GetCurrent();
		if(!camera_instance)
		{
			CreateCameraInstance(new_origin);
		}
		else
		{
			camera_instance->SetLocalOrigin(new_origin);
		}
	}
	else
	{
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Camera passed in set its origin
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		camera_instance->SetLocalOrigin(new_origin);
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
	CameraInstanceManager::CreateCameraInstance(const Origin &new_origin)
{
	Check(this);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~
	// Find a Unique CameraID
	//~~~~~~~~~~~~~~~~~~~~~~~~
	//
	int camera_ID;
	camera_ID = FindUniqueCameraID();

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create the new cameraInstance
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	CameraInstance *camera_instance =
		new CameraInstance(
			camera_ID,
			new_origin
		);
	Register_Object(camera_instance);
	AddCameraInstance(camera_instance);

	Check_Fpu();
	return camera_instance;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
	CameraInstanceManager::AddCameraInstance(CameraInstance *camera_instance)
{
	Check(this);
	Check(cameraIterator);
	Check(&cameraList);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Insert the new Camera Instance into the cameraList
	// & Point the cameraIterator to it
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	if (!cameraIterator->GetCurrent())
	{
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// If nothing in the cameraList create the first element
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		cameraList.Add(camera_instance);
		cameraIterator->First();
		Tell("Created the First Camera Instance \n");
	}
	else
	{
		cameraList.Add(camera_instance);
		cameraIterator->Next();
		Tell("Created a Camera Instance \n");
	}
	Check_Fpu();
	return camera_instance;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
	CameraInstanceManager::DeleteCameraInstance()
{
	CameraInstance *camera_instance = cameraIterator->GetCurrent();
	if(!camera_instance)
	{
		return NULL;
	}

	cameraIterator->Remove();
	if(!cameraIterator->GetCurrent())
	{
		cameraIterator->First();
	}
	Unregister_Object(camera_instance);
	delete camera_instance;
	return GetCurrent();
}

//##########################################################################
// CameraList Manipulator Functions
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstanceManager::DeleteCameraList()
{
	while (cameraIterator->GetCurrent() != NULL)
	{
		DeleteCameraInstance();
	}
}

//##########################################################################
// CameraList Iterator Manipulator Functions
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
	CameraInstanceManager::IncrementIterator()
{
	Check(this);
	if (cameraIterator->GetCurrent())
	{
		Check(cameraIterator);
		cameraIterator->ReadAndNext();
		if (!cameraIterator->GetCurrent())
		{
			cameraIterator->First();
		}
	}
	Check_Fpu();
	return GetCurrent();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
	CameraInstanceManager::DecrementIterator()
{
	Check(this);
	if (cameraIterator->GetCurrent())
	{
		Check(cameraIterator);
		cameraIterator->ReadAndPrevious();
		if (!cameraIterator->GetCurrent())
		{
			cameraIterator->Last();
		}
	}
	Check_Fpu();
	return GetCurrent();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
	CameraInstanceManager::AdvanceIterator(const CameraInstance &camera_instance)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Search for the camera_instance in the cameraList
// Iterator points to camera_instance upon return
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
	Check(&camera_instance);
	Check(&cameraList);
	Check(cameraIterator);

	CameraInstance *current_instance;
	cameraIterator->First();

	while((current_instance = cameraIterator->GetCurrent()) != NULL)
	{
		if(current_instance == &camera_instance)
		{
			Check_Fpu();
			return current_instance;
		}
		cameraIterator->Next();
	}
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// camera_instance not found return NULL
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Check_Fpu();
	return NULL;
}

//##########################################################################
// CameraList Searching and Query Functions
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance*
	CameraInstanceManager::FindClosestCamera(const Point3D &point_3D)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Use a Simple distance formula to find closest camera
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
	Check(this);
	CameraInstance *closest_camera = NULL;
	Scalar dist_to_goal, min_dist=1e8f;

	SChainIteratorOf<CameraInstance*> iterator(cameraList);
	CameraInstance *camera_instance;
	while ( (camera_instance = iterator.ReadAndNext()) != NULL)
	{
		Check(camera_instance);
		if (camera_instance->CanCameraSee(point_3D))
		{
			Vector3D v;
			v.Subtract(point_3D, camera_instance->GetPosition());
			dist_to_goal = v.LengthSquared();
			Check_Fpu();
			if (dist_to_goal < min_dist)
			{
				min_dist = dist_to_goal;
				closest_camera = camera_instance;
			}
		}
	}
	Check_Fpu();
	return closest_camera;
}

//##########################################################################
// Test Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	CameraInstanceManager::TestInstance() const
{
	return True;
}

//##########################################################################
// Tool Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstanceManager::InitializeCameraInstances()
{
	Check(application);
	ResourceDescription::ResourceID map_ID;
	map_ID = application->GetCurrentMission()->GetMapID();

	ResourceFile *res_file;
	res_file = application->GetResourceFile();
	Check(res_file);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create the camera filename from the map name
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	ResourceDescription	*res = res_file->FindResourceDescription(map_ID);
	Check(res);
	Str_Copy(
		cameraFilename,
		"cameras\\",
		sizeof(cameraFilename)
	);
	//
	//	cam name matches map name
	//
	Str_Cat(
		cameraFilename,
		res->resourceName,
		sizeof(cameraFilename)
	);
	//
	// trunc cam name to DOS filename size
	//
	int name_length = strlen(cameraFilename);
	if(name_length > 17)
	{
		cameraFilename[17] = '\0';
	}

	//
	//	Add the extension
	//
	Str_Cat(
		cameraFilename,
		".cam",
		sizeof(cameraFilename)
	);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~
	// See if the file exists
	//~~~~~~~~~~~~~~~~~~~~~~~~
	//
	std::fstream cam_file;
	//cam_file.open(cameraFilename, std::ios::nocreate | std::ios::in | std::ios::out);
	// following alternate line created by Ryan Bunker on 1/6/07
	cam_file.open(cameraFilename, std::ios.in);
	if(cam_file)		//NOTE: this appears broken (RB 1/6/07)
	{
		cam_file.close();
		ReadNotationFile();
	}
	else
	{
		cam_file.close();
		CreateStreamedCameraInstances(res_file, res->resourceName);
	}
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstanceManager::ReadNotationFile()
{
	Check(this);
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Create a Notation file out of the camera file
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	NotationFile *cam_file = new NotationFile(cameraFilename);
	Register_Object(cam_file);

	NameList*  camera_namelist = cam_file->MakePageList();
	Register_Object(camera_namelist);

	NameList::Entry *camera_entry = camera_namelist->GetFirstEntry();

	char
		camera_page_name[128];

	while (camera_entry)
	{
		Str_Copy(
			camera_page_name,
			camera_entry->GetName(),
			sizeof(camera_page_name)
		);

		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		// Create a new camera Instance for this page
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		CameraInstance *camera_instance;
		const char *camera_data_entry;
		int cam_type;
		if(
			!cam_file->GetEntry(
				camera_page_name,
				"cameraType",
				&camera_data_entry
			)
		)
		{
			cam_type = CameraInstance::DefaultCameraType;
		}
		else
		{
			Check_Pointer(camera_data_entry);
			cam_type = CameraInstance::FindCameraType(camera_data_entry);
		}
		switch (cam_type)
		{
		case CameraInstance::DefaultCameraType:
			camera_instance = new CameraInstance(cam_file, camera_page_name);
			break;
		case CameraInstance::AlwaysSeesCameraType:
			camera_instance =
				new AlwaysSeesCameraInstance(cam_file, camera_page_name);
			break;
		}
		Register_Object(camera_instance);
		AddCameraInstance(camera_instance);
		camera_entry = camera_entry->GetNextEntry();
	}
	Unregister_Object(camera_namelist);
	delete camera_namelist;
	Unregister_Object(cam_file);
	delete cam_file;
	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define PAGE_NAME_SIZE 128

void
	CameraInstanceManager::CreateStreamedCameraInstances(
		ResourceFile *res_file,
		const char *map_name
	)
{
	Check(this);
	Check_Pointer(res_file);
	Check_Pointer(map_name);

	ResourceDescription *res =
		res_file->FindResourceDescription(
			map_name,
			ResourceDescription::CameraStreamResourceType
		);
	if (!res)
	{
		Check_Fpu();
		return;
	}

	res->Lock();

	MemoryStream camera_stream(res->resourceAddress, res->resourceSize);
	int camera_count = *(int*)camera_stream.GetPointer();
	camera_stream.AdvancePointer(sizeof(camera_count));

	//
	//--------------------
	// Read in each camera
	//--------------------
	//
	while (camera_count--)
	{
		Check(&camera_stream);
		CameraInstance::StreamedInstance *model =
			(CameraInstance::StreamedInstance*)camera_stream.GetPointer();
		Check_Pointer(model);
		CameraInstance *camera_instance;
		switch (model->cameraType)
		{
		case CameraInstance::DefaultCameraType:
			camera_instance = new CameraInstance(model);
			break;
		case CameraInstance::AlwaysSeesCameraType:
			camera_instance = new AlwaysSeesCameraInstance(model);
			break;
		}
		Register_Object(camera_instance);
		AddCameraInstance(camera_instance);
		camera_stream.AdvancePointer(model->instanceSize);
	}
	res->Unlock();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstanceManager::WriteNotationFile()
{
	Check(this);
	Check(&cameraList);

	NotationFile *camera_stream = new NotationFile();
	Register_Object(camera_stream);

	if(!camera_stream)
	{
		Fail("Error Opening Camera File\n");
	}
	SChainIteratorOf<CameraInstance*> iterator(cameraList);
	CameraInstance *camera_instance;

	iterator.First();
	while ( (camera_instance = iterator.ReadAndNext()) != NULL)
	{
		camera_instance->WriteNotationPage(camera_stream);
	}
	camera_stream->WriteFile(cameraFilename);
	Unregister_Object(camera_stream);
	delete camera_stream;
	Check_Fpu();
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define PAGE_NAME_SIZE 128
ResourceDescription::ResourceID
	CameraInstanceManager::CreateCameraInstancesStream(
		ResourceFile *resource_file,
		const char *camera_name,
		const ResourceDirectories *directories
	)
{
	Check_Pointer(camera_name);
	Check_Pointer(directories);

	//
	//-------------------
	// Open the .cam file
	//-------------------
	//
	int length = strlen(camera_name) + strlen(directories->mapDirectory)+ 1;

	char *filename = new char[length];
	Register_Pointer(filename);

	Str_Copy(filename, directories->mapDirectory, length);
	strncat(filename, camera_name, strlen(camera_name)-3);
	Str_Cat(filename, "cam", length);

	NotationFile *camera_notation = new NotationFile(filename);
	Register_Object(camera_notation);

	if (camera_notation->PageCount() == 0)
	{
		DEBUG_STREAM << "Missing .cam file" << std::endl << std::flush;
Dump_And_Die:
		Unregister_Object(camera_notation);
		delete camera_notation;
		Unregister_Pointer(filename);
		delete[] filename;
		return ResourceDescription::NullResourceID;
	}

	int camera_count = 0;
	NameList *pagelist = camera_notation->MakePageList();
	Register_Object(pagelist);
	NameList::Entry *entry = pagelist->GetFirstEntry();
	char page_name[PAGE_NAME_SIZE];

	while (entry)
	{
		++camera_count;
		entry = entry->GetNextEntry();
	}

	size_t array_size =
		sizeof(CameraInstance::StreamedInstance) * camera_count * 2;
	long *camera_array = new long[(array_size+3) >> 2];
	Register_Pointer(camera_array);
	MemoryStream camera_stream(camera_array, array_size);

	*(int*)camera_stream.GetPointer() = camera_count;
	camera_stream.AdvancePointer(sizeof(camera_count));

	entry = pagelist->GetFirstEntry();
	while (entry)
	{
		Str_Copy(
			page_name,
			entry->GetName(),
			sizeof(page_name)
		);

		if(strcmp(page_name, "CameraInfo") == 0)
		{
			entry = entry->GetNextEntry();
			continue;
		}

		Check(&camera_stream);
		CameraInstance::StreamedInstance *model =
			(CameraInstance::StreamedInstance*)camera_stream.GetPointer();
		Check_Pointer(model);
		if (
			!CameraInstance::CreateStreamedInstance(
				model,
				camera_notation,
				page_name
			)
		)
		{
			Unregister_Pointer(camera_array);
			delete[] camera_array;
			Unregister_Object(pagelist);
			delete pagelist;
			goto Dump_And_Die;
		}
		camera_stream.AdvancePointer(model->instanceSize);
		entry = entry->GetNextEntry();
	}

	//
	//-------------------------------
	// Write the stream out to disk.
	//

	Str_Copy(filename, camera_name, length);
	filename[strlen(camera_name)-4] = '\0';

	ResourceDescription *new_res =
		resource_file->AddResource(
			filename,
			ResourceDescription::CameraStreamResourceType,
			1,
			ResourceDescription::LoadOnDemandFlag,
			camera_array,
			camera_stream.GetBytesUsed()
		);
	Check(new_res);

	//-----------
	//      Free Mem
	//


	Unregister_Pointer(camera_array);
	delete[] camera_array;
	Unregister_Pointer(filename);
	delete[] filename;
	Unregister_Object(camera_notation);
	delete camera_notation;
	Unregister_Object(pagelist);
	delete pagelist;

	return  new_res->resourceID;
}
