#include "AdeptHeaders.hpp"

#include "VideoHeaders.hpp"
#include "Interface.hpp"
#include "Mission.hpp"
#include <ElementRenderer\CameraElement.hpp>
#include <ElementRenderer\StateChange.hpp>
#include <ElementRenderer\LightElement.hpp>

//
// Shared Data Support
//
Component::ClassData*
	CameraComponent::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//	InitializeClass
//
void
	CameraComponent::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			CameraComponentClassID,
			"Adept::CameraComponent",
			BaseClass::DefaultData,
			0,
			NULL
		);
	Register_Object(DefaultData);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraComponent::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//#############################################################################
// Constructors and destructors
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::CameraElement*
	CameraComponent::AllocateCamera()
{
	gos_PushCurrentHeap(ElementRenderer::g_Heap);
	ElementRenderer::StateChange *states = new ElementRenderer::StateChange;
	Register_Object(states);
	ElementRenderer::CameraElement *camera = new ElementRenderer::CameraElement(states);
	Register_Object(camera);
	gos_PopCurrentHeap();
	return camera;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CameraComponent
//
CameraComponent::CameraComponent(
	ClassData *class_data,
	MemoryStream *stream,
	VideoComponentWeb *owning_web,
	Interface * intface
):
	GroupComponent(
		class_data,
		stream,
		owning_web,
		AllocateCamera()
	)
{
	ElementRenderer::CameraElement *camera = GetElement();
	Check_Object(camera);

	//
	//-----------------------
	// Read the viewing state
	//-----------------------
	//
	ElementRenderer::StateChange *view_states = camera->GetViewingStateChange();
	Check_Object(view_states);
	view_states->Load(stream, ElementRenderer::ReadERFVersion(stream));

	//
	//------------------------
	// Set the clipping planes
	//------------------------
	//
	Scalar angle;
	*stream >> angle;
	Check_Object(Mission::GetInstance());
	const Mission::GameModel *model = Mission::GetInstance()->GetGameModel();
	Check_Object(model);

	//
	//------------------
	// Set the view port
	//------------------
	//
	Scalar x_position;
	Scalar y_position;
	Scalar width;
	Scalar height;
	*stream >> x_position;
	*stream >> y_position;
	*stream >> width;
	*stream >> height;
	SetViewPort(x_position, y_position, width, height);
	SetPerspective(model->m_nearClip, model->m_farClip, angle, (height/width)*0.75f);

	//
	//-------------------
	// Load up the lights
	//-------------------
	//
	ChainIteratorOf<gosFX::Light*> lights(&Mission::GetInstance()->m_staticLights);
	gosFX::Light *light;
	int i=0;
	while ((light = lights.ReadAndNext()) != NULL)
	{
		Verify(i < ELEMENTS(view_states->m_lights));
		view_states->m_lights[i++] = light->m_light;
	}
	view_states->m_lights[i] = NULL;

	//
	//---------------------------------------
	// Set up the sky element if there is one
	//---------------------------------------
	//
#if 0
	const Mission::GameModel *mission_model = Mission::GetInstance()->GetGameModel();
	Check_Object(mission_model);
#endif

	*stream >> m_isMainSceneCamera;
	*stream >> m_drawSky;
	*stream >> m_LODOffset;

	if(m_drawSky)
	{
		camera->DrawSky(true);
		if(Mission::GetInstance()->m_isNightMission)
		{
			if(Mission::GetInstance()->m_nightSky.GetCurrent())
				camera->AdoptSkyElement(Mission::GetInstance()->m_nightSky.GetCurrent());
		}
		else
		{
			if(Mission::GetInstance()->m_sky.GetCurrent())
				camera->AdoptSkyElement(Mission::GetInstance()->m_sky.GetCurrent());
		}
	}
	else
	{
		camera->DrawSky(false);
	}


	//
	//------------------------------------------
	// Attach the camera to the renderer's scene
	//------------------------------------------
	//

	VideoRenderer *renderer =
		Cast_Object(VideoRenderer*, owning_web->GetRenderer());
	if(m_isMainSceneCamera)
	{
		Element *root = renderer->GetSceneRoot();
		Check_Object(root);
		camera->SetScene(root);
		renderer->SetCamera(const_cast<CameraComponent*>(this));
	}
	else
	{
		renderer->AddSecondaryCamera(const_cast<CameraComponent*>(this));
		camera->SetSecondaryCamera();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraComponent*
	CameraComponent::Make(
		MemoryStream *stream,
		VideoComponentWeb *owning_web,
		Replicator *replicator
	)
{
	CameraComponent *camera =
		new
			CameraComponent(
				DefaultData,
				stream,
				owning_web,
				Cast_Object(Interface*, replicator)
			);
	return camera;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraComponent::~CameraComponent()
{
	if (m_isMainSceneCamera && m_drawSky)
	{
		ElementRenderer::CameraElement *camera = GetElement();
		Check_Object(camera);
		camera->DeleteSky();
	}

	Check_Object(VideoRenderer::Instance);
	if (VideoRenderer::Instance->GetSceneCamera() == this)
		VideoRenderer::Instance->SetCamera(NULL);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraComponent::Execute()
{
	//
	//-------------------------------------------------------
	// Get the camera and display, then clear the back buffer
	//-------------------------------------------------------
	//
	ElementRenderer::LODElement::s_Offset -= m_LODOffset;

	Check_Object(this);
	ElementRenderer::CameraElement *camera = GetElement();
	Check_Object(camera);
	camera->DrawScene();

	ElementRenderer::LODElement::s_Offset += m_LODOffset;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraComponent::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraComponent::ComputeEyeLine(
		Line3D *eye_line,
		const Vector2DOf<Scalar> &cursor
	)
{
	Check_Object(this);
	Check_Pointer(eye_line);
	Check_Object(&cursor);
	Verify(
		cursor.x >= 0.0f && cursor.x <= 1.0f
		 && cursor.y >= 0.0f && cursor.y <= 1.0f
	);

	//
	//----------------------------------
	// Figure out the near plane borders
	//----------------------------------
	//
	ElementRenderer::CameraElement *camera = GetElement();
	Check_Object(camera);
	Scalar
		left,
		right,
		up,
		down,
		n,
		f;
	camera->GetNearPlaneBorders(&left, &right, &up, &down, &n, &f);
	Verify(n > SMALL);

	//
	//----------------------------------------------------------------------
	// Figure out the point the near plane where the eye hits, the normalize
	// the direction
	//----------------------------------------------------------------------
	//
	Vector3D eye_to_plane;
	eye_to_plane.x = Lerp(left, right, cursor.x);
	eye_to_plane.y = Lerp(up, down, cursor.y);
	eye_to_plane.z = n;
	UnitVector3D local_d(eye_to_plane);

	//
	//------------------------------------
	// Transform this into the world space
	//------------------------------------
	//
	const LinearMatrix4D &eye_to_world = camera->GetLocalToWorld();
	eye_line->m_origin = eye_to_world;
	eye_line->m_direction.Multiply(local_d, eye_to_world);
	eye_line->m_length = f / local_d.z;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	CameraComponent::ComputeCursor(
		Vector2DOf<Scalar> *cursor,
		const Point3D &location
	)
{
	Check_Object(this);
	Check_Pointer(cursor);
	Check_Object(&location);

	//
	//----------------------------------
	// Figure out the near plane borders
	//----------------------------------
	//
	ElementRenderer::CameraElement *camera = GetElement();
	Check_Object(camera);
	Scalar
		left,
		right,
		up,
		down,
		n,
		f;
	camera->GetNearPlaneBorders(&left, &right, &up, &down, &n, &f);
	Verify(n > SMALL);

	//
	//-------------------------------------------------------------------------
	// Transform the world point into camera space and project it unto the near
	// viewing plane.  If there is nothing to project, just take the points as
	// they are
	//-------------------------------------------------------------------------
	//
	LinearMatrix4D world_to_eye;
	world_to_eye.Invert(camera->GetLocalToWorld());
	Point3D local_point;
	local_point.Multiply(location, world_to_eye);
	if (!Small_Enough(local_point.z))
	{
		Scalar scale = n / local_point.z;
		local_point.x *= scale;
		local_point.y *= scale;
	}

	//
	//---------------------------------------------------------------
	// Now figure out how far they are between the edges of the scene
	//---------------------------------------------------------------
	//
	cursor->x = (local_point.x - left) / (right - left);
	cursor->y = (local_point.y - up) / (down - up);

	//
	//---------------------------------------
	// Return true if the cursor is on screen
	//---------------------------------------
	//
	return
		cursor->x >= 0.0f && cursor->x <= 1.0f
		 && cursor->y >= 0.0f && cursor->y <= 1.0f;
}
