#include "../../Scene/Terrain/RuWorld_Base.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuWorld_ChunkTree, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuWorld_ChunkTree, "CRuWorld_ChunkTree", "IRuObject")
ruCLASSGUID_DEFINE(CRuWorld_ChunkTree, 0xDFD72A85, 0x40394181, 0xBE44CECD, 0x8D2B0527)

ruRTTI_DEFINE_SUBCLASS(CRuWorld_EntityDescriptor, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuWorld_EntityDescriptor, "CRuWorld_EntityDescriptor", "IRuObject")
ruCLASSGUID_DEFINE(CRuWorld_EntityDescriptor, 0x829C671F, 0xF6B44e14, 0xA1320ECF, 0x93885693)

ruRTTI_DEFINE_SUBCLASS(CRuWorld_Trigger, CRuEntity)
ruHEAP_DEFINE_SUBCLASS(CRuWorld_Trigger, "CRuWorld_Trigger", "CRuEntity")
ruCLASSGUID_DEFINE(CRuWorld_Trigger, 0x83E36878, 0x3C324a32, 0x97AD4DD7, 0x6A7E7350)

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

CRuWorld_ChunkTree::ChunkTreeNode::ChunkTreeNode()
:	m_chunkArraySize(0),
	m_numChunks(0),
	m_chunks(NULL)
{
	// Initialize children array to NULL
	for(INT32 i = 0; i < 4; ++i)
	{
		m_children[i] = NULL;
	}
}

CRuWorld_ChunkTree::ChunkTreeNode::~ChunkTreeNode()
{
	// Destroy chunk descriptor array
	delete[] m_chunks;

	// Destroy children
	for(INT32 i = 0; i < 4; ++i)
	{
		delete m_children[i];
	}
}

BOOL CRuWorld_ChunkTree::ChunkTreeNode::IsLeaf()
{
	if(m_children[0] == NULL)
		return TRUE;

	return FALSE;
}

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

CRuWorld_ChunkTree::CRuWorld_ChunkTree()
:	m_root(NULL)
{
}

CRuWorld_ChunkTree::~CRuWorld_ChunkTree()
{
	delete m_root;
}

BOOL CRuWorld_ChunkTree::Initialize(const CRuAABB &treeBounds, INT32 chunksPerNode)
{
	m_root = ruNEW ChunkTreeNode();
	m_root->m_bounds = treeBounds;

	m_chunksPerNode = chunksPerNode;

	return TRUE;
}

BOOL CRuWorld_ChunkTree::InsertChunk(INT32 x, INT32 z, const CRuAABB &bounds)
{
	return InsertChunk(m_root, x, z, bounds);
}

void CRuWorld_ChunkTree::EnumerateChunks(const CRuPlane4 *frustumPlanes, CRuArrayList<DWORD> &chunkKeyOut)
{
	EnumerateChunks(m_root, frustumPlanes, chunkKeyOut);
}

void CRuWorld_ChunkTree::EnumerateChunks(const CRuAABB &box, CRuArrayList<DWORD> &chunkKeyOut)
{
	EnumerateChunks(m_root, box, chunkKeyOut);
}

void CRuWorld_ChunkTree::EnumerateChunks(const CRuSphere &sphere, CRuArrayList<DWORD> &chunkKeyOut)
{
	EnumerateChunks(m_root, sphere, chunkKeyOut);
}

void CRuWorld_ChunkTree::EnumerateChunks(const CRuRay3 &ray, CRuArrayList<DWORD> &chunkKeyOut)
{
	EnumerateChunks(m_root, ray, chunkKeyOut);
}

void CRuWorld_ChunkTree::EnumerateChunks(const CRuSegment3 &segment, CRuArrayList<DWORD> &chunkKeyOut)
{
	EnumerateChunks(m_root, segment, chunkKeyOut);
}

