#include "../../Scene/Container/RuContainer_Portal.h"

#include "../../Scene/Base/RuEntityACT.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuContainer_Portal, IRuEntity_Container)
ruHEAP_DEFINE_SUBCLASS(CRuContainer_Portal, "CRuContainer_Portal", "IRuEntity_Container")
ruCLASSGUID_DEFINE(CRuContainer_Portal, 0x0609E0D9, 0x9FD44208, 0x9F2EF97D, 0x40EC2AC7)

// ************************************************************************************************************************************************************

CRuContainer_Portal::CRuContainer_Portal()
{
}

CRuContainer_Portal::~CRuContainer_Portal()
{
	for(INT32 i = 0; i < m_rooms.Count(); ++i)
	{
		ruSAFE_RELEASE(m_rooms[i].m_roomEntity);
		delete[] m_rooms[i].m_portals;
	}
}

void CRuContainer_Portal::SetCamera(CRuCamera *viewCamera)
{
	const CRuVector3 &cameraPosition = viewCamera->GetCameraPosition();

	// Reset status data
	m_positionType = ruPORTAL_ROOMTYPE_INDOOR;
	m_intersectingRooms.Clear();

	// First determine which indoor sectors the camera lies within, and the indoor-outdoor status of the camera
	for(INT32 i = 0; i < m_rooms.Count(); ++i)
	{
		// Reset visible and traversed flags
		m_rooms[i].m_roomVisible = FALSE;
		m_rooms[i].m_roomTraversed = FALSE;

		if(m_rooms[i].m_roomEntity->GetWorldBounds().Contains(cameraPosition))
		{
			// If the containing room is an outdoor room, set the position type to outdoor
			if(m_rooms[i].m_roomType == ruPORTAL_ROOMTYPE_OUTDOOR)
			{
				m_positionType = ruPORTAL_ROOMTYPE_OUTDOOR;
			}
			else
			{
				m_rooms[i].m_roomVisible = TRUE;
				m_intersectingRooms.Add(i);
			}
		}
	}

	// If the camera lies outdoors, add all outdoor sectors to the list of intersecting rooms
	if(m_positionType == ruPORTAL_ROOMTYPE_OUTDOOR || m_intersectingRooms.Count() == 0)
	{
		for(INT32 i = 0; i < m_rooms.Count(); ++i)
		{
			if(m_rooms[i].m_roomType == ruPORTAL_ROOMTYPE_OUTDOOR)
			{
				m_rooms[i].m_roomVisible = TRUE;
				m_intersectingRooms.Add(i);
			}
		}
	}

	// Recursively enumerate all visible rooms
	for(INT32 i = 0; i < m_intersectingRooms.Count(); ++i)
	{
		EnumerateVisibleRooms(m_intersectingRooms[i], viewCamera, CRuVector2(-1.0f, -1.0f), CRuVector2(1.0f, 1.0f));
	}

	// Populate list of visible entities
	for(INT32 i = 0; i < m_rooms.Count(); ++i)
	{
		if(m_rooms[i].m_roomVisible)
		{
			m_rooms[i].m_roomEntity->SetOption(ruENTITYOPTION_INVISIBLE, FALSE);
		}
		else
		{
			m_rooms[i].m_roomEntity->SetOption(ruENTITYOPTION_INVISIBLE, TRUE);
		}
	}
}

IRuObject *CRuContainer_Portal::Clone(IRuObject *clonedObject)
{
	// Create a new cloned entity if necessary
	clonedObject = clonedObject ? clonedObject : ruNEW CRuContainer_Portal();

	// Chain cloning call to parent class
	IRuEntity_Container::Clone(clonedObject);

	// Shortcut reference to the cloned entity
	CRuContainer_Portal *clonedContainer = static_cast<CRuContainer_Portal *>(clonedObject);

	// Clone all rooms
	for(INT32 i = 0; i < m_rooms.Count(); ++i)
	{
		// Append new room structure
		clonedContainer->m_rooms.Add(Room());
		Room &room = clonedContainer->m_rooms[clonedContainer->m_rooms.Count() - 1];

		IRuObject *clonedEntity = m_rooms[i].m_roomEntity->Clone();

		// Alternate processing for ACT rooms
		if(m_rooms[i].m_roomEntity->GetType().IsSubClassOf(CRuACTEntity::Type))
		{
			clonedEntity = static_cast<CRuACTEntity *>(m_rooms[i].m_roomEntity)->GetTemplate()->Construct();
		}

		if(clonedEntity && clonedEntity->GetType().IsSubClassOf(CRuEntity::Type))
		{
			room.m_roomType = m_rooms[i].m_roomType;
			room.m_roomEntity = static_cast<CRuEntity *>(clonedEntity);
//			room.m_roomIteration = room.m_roomEntity->GetIteration();
			room.m_numPortals = m_rooms[i].m_numPortals;
			room.m_portals = ruNEW Portal [room.m_numPortals];

			for(INT32 j = 0; j < room.m_numPortals; ++j)
			{
				room.m_portals[j] = m_rooms[i].m_portals[j];
			}

			// Disable cloning and serialization on the room entity
			room.m_roomEntity->SetOption(ruENTITYOPTION_NOCLONE, TRUE);
			room.m_roomEntity->SetOption(ruENTITYOPTION_NOSERIALIZE, TRUE);

			// Attach child to portal container
			clonedContainer->AddChild(room.m_roomEntity);
		}
		else
		{
			ruSAFE_RELEASE(clonedContainer);
			return NULL;
		}
	}

	return clonedContainer;
}

