/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "scene3d/scene.h"
		#include "scene3d/mlight.h"
		#include "scene3d/mobject.h"
		#include "scene3d/mcamera.h"
		#include "scene3d/mtrigger.h"
		#include "scene3d/memitter.h"
		#include "scene3d/mterrain.h"
		#include "scene3d/instance.h"
		#include "scene3d/mconstraint.h"
		#include "scene3d/mitem_scripted_object.h"
		#include "scene3d/scene_event_script_interface.h"
		#include "scene3d/scene_renderer_environment_interface.h"
		#include "scene3d/scene_physic_world_interface.h"
		#include "scene3d/scene_scripted_object.h"
		#include "scene3d/scene_script_unit.h"
		#include "scene3d/mitem_event_script_interface.h"
		#include "physic/physic_constraint.h"
		#include "physic/physic_world.h"
		#include "ui/ui.h"

		using namespace GS;
		using namespace S3D;


//------------------------------------------------------------------------------
void				Scene::SetClock(Core::Clock *c, bool set_ui)
{
	clock = c ? c : new Core::Clock;
	if	(set_ui)
		ui->SetClock(c);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::GetMinMax(MinMax &minmax) const
{
	bool initial = true;

	ArrayListForeachPtr(MItem *, i, active_list)
	{
		if	(i->GetItemType() != Type_Object)
			continue;

		MObject *object = (MObject *)i;
		if	(object->render_data.IsNull() || object->render_data->geometry.IsNull())
			continue;

		MinMax local_minmax = object->render_data->geometry->minmax;

		if	(initial)
				minmax = local_minmax;
		else	minmax.Grow(local_minmax);
		initial = false;
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Group				*Scene::AddGroup(Group *g)
{
	group_list.Add(g);
	return g;
}
bool				Scene::RemoveGroup(Group *g)
{
	return group_list.Remove(g);
}
Group				*Scene::FindGroup(const char *name) const
{
	ListForeachPtr(Group *, g, group_list)
		if	(g->name == name)
			return g;
	return NULL;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
uint				Scene::GetItemGroupList(const MItem *item, SharedList <Group *> &list) const
{
	uint count = 0;
	ListForeachPtr(Group *, g, group_list)
		if	(g->IsMember(item))
		{
			count++;
			list.Add(g);
		}

	return count;
}
void				Scene::UnregisterItemFromGroups(MItem *item) const
{
	ListForeachPtr(Group *, g, group_list)
		g->Remove(item);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Scene::ActivateItem(MItem *i, bool v)
{
	if	(v)
	{
		if	(active_list.IndexOf(i) == -1)
			active_list.Add(i);
	}
	else
		active_list.Remove(i);

	if	(i->physic_item)
		i->physic_item->SetActive(v);

	i->mitem_flags.Raise(MItem::Flag_IsActive, v);
	return true;
}
void				Scene::ProcessItemActivationQueue()
{
	ListForeachPtr(MItem *, i, activation_queue)
		ActivateItem(i, true);
	ListForeachPtr(MItem *, i, deactivation_queue)
		ActivateItem(i, false);

	activation_queue.Clear();
	deactivation_queue.Clear();
}
void				Scene::QueueItemActivation(MItem *item, bool activate, bool propagate)
{
	if	(item->mitem_flags.IsSet(MItem::Flag_IsActive) != activate)
	{
		// Queue item.
		if	(activate)
		{
			if	(!activation_queue.Find(item))
				activation_queue.Add(item);
			deactivation_queue.Remove(item);
		}
		else
		{
			if	(!deactivation_queue.Find(item))
				deactivation_queue.Add(item);
			activation_queue.Remove(item);
		}

		// Script callback.
		item->item_event->OnActivate(item, activate);
		item->mitem_flags.Raise(MItem::Flag_IsActive, activate);
	}

	if	(propagate)
		ListForeachPtr(Core::Item *, child, item->GetBaseItem()->GetChildren())
			QueueItemActivation(LocateManagedItem(child), activate, true);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Scene::Create(PhysicWorld *w)
{
	Clear();

	if	((physic_world = w) != NULL)
	{
		if	(!physic_world->Create())
			return false;

	//	bullet_world->CreateDebugger(GetEngine().GetRenderer());
		physic_world->SetWorldInterface(iphysic_world);
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
MTrigger			*Scene::RaytraceTriggerList(Vector4 &s, Vector4 &d, float l, Vector4 *i)
{
	ListForeachPtr(MItem *, item, item_list)
		if	(item->GetItemType() == Type_Trigger)
		{
			MTrigger *t = (MTrigger *)item;

			// Move ray to trigger space.
			Vector4 local_s, local_d;
			t->GetInverseMatrix().Apply(&local_s, &s);
			t->GetInverseMatrix().ApplyRotation(&local_d, &d);

			// Test ray against bbox normalized space.
			MinMax mm;
			mm.SetFromPositionSize(Vector4(0, 0, 0), Vector4(1, 1, 1));

			// Raytrace against minmax.
			Vector4 local_i;
			if	(l == -1 ? mm.ClassifyLine(local_s, local_d, local_i) : mm.ClassifySegment(local_s, local_s + local_d * l, local_i))
			{
				if	(i)
					*i = local_i * t->GetMatrix();
				return t;
			}
		}

	return NULL;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::RecordAllItemLocation()
{
	ListForeachPtr(MItem *, i, item_list)
		i->SetInitialTransformation();
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
MItem				*Scene::ItemFromUid(uint uid) const
{
	ListForeachPtr(MItem *, i, item_list)
		if	(i->GetUid() == uid)
			return i;

	return NULL;
}
MItemType			Scene::LocateItem(const Core::Item *item, MItem **entity) const
{
	ListForeachPtr(MItem *, i, item_list)
		if	(i->GetBaseItem() == item)
		{
			if	(entity)
				*entity = i;
			return i->GetItemType();
		}

	return Type_None;
}
MItem				*Scene::LocateManagedItem(Core::Item *i)
{
	return i ? (MItem *)i->mitem : NULL;
}
//------------------------------------------------------------------------------

static bool FindItemByName(const MItem *i, const char *name) { return i->name == name; }

//------------------------------------------------------------------------------
MItem				*Scene::Item(const char *item_name, MItem *parent) const
{
	StringList node;
	String(item_name).Split("/", node);

	MItem *item = NULL;

	if	(parent)
		ListForeachPtr(Core::Item *, child, parent->GetBaseItem()->GetChildren())
		{
			MItem *child_item = LocateManagedItem(child);
			if	(child_item && (child_item->name == node[0]))
			{
				item = child_item;
				break;
			}
		}
	else
		item = ListFindEx(item_list, FindItemByName, node[0]);

	if	(!item)
		__ERR__(__LOG_W__ << "No item named '" << item_name << "' in scene '" << name << "'.\n", NULL)

	// Solve additional path.
	if	(node.GetCount() > 1)
		for	(uint n = 1; n < node.GetCount(); ++n)
		{
			if	(item->GetItemType() != Type_Instance)
				__ERR__(__LOG_W__ << "Cannot solve sub-path '" << node[n] << "' on item '" << item->name << "'.\n", NULL)

			Instance *instance = (Instance *)item;
			if	(!instance->instance_group)
				return NULL;

			item = instance->instance_group->Item(node[n]);
		}

	return item;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::SetItemStatic(MItem *item, bool v)
{
	item->mitem_flags.Raise(MItem::Flag_IsStatic, v);

	if	(Core::Renderable *r = item->GetRenderable())
	{
		RemoveRenderable(r);
		AddRenderable(r, v);
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::AddRenderable(Core::Renderable *r, bool is_static)
{
	if	(is_static)
			octree_culling_system.AddRenderable(r);
	else	simple_culling_system.AddRenderable(r);
}
void				Scene::RemoveRenderable(Core::Renderable *r)
{
	simple_culling_system.DeleteRenderable(r);
	octree_culling_system.DeleteRenderable(r);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::SetupItemComponents(MItem *i)
{
	// Create scripted object and event handler.
	i->scripted_object = new MItemScriptedObject(i, vm);
	if	(vm)
		i->item_event = new MItemScriptEvent;

	// Create physic item.
	if	(physic_world && i->GetBaseItem())
	{
		i->physic_item = physic_world->NewItem();
		i->physic_item->SetUserPointer((void *)i);
	}
}
void				Scene::AddItem(MItem *i, bool setup_components)
{
	i->uid = current_item_uid++;

	if	(setup_components)
		SetupItemComponents(i);

	g_scene_messenger.BroadcastMessage(SceneMsg_AddingItem, this, i);

	// Add to all lists.
	item_list.Add(i);
	active_list.Add(i);

	switch (i->GetItemType())
	{
		case Type_Light:	light_list.Add((MLight *)i);		break;
		case Type_Trigger:	trigger_list.Add((MTrigger *)i);	break;
	}

	// Add to culling systems.
	if	(Core::Renderable *r = i->GetRenderable())
		AddRenderable(r, i->mitem_flags.IsSet(MItem::Flag_IsStatic));

	g_scene_messenger.BroadcastMessage(SceneMsg_ItemAdded, this, i);
}
bool				Scene::RemoveItem(MItem *i)
{
	g_scene_messenger.BroadcastMessage(SceneMsg_DeletingItem, this, (void *)i);

	i->item_event->OnDelete(i);

	if	(vm.IsValid())
		vm->InvalidateNativeReference(i);

	// Remove from groups.
	UnregisterItemFromGroups(i);

	if	(Core::Renderable *r = i->GetRenderable())
	{
		// Remove from culling systems.
		simple_culling_system.DeleteRenderable(r);
		octree_culling_system.DeleteRenderable(r);
	}

	if	(Core::Item *b = i->GetBaseItem())
	{
		// Unset if current camera.
		if	(current_camera == b)
			current_camera = NULL;

		// Remove from triggers & skins.
		ListForeachPtr(MItem *, item, item_list)
			switch (item->GetItemType())
			{
				case Type_Trigger:
					((MTrigger *)item)->DropItem(b);
					break;

				case Type_Object:
				{
					MObject *o = (MObject *)item;
					for	(uint n = 0; n < o->GetBoneCount(); ++n)
						if	(o->GetBone(n) == b)
							o->BindBone(n, NULL);
				}
				break;
			}
	}

	// Remove from physic world.
	i->physic_item = NULL;

	switch (i->GetItemType())
	{
		case Type_Light:	light_list.Remove((MLight *)i);		break;
		case Type_Trigger:	trigger_list.Remove((MTrigger *)i);	break;
	}

	active_list.Remove(i);
	removal_queue.Remove(i);
	activation_queue.Remove(i);
	deactivation_queue.Remove(i);

	item_list.Remove(i);	// keep last

	g_scene_messenger.BroadcastMessage(SceneMsg_ItemDeleted, this, i);
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::QueueItemRemoval(MItem *i)
{	removal_queue.Add(i);	}
void				Scene::ProcessItemRemovalQueue()
{
	while (List <MItem *> ::Item *r = removal_queue.GetRoot())
		RemoveItem(r->Object());
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Scene::Clear(bool keep_script)
{
	g_scene_messenger.BroadcastMessage(SceneMsg_Clearing, this, 0);

	scene_event->OnDelete(this);

	// Remove all items (immediate/non-queued).
	while (List <MItem *> ::Item *i = item_list.GetRoot())
		RemoveItem(i->Object());

	// Drop UI.
	ui->Clear();

	// Close scripts.
	if	(!keep_script)
		scripted_object->RemoveAllUnit();

	fog_color.Set();
	fog_near = 0;
	fog_far = 0;

	current_item_uid = 0;

	g_scene_messenger.BroadcastMessage(SceneMsg_Cleared, this, 0);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Scene::Scene(Script::IVM *script_vm) : profiler(*this)
{
	vm = script_vm;
	current_camera = NULL;

	current_item_uid = 0;

	ambient_intensity = 1;
	target_exposure = 0.25;

	time_of_day = 0.5;

	fog_near = 0;
	fog_far = 0;
	fog_color.Set(0, 0, 0);

	background_color.Set(0, 0, 0);
	ambient_color.Set(0, 0, 0);

	clock = new Core::Clock;

	scene_event = vm ? new SceneScriptEvent : new ISceneEvent;
	scripted_object = new SceneScriptedObject(vm, this);

	irenderer_environment = new IEnvironment(this);
	iphysic_world = new IScenePhysicWorld(this);

	ui = new S2D::Scene(vm);
}
Scene::~Scene()
{
	SetAsScriptGlobalScene();
	Clear();

	if	(vm)
		vm->InvalidateNativeReference((void *)this);
}
//------------------------------------------------------------------------------