BOOL CRuWorld_ChunkTree::InsertChunk(ChunkTreeNode *node, INT32 x, INT32 z, const CRuAABB &bounds)
{
	CRuVector3 centroid = (bounds.Minima() + bounds.Maxima()) * 0.5f;

	if(node->m_bounds.Contains(centroid))
	{
		if(node->IsLeaf())
		{
			if(node->m_chunkArraySize == 0)
			{
				node->m_chunkArraySize = m_chunksPerNode;
				node->m_numChunks = 0;
				node->m_chunks = ruNEW ChunkDescriptor [node->m_chunkArraySize];
			}

			if(node->m_numChunks >= node->m_chunkArraySize)
			{
				// Subdivide and store into children
				if(Subdivide(node))
				{
					return InsertChunk(node, x, z, bounds);
				}

				return FALSE;
			}
			else
			{
				node->m_chunks[node->m_numChunks].m_x = x;
				node->m_chunks[node->m_numChunks].m_z = z;
				node->m_chunks[node->m_numChunks].m_bounds = bounds;

				++node->m_numChunks;

				return TRUE;
			}
		}
		else
		{
			// Insert the chunk into the first acceptable child
			for(INT32 i = 0; i < 4; ++i)
			{
				if(InsertChunk(node->m_children[i], x, z, bounds) == TRUE)
				{
					return TRUE;
				}
			}
		}
	}

	return FALSE;
}

BOOL CRuWorld_ChunkTree::Subdivide(ChunkTreeNode *node)
{
	// Create children
	for(INT32 i = 0; i < 4; ++i)
	{
		node->m_children[i] = ruNEW ChunkTreeNode();
		node->m_children[i]->m_bounds.SetBoundsToQuadrantXZ(node->m_bounds, i);
	}

	// Distribute chunks to children
	for(INT32 i = 0; i < node->m_numChunks; ++i)
	{
		if(InsertChunk(node, node->m_chunks[i].m_x, node->m_chunks[i].m_z, node->m_chunks[i].m_bounds) == FALSE)
		{
			return FALSE;
		}
	}

	// Destroy chunk information at node
	delete[] node->m_chunks;

	node->m_chunkArraySize = 0;
	node->m_numChunks = 0;
	node->m_chunks = NULL;

	return TRUE;
}

void CRuWorld_ChunkTree::EnumerateChunks(ChunkTreeNode *node, const CRuPlane4 *frustumPlanes, CRuArrayList<DWORD> &chunkKeyOut)
{
	// Ensure the node intersects the frustum
	if(IntersectsFrustum(frustumPlanes, node->m_bounds))
	{
		if(node->IsLeaf())
		{
			// Enumerate visible chunks
			for(INT32 i = 0; i < node->m_numChunks; ++i)
			{
				if(IntersectsFrustum(frustumPlanes, node->m_chunks[i].m_bounds))
				{
					// Add chunk key to output list
					chunkKeyOut.Add((node->m_chunks[i].m_x << 16) | node->m_chunks[i].m_z);
				}
			}
		}
		else
		{
			// Recurse to children
			for(INT32 i = 0; i < 4; ++i)
			{
				EnumerateChunks(node->m_children[i], frustumPlanes, chunkKeyOut);
			}
		}
	}
}

void CRuWorld_ChunkTree::EnumerateChunks(ChunkTreeNode *node, const CRuAABB &box, CRuArrayList<DWORD> &chunkKeyOut)
{
	// Ensure the node intersects the box
	if(Intersects(box, node->m_bounds))
	{
		if(node->IsLeaf())
		{
			// Enumerate visible chunks
			for(INT32 i = 0; i < node->m_numChunks; ++i)
			{
				if(Intersects(box, node->m_chunks[i].m_bounds))
				{
					// Add chunk key to output list
					chunkKeyOut.Add((node->m_chunks[i].m_x << 16) | node->m_chunks[i].m_z);
				}
			}
		}
		else
		{
			// Recurse to children
			for(INT32 i = 0; i < 4; ++i)
			{
				EnumerateChunks(node->m_children[i], box, chunkKeyOut);
			}
		}
	}
}

void CRuWorld_ChunkTree::EnumerateChunks(ChunkTreeNode *node, const CRuSphere &sphere, CRuArrayList<DWORD> &chunkKeyOut)
{
	// Ensure the node intersects the sphere
	if(Intersects(node->m_bounds, sphere))
	{
		if(node->IsLeaf())
		{
			// Enumerate visible chunks
			for(INT32 i = 0; i < node->m_numChunks; ++i)
			{
				if(Intersects(node->m_chunks[i].m_bounds, sphere))
				{
					// Add chunk key to output list
					chunkKeyOut.Add((node->m_chunks[i].m_x << 16) | node->m_chunks[i].m_z);
				}
			}
		}
		else
		{
			// Recurse to children
			for(INT32 i = 0; i < 4; ++i)
			{
				EnumerateChunks(node->m_children[i], sphere, chunkKeyOut);
			}
		}
	}
}