BOOL CRuContainer_Portal::SerializeFrom(IRuStream *stream)
{
	CRuGUID classGUID;

	// Read class GUID
	if(RuReadGUID(stream, classGUID) == FALSE)
	{
		return FALSE;
	}

	// Read version 
	INT32 version = 1;
	stream->Read(&version, sizeof(INT32));

	// Read number of rooms
	INT32 numRooms = 0;
	stream->Read(&numRooms, sizeof(INT32));

	// Read data for each room
	for(INT32 i = 0; i < numRooms; ++i)
	{
		// Append new room structure
		m_rooms.Add(Room());
		Room &room = m_rooms[m_rooms.Count() - 1];

		// Read room type
		stream->Read(&room.m_roomType, sizeof(RuPortal_RoomType));

		// Read room entity using the object factory
		IRuObject *entityObject = g_ruObjectFactory.CreateObject(stream);

		if(entityObject == NULL)
		{
		}
		else
		{
			if(entityObject->GetType().IsSubClassOf(CRuACTTemplate::Type))
			{
				// Construct an entity from the ACT template
				room.m_roomEntity = static_cast<CRuACTTemplate *>(entityObject)->Construct();
				ruSAFE_RELEASE(entityObject);
			}
			else if(entityObject->GetType().IsSubClassOf(CRuEntity::Type))
			{
				room.m_roomEntity = static_cast<CRuEntity *>(entityObject);

				// Disable cloning and serialization on the room entity
				room.m_roomEntity->SetOption(ruENTITYOPTION_NOCLONE, TRUE);
				room.m_roomEntity->SetOption(ruENTITYOPTION_NOSERIALIZE, TRUE);

				// Attach child to portal container
				this->AddChild(room.m_roomEntity);
			}
		}

		// Read number of portals
		stream->Read(&room.m_numPortals, sizeof(INT32));

		// Read data for each portal
		room.m_portals = ruNEW Portal [room.m_numPortals];
		for(INT32 j = 0; j < room.m_numPortals; ++j)
		{
			// Read normal
			stream->Read(&room.m_portals[j].m_normal.x, sizeof(REAL));
			stream->Read(&room.m_portals[j].m_normal.y, sizeof(REAL));
			stream->Read(&room.m_portals[j].m_normal.z, sizeof(REAL));

			// Read portal vertices
			for(INT32 k = 0; k < 4; ++k)
			{
				stream->Read(&room.m_portals[j].m_vertices[k].x, sizeof(REAL));
				stream->Read(&room.m_portals[j].m_vertices[k].y, sizeof(REAL));
				stream->Read(&room.m_portals[j].m_vertices[k].z, sizeof(REAL));
			}

			// Read destination room index
			stream->Read(&room.m_portals[j].m_desination, sizeof(INT32));
		}
	}

	return TRUE;
}

BOOL CRuContainer_Portal::SerializeTo(IRuStream *stream)
{
	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	// Write version
	INT32 version = 1;
	stream->Write(&version, sizeof(INT32));

	// Write number of rooms
	INT32 numRooms = m_rooms.Count();
	stream->Write(&numRooms, sizeof(INT32));

	// Write data for each room
	for(INT32 i = 0; i < numRooms; ++i)
	{
		Room &room = m_rooms[i];

		// Write room type
		stream->Write(&room.m_roomType, sizeof(RuPortal_RoomType));

		// Write room entity as embedded data stream
		if(room.m_roomEntity->GetType().IsSubClassOf(CRuACTEntity::Type))
		{
			static_cast<CRuACTEntity *>(room.m_roomEntity)->GetTemplate()->SerializeTo(stream);
		}
		else if(room.m_roomEntity->GetType().IsSubClassOf(CRuEntity::Type))
		{
			room.m_roomEntity->SerializeTo(stream);
		}

		// Write number of portals
		stream->Write(&room.m_numPortals, sizeof(INT32));

		// Write data for each portal
		for(INT32 j = 0; j < room.m_numPortals; ++j)
		{
			// Write normal
			stream->Write(&room.m_portals[j].m_normal.x, sizeof(REAL));
			stream->Write(&room.m_portals[j].m_normal.y, sizeof(REAL));
			stream->Write(&room.m_portals[j].m_normal.z, sizeof(REAL));

			// Write portal vertices
			for(INT32 k = 0; k < 4; ++k)
			{
				stream->Write(&room.m_portals[j].m_vertices[k].x, sizeof(REAL));
				stream->Write(&room.m_portals[j].m_vertices[k].y, sizeof(REAL));
				stream->Write(&room.m_portals[j].m_vertices[k].z, sizeof(REAL));
			}

			// Write destination room index
			stream->Write(&room.m_portals[j].m_desination, sizeof(INT32));
		}
	}

	return TRUE;
}