void CRuWorld_ChunkTree::EnumerateChunks(ChunkTreeNode *node, const CRuRay3 &ray, CRuArrayList<DWORD> &chunkKeyOut)
{
	// Ensure the node intersects the ray
	if(Intersects(ray, node->m_bounds))
	{
		if(node->IsLeaf())
		{
			// Enumerate visible chunks
			for(INT32 i = 0; i < node->m_numChunks; ++i)
			{
				if(Intersects(ray, node->m_chunks[i].m_bounds))
				{
					// Add chunk key to output list
					chunkKeyOut.Add((node->m_chunks[i].m_x << 16) | node->m_chunks[i].m_z);
				}
			}
		}
		else
		{
			// Recurse to children
			for(INT32 i = 0; i < 4; ++i)
			{
				EnumerateChunks(node->m_children[i], ray, chunkKeyOut);
			}
		}
	}
}

void CRuWorld_ChunkTree::EnumerateChunks(ChunkTreeNode *node, const CRuSegment3 &segment, CRuArrayList<DWORD> &chunkKeyOut)
{
	// Ensure the node intersects the segment
	if(Intersects(segment, node->m_bounds))
	{
		if(node->IsLeaf())
		{
			// Enumerate visible chunks
			for(INT32 i = 0; i < node->m_numChunks; ++i)
			{
				if(Intersects(segment, node->m_chunks[i].m_bounds))
				{
					// Add chunk key to output list
					chunkKeyOut.Add((node->m_chunks[i].m_x << 16) | node->m_chunks[i].m_z);
				}
			}
		}
		else
		{
			// Recurse to children
			for(INT32 i = 0; i < 4; ++i)
			{
				EnumerateChunks(node->m_children[i], segment, chunkKeyOut);
			}
		}
	}
}

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

CRuWorld_EntityDescriptor::CRuWorld_EntityDescriptor()
:	m_resourceName(NULL),
	m_zoneFlags(0x00000000),
	m_scale(1.0f, 1.0f, 1.0f),
	m_rotation(0.0f, 1.0f, 0.0f, 0.0f),
	m_translation(0.0f, 0.0f, 0.0f),
	m_worldBoundsDirty(FALSE)
{
}

CRuWorld_EntityDescriptor::~CRuWorld_EntityDescriptor()
{
	delete[] m_resourceName;
}

BOOL CRuWorld_EntityDescriptor::SerializeFrom(IRuStream *stream)
{
	// Read class GUID
	CRuGUID classGUID;
	if(RuReadGUID(stream, classGUID) == FALSE)
	{
		return FALSE;
	}

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

	// Chain serialization to base class
	IRuObject::SerializeFrom(stream);

	// Read entity guid
	RuReadGUID(stream, m_guid);

	// Read length of the resource name string (including NULL)
	INT32 resNameLength = 0;
	stream->Read(&resNameLength, sizeof(INT32));

	// Read the resource name string
	m_resourceName = ruNEW char [resNameLength];
	stream->Read(m_resourceName, resNameLength);

	// Read zone flags
	stream->Read(&m_zoneFlags, sizeof(UINT32));

	stream->Read(&m_scale.x, sizeof(REAL));
	stream->Read(&m_scale.y, sizeof(REAL));
	stream->Read(&m_scale.z, sizeof(REAL));

	stream->Read(&m_rotation.x, sizeof(REAL));
	stream->Read(&m_rotation.y, sizeof(REAL));
	stream->Read(&m_rotation.z, sizeof(REAL));
	stream->Read(&m_rotation.w, sizeof(REAL));

	stream->Read(&m_translation.x, sizeof(REAL));
	stream->Read(&m_translation.y, sizeof(REAL));
	stream->Read(&m_translation.z, sizeof(REAL));

	stream->Read(&m_localBounds.Center().x, sizeof(REAL));
	stream->Read(&m_localBounds.Center().y, sizeof(REAL));
	stream->Read(&m_localBounds.Center().z, sizeof(REAL));
	stream->Read(&m_localBounds.Radius(), sizeof(REAL));

	// Read number of linked chunks
	INT32 numLinkedChunks = m_linkedChunks.Count();
	stream->Read(&numLinkedChunks, sizeof(INT32));

	// Read all linked chunks
	m_linkedChunks.SetArraySize(numLinkedChunks);
	m_linkedChunks.SetNumEntries(numLinkedChunks);
	for(INT32 i = 0; i < numLinkedChunks; ++i)
	{
		stream->Read(&m_linkedChunks[i], sizeof(INT32));
	}

	// Set world bounds dirty to TRUE
	m_worldBoundsDirty = TRUE;

	return TRUE;
}

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

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

	// Chain serialization to base class
	IRuObject::SerializeTo(stream);

	// Write entity GUID
	stream->Write(m_guid.Data(), m_guid.Size());

	if(m_resourceName)
	{
		// Write length of the resource name string (including NULL)
		INT32 resNameLength = strlen(m_resourceName) + 1;
		stream->Write(&resNameLength, sizeof(INT32));

		// Write the resource name string
		stream->Write(m_resourceName, resNameLength);
	}
	else
	{
		// Write length of the resource name string (including NULL)
		INT32 resNameLength = 1;
		stream->Write(&resNameLength, sizeof(INT32));

		// Write the resource name string
		stream->Write("", resNameLength);
	}

	// Write zone flags
	stream->Write(&m_zoneFlags, sizeof(UINT32));

	// Write entity scale
	stream->Write(&m_scale.x, sizeof(REAL));
	stream->Write(&m_scale.y, sizeof(REAL));
	stream->Write(&m_scale.z, sizeof(REAL));

	// Write entity rotation
	stream->Write(&m_rotation.x, sizeof(REAL));
	stream->Write(&m_rotation.y, sizeof(REAL));
	stream->Write(&m_rotation.z, sizeof(REAL));
	stream->Write(&m_rotation.w, sizeof(REAL));

	// Write entity translation
	stream->Write(&m_translation.x, sizeof(REAL));
	stream->Write(&m_translation.y, sizeof(REAL));
	stream->Write(&m_translation.z, sizeof(REAL));

	// Write entity local bounds
	stream->Write(&m_localBounds.Center().x, sizeof(REAL));
	stream->Write(&m_localBounds.Center().y, sizeof(REAL));
	stream->Write(&m_localBounds.Center().z, sizeof(REAL));
	stream->Write(&m_localBounds.Radius(), sizeof(REAL));

	// Write number of linked chunks
	INT32 numLinkedChunks = m_linkedChunks.Count();
	stream->Write(&numLinkedChunks, sizeof(INT32));

	// Write all linked chunks
	for(INT32 i = 0; i < numLinkedChunks; ++i)
	{
		stream->Write(&m_linkedChunks[i], sizeof(INT32));
	}

	return TRUE;
}

const char *CRuWorld_EntityDescriptor::GetResourceName() const
{
	if(m_resourceName)
		return m_resourceName;

	return "";
}

void CRuWorld_EntityDescriptor::SetResourceName(const char *resName)
{
	delete[] m_resourceName;
	m_resourceName = ruNEW char [strlen(resName) + 1];
	strcpy(m_resourceName, resName);
}

UINT32 CRuWorld_EntityDescriptor::GetZoneFlags() const
{
	return m_zoneFlags;
}

void CRuWorld_EntityDescriptor::SetZoneFlags(UINT32 zoneFlags)
{
	m_zoneFlags = zoneFlags;
}

const CRuVector3 &CRuWorld_EntityDescriptor::GetScale() const
{
	return m_scale;
}

void CRuWorld_EntityDescriptor::SetScale(const CRuVector3 &scale)
{
	m_scale = scale;
	m_worldBoundsDirty = TRUE;
}

const CRuQuaternion &CRuWorld_EntityDescriptor::GetRotation() const
{
	return m_rotation;
}

void CRuWorld_EntityDescriptor::SetRotation(const CRuQuaternion &rotation)
{
	m_rotation = rotation;
	m_worldBoundsDirty = TRUE;
}

const CRuVector3 &CRuWorld_EntityDescriptor::GetTranslation() const
{
	return m_translation;
}

void CRuWorld_EntityDescriptor::SetTranslation(const CRuVector3 &translation)
{
	m_translation = translation;
	m_worldBoundsDirty = TRUE;
}