BOOL CRuContainer_Portal::ResetStates()
{
	return TRUE;
}

BOOL CRuContainer_Portal::Update(float elapsedTime)
{
	// Chain update call to parent class
	return IRuEntity_Container::Update(elapsedTime);
}

BOOL CRuContainer_Portal::QueryCollision(CRuCollisionQuery *colQuery)
{
	// If collision query is disabled, return immediately
	if(colQuery->m_ignoreCollisionFlag == FALSE && GetOption(ruENTITYOPTION_COLLISION) == 0)
		return TRUE;

	// Save previous collision transform
	CRuMatrix4x4 *originalWorldTransform = colQuery->m_worldTransform;
	CRuMatrix4x4 *originalInvWorldTransform = colQuery->m_invWorldTransform;

	// Store the inverse of the current world transform into the collision query
	colQuery->m_worldTransform = const_cast<CRuMatrix4x4 *>(&this->GetWorldTransform());
	colQuery->m_invWorldTransform = const_cast<CRuMatrix4x4 *>(&this->GetInverseWorldTransform());

	// Return value
	BOOL retVal = TRUE;

	if(m_collisionObject)
	{
		// Query attached collision object
		m_collisionObject->QueryCollision(colQuery);
	}
	else
	{
		// Query collision on all of the children
		for(INT32 i = 0; i < m_rooms.Count(); ++i)
		{
			if(m_rooms[i].m_roomEntity->QueryCollision(colQuery) == FALSE)
			{
				retVal = FALSE;
				break;
			}
		}
	}

	// Restore original collision transform
	colQuery->m_worldTransform = originalWorldTransform;
	colQuery->m_invWorldTransform = originalInvWorldTransform;

	return retVal;
}

INT32 CRuContainer_Portal::GetNumRooms() const
{
	return m_rooms.Count();
}

CRuEntity *CRuContainer_Portal::GetRoomEntity(INT32 index)
{
	return m_rooms[index].m_roomEntity;
}

void CRuContainer_Portal::EnumerateVisibleRooms(INT32 roomIndex, CRuCamera *camera, CRuVector2 &viewportMinima, CRuVector2 &viewportMaxima)
{
	CRuVector2 curMinima, curMaxima;
	CRuVector3 wsEyeDir, ssVertices[4];

	Room &room = m_rooms[roomIndex];

	// If the room has already been traversed, return immediately
	if(room.m_roomTraversed == TRUE)
	{
		return;
	}

	// Mark room as traversed
	room.m_roomTraversed = TRUE;

	for(INT32 j = 0; j < room.m_numPortals; ++j)
	{
//		// If the angle to the portal is too large, it is not visible (this does not work right)
//		if(DotProduct(room.m_portals[j].m_normal, camera->GetCameraLookAtVector()) >= 0.0f)
//		{
//			continue;
//		}

		// Transform portal vertices into screen space
		for(INT32 k = 0; k < 4; ++k)
		{
			ssVertices[k] = room.m_portals[j].m_vertices[k];
			room.m_roomEntity->GetWorldTransform().TransformPoint(ssVertices[k]);
			camera->GetFrustumTransform().TransformPointProjected(ssVertices[k]);
		}

		// Get bounding box of portal on the view plane
		curMinima.x = max(min(min(min(ssVertices[0].x, ssVertices[1].x), ssVertices[2].x), ssVertices[3].x), viewportMinima.x);
		curMinima.y = max(min(min(min(ssVertices[0].y, ssVertices[1].y), ssVertices[2].y), ssVertices[3].y), viewportMinima.y);
		curMaxima.x = min(max(max(max(ssVertices[0].x, ssVertices[1].x), ssVertices[2].x), ssVertices[3].x), viewportMaxima.x);
		curMaxima.y = min(max(max(max(ssVertices[0].y, ssVertices[1].y), ssVertices[2].y), ssVertices[3].y), viewportMaxima.y);

		// If portal projection does not lie on the current view area, it is not visible
		if(curMinima.x >= curMaxima.x || curMinima.y >= curMaxima.y)
		{
			continue;
		}

		// Mark destination room as visible
		m_rooms[room.m_portals[j].m_desination].m_roomVisible = TRUE;

		// Traverse the destination room
		EnumerateVisibleRooms(room.m_portals[j].m_desination, camera, curMinima, curMaxima);
	}
}

// ************************************************************************************************************************************************************

#pragma managed