const CRuSphere &CRuWorld_EntityDescriptor::GetLocalBounds() const
{
	return m_localBounds;
}

void CRuWorld_EntityDescriptor::SetLocalBounds(const CRuSphere &bounds)
{
	m_localBounds = bounds;
	m_worldBoundsDirty = TRUE;
}

const CRuSphere &CRuWorld_EntityDescriptor::GetWorldBounds()
{
	if(m_worldBoundsDirty)
	{
		CRuMatrix4x4 worldTransform;
		worldTransform.SetToIdentity();
		worldTransform.Scale(m_scale);
		worldTransform.Rotate(m_rotation);
		worldTransform.Translate(m_translation);

		worldTransform.TransformPoint(m_localBounds.Center(), m_worldBounds.Center());
		m_worldBounds.Radius() = m_localBounds.Radius() * max(m_scale.m_x, max(m_scale.m_y, m_scale.m_z));
	}

	return m_worldBounds;
}

INT32 CRuWorld_EntityDescriptor::GetNumLinkedChunks() const
{
	return m_linkedChunks.Count();
}

INT32 CRuWorld_EntityDescriptor::GetLinkedChunk(INT32 index) const
{
	return m_linkedChunks[index];
}

BOOL CRuWorld_EntityDescriptor::InsertLinkedChunk(INT32 chunkID)
{
	if(m_linkedChunks.Contains(chunkID) == FALSE)
	{
		m_linkedChunks.Add(chunkID);
	}

	return TRUE;
}

BOOL CRuWorld_EntityDescriptor::DeleteLinkedChunk(INT32 chunkID)
{
	m_linkedChunks.Remove(chunkID);

	return TRUE;
}

void CRuWorld_EntityDescriptor::ClearLinkedChunks()
{
	m_linkedChunks.Clear();
}

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

CRuWorld_Trigger::CRuWorld_Trigger()
:	m_planeGizmo(NULL),
	m_triggerType(RuWorld::ruTRIGGERTYPE_ZONE),
	m_zoneID(0)
{
	m_planeGizmo = ruNEW CRuGizmo_Plane();
	this->AddChild(m_planeGizmo);
}

CRuWorld_Trigger::~CRuWorld_Trigger()
{
	ruSAFE_RELEASE(m_planeGizmo);
}

BOOL CRuWorld_Trigger::SerializeFrom(IRuStream *stream)
{
	// Read class GUID
	CRuGUID classGUID;
	if(RuReadGUID(stream, classGUID) == FALSE)
	{
		return FALSE;
	}

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

	// Chain serialization to base class
	CRuEntity::SerializeFrom(stream);

	// Read entity guid
	RuReadGUID(stream, m_guid);

	// Serialize plane gizmo
	m_planeGizmo->SerializeFrom(stream);

	// Read trigger type
	stream->Read(&m_triggerType, sizeof(RuWorld::TriggerType));

	// Read zone ID
	stream->Read(&m_zoneID, sizeof(INT32));

	return TRUE;
}

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

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

	// Chain serialization to base class
	CRuEntity::SerializeTo(stream);

	// Write entity GUID
	stream->Write(m_guid.Data(), m_guid.Size());

	// Serialize plane gizmo
	m_planeGizmo->SerializeTo(stream);

	// Write trigger type
	stream->Write(&m_triggerType, sizeof(RuWorld::TriggerType));

	// Write zone ID
	stream->Write(&m_zoneID, sizeof(INT32));

	return TRUE;
}

void CRuWorld_Trigger::Initialize(const CRuVector3 &origin)
{
	// Set translation
	this->ResetAllTransforms();
	this->Translate(origin);

	// Reset gizmo parameters
}

CRuGizmo_Plane *CRuWorld_Trigger::GetGizmo()
{
	return m_planeGizmo;
}

RuWorld::TriggerType CRuWorld_Trigger::GetTriggerType()
{
	return m_triggerType;
}

void CRuWorld_Trigger::SetTriggerType(RuWorld::TriggerType triggerType)
{
	m_triggerType = triggerType;
}

INT32 CRuWorld_Trigger::GetZoneID()
{
	return m_zoneID;
}

void CRuWorld_Trigger::SetZoneID(INT32 zoneID)
{
	m_zoneID = zoneID;
}

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

#pragma managed
