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


		#include "scene3d/scene.h"
		#include "scene3d/mobject.h"
		#include "scene3d/mlight.h"
		#include "scene3d/mcamera.h"
		#include "scene3d/mtrigger.h"
		#include "scene3d/instance.h"
		#include "scene3d/memitter.h"
		#include "scene3d/mterrain.h"
		#include "scene3d/mconstraint.h"
		#include "scene3d/group.h"
		#include "physic/physic_constraint.h"
		#include "physic/physic_world.h"
		#include "ui/ui.h"
		#include "script/scripted_object.h"
		#include "script/script_unit.h"
		#include "container/nmap.h"

		using namespace GS;
		using namespace S3D;
		using NML::Tag;


//------------------------------------------------------------------------------
static MItem	*GetMappedItemFromTag(Scene &scene, Tag *pt, const char *tag, Map <uint, uint> &uid_map, MItemType type = Type_None)
{
	Tag *muid = pt->GetTypedTag(tag, Variant::VariantInteger);
	if	(!muid)
		__ERR__(__LOG_W__ << "Invalid UID tag.\n", NULL)

	uint uid = muid->GetUnsigned();
	if	(!uid_map.HasKey(uid))
		__ERR__(__LOG_W__ << "Invalid item UID.\n", NULL)

	MItem *mitem = scene.ItemFromUid(uid_map[uid]);
	if	(mitem && (type != Type_None) && (mitem->GetItemType() != type))
		__ERR__(__LOG_V__ << "Item type filtered out.\n", NULL)

	return mitem;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static bool		ShouldLoadItem(ToolMode mode, Tag *pt, uint flag, int load_level)
{
	if	(pt->GetTag("MItem:ToolSpecific;") || pt->GetTag("MItem:Helper;"))
	{
		if	(!(flag & SceneIOHelper))
			return false;

		// Do not load tool specific items in no tool or project preview modes.
		if	((mode == NoTool) || (mode == ToolProjectPreview))
			return false;

		// Do not load tool specific items in no tool mode above the first load level.
		if	(load_level > 0)
			return false;
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
template <class T> static T *LoadItemDerived(Scene *scene, T *i, Map <uint, uint> &uid_map, Tag *t, uint type_flag, const uint load_flag, int load_level, ToolMode tool_mode)
{
	if	(!(load_flag & type_flag) || !ShouldLoadItem(tool_mode, t, load_flag, load_level))
	{
		delete i;
		return NULL;
	}

	scene->SetupItemComponents(i);

	if	(i->FromMetaTag(*t))
	{
		uint olduid = i->GetUid();			// uid from source scene
		scene->AddItem(i, false);			// add to live scene
		uid_map.Add(olduid, i->GetUid());	// store map from source to live scene
	}
	return i;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Scene::FromMetaTagStoreGroup(const Tag &tag, Group **group, uint load_flag, ToolMode tool_mode, int load_level)
{
	if	(tag.name != "Scene")
		__ERR__(__LOG_E__ << "Could not parse scene, incorrect root tag (" << tag.name << ").\n", false);

	Benchmark bench(true);
	g_scene_messenger.BroadcastMessage(SceneMsg_Loading, this, 0);

	ui->ResetCommandList();

	// Create scene group.
	if	(group && !group[0])
	{
		group[0] = new Group;
		group[0]->name = "SceneGroup";
		group_list.Add(group[0]);
	}

	// Read items and setup uid remap array.
	Map <uint, uint> uid_map;

	if	(Tag *itag = tag.GetTag("Items"))
	{
		// Item count in scene.
		uint item_to_load = 0;
		NMLTagForeach(pt, *itag)
			if	(Tag *item_tag = pt->GetTag("MItem;"))
				item_to_load++;

		NMLTagForeach(pt, *itag)
		{
			MItem *citem = NULL;

					if	(pt->name == "MCamera")
					citem = LoadItemDerived(this, new MCamera, uid_map, pt, SceneIOCamera, load_flag, load_level, tool_mode);
			else	if	(pt->name == "MObject")
					citem = LoadItemDerived(this, new MObject, uid_map, pt, SceneIOObject, load_flag, load_level, tool_mode);
			else	if	(pt->name == "MLight")
					citem = LoadItemDerived(this, new MLight, uid_map, pt, SceneIOLight, load_flag, load_level, tool_mode);
			else	if	((pt->name == "MTrigger") || (pt->name == "Trigger"))	// Legacy
					citem = LoadItemDerived(this, new MTrigger, uid_map, pt, SceneIOTrigger, load_flag, load_level, tool_mode);
			else	if	(pt->name == "Instance")
					citem = LoadItemDerived(this, new Instance, uid_map, pt, SceneIOInstance, load_flag, load_level, tool_mode);
			else	if	(pt->name == "MEmitter")
					citem = LoadItemDerived(this, new MEmitter, uid_map, pt, SceneIOEmitter, load_flag, load_level, tool_mode);
			else	if	(pt->name == "MTerrain")
					citem = LoadItemDerived(this, new MTerrain, uid_map, pt, SceneIOTerrain, load_flag, load_level, tool_mode);
			else	if	(pt->name == "MConstraint")
					citem = LoadItemDerived(this, new MConstraint(physic_world ? physic_world->NewConstraint() : NULL), uid_map, pt, SceneIOConstraint, load_flag, load_level, tool_mode);
			else
					__LOG_W__ << "Unexpected <" << pt->name << "> sub-tag in <Items>.\n";

			if	(citem)
			{
				// Add item to the scene group.
				if	(group && group[0])
					group[0]->Add(citem);

				// Remove from active list if inactive.
				if	(!citem->isActive())
					active_list.Remove(citem);
			}
		}
	}

	// Solve links.
	if	(Tag *ltag = tag.GetTag("Links"))
	{
		NMLTagForeach(pt, *ltag)
		{
			if	(pt->name == "Link")
			{
				int	item = -1, link = -1;

				NMLTagForeach(lt, *pt)
				{
							if	(lt->name == "Item")	item = lt->GetInteger();
					else	if	(lt->name == "Link")	link = lt->GetInteger();
				}

				MItem *sitem = NULL, *litem = NULL;

				if	((item == -1) || (link == -1))
					__LOG_W__ << "Incomplete link tag.\n";
				else
				{
					// Some items might have been masked out from import.
					if	(uid_map.HasKey(item) && uid_map.HasKey(link))
					{
						sitem = ItemFromUid(uid_map[item]);
						litem = ItemFromUid(uid_map[link]);
						if	(sitem && litem)
							sitem->GetBaseItem()->SetParent(litem->GetBaseItem());

//						__LOG__ << "Solving link: " << item << "(" << (sitem ? "OK" : "Failed") << ")->" << link << "(" << (litem ? "OK" : "Failed") << ").\n";
					}
//					else	__LOG_W__ << "Link item masked out during import.\n";
				}
			}
			else
				__LOG_W__ << "Unexpected <" << pt->name << "> sub-tag in <Links>.\n";
		}
	}

	// Solve constraints.
	if	(Tag *ctag = tag.GetTag("Constraints"))
	{
		NMLTagForeach(pt, *ctag)
			if	(pt->name == "Constraint")
			{
				MItem *mitem = GetMappedItemFromTag(*this, pt, "Uid;", uid_map, Type_Constraint);
				if	(!mitem)
					continue;

				// Get constraint.
				if	(MConstraint *constraint = (MConstraint *)mitem)
				{
					MItem *mitem_a = GetMappedItemFromTag(*this, pt, "UidA;", uid_map),
						   *mitem_b = GetMappedItemFromTag(*this, pt, "UidB;", uid_map);

					constraint->desc.item_a = mitem_a;
					constraint->desc.item_b = mitem_b;
				}
			}
	}

	// Solve skins.
	if	(Tag *btag = tag.GetTag("Skins"))
	{
		NMLTagForeach(pt, *btag)
			if	(pt->name == "Skin")
				if	(MItem *mitem = GetMappedItemFromTag(*this, pt, "UId;", uid_map, Type_Object))
				{
					// We can now safely cast to the managed and core objects.
					MObject *mobj = (MObject *)mitem;
					Core::Object *obj = (Core::Object *)mobj;

					uint bone_count = 0;
					NMLTagForeach(st, *pt)
						if	(st->name == "Bone")
							bone_count++;

					if	(!obj->AllocateSkin(bone_count))
						continue;

					// Process skin binding.
					NMLTagForeach(st, *pt)
						if	(st->name == "Bone")
						{
							// Grab mandatory bone tags.
							Tag *itag = st->GetTypedTag("Index", Variant::VariantInteger),
									 *utag = st->GetTypedTag("UId", Variant::VariantInteger);

							if	(!itag || !utag)
							{	__LOG_E__ << "Invalid bone information, incomplete bone tag.\n"; continue;	}

							int index = itag->GetInteger();
							if	((index < 0) || (index >= (int)obj->GetBoneCount()))
							{	__LOG_E__ << "Invalid bone information, bone index out of bound.\n"; continue;	}

							// Locate bone item.
							uint uid = utag->GetUnsigned();
							if	(!uid_map.HasKey(uid))
							{	__LOG_E__ << "Invalid bone information, item uid out of bound.\n"; continue;	}

							MItem *bone = ItemFromUid(uid_map[uid]);
							if	(!bone)
							{	__LOG_E__ << "Invalid bone information, item not found.\n"; continue;	}

							obj->BindBone(index, bone->GetBaseItem());
						}
				}
	}

	// Read groups.
	if	(load_flag & SceneIOGroup)
		if	(Tag *gstag = tag.GetTag("Groups"))
		{
			String _item("Item");

			NMLTagForeach(gtag, *gstag)
			{
				if	(gtag->name == "Group")
				{
					if	(Tag *tid = gtag->GetTag("Id"))
					{
						if	(Group *new_group = new Group)
						{
							new_group->name = tid->GetString();
							group_list.Add(new_group);

							NMLTagForeach(mtag, *gtag)
							{
								if	(mtag->name == _item)
								{
									int uid = mtag->GetInteger();
									if	(uid_map.HasKey(uid))
										new_group->Add(ItemFromUid(uid_map[uid]));
						//			else
							//			__LOG_W__ << "Group member masked out during import.\n";
								}
							}

							// Add as sub-group.
							if	(group)
								group[0]->Add(new_group);
						}
						else
							__LOG_E__ << "Failed to allocate group '" << tid->GetString() << "'.\n";						
					}
					else
						__LOG_W__ << "Could not read group, no identifier found.\n";
				}
			}
		}

	// Script unit.
	if	(load_flag & SceneIOScript)
	{
		if	(Tag *stag = tag.GetTag("ScriptedObject"))
			scripted_object->FromMetaTag(*stag);

	#if	1	// Compatibility
		else
			if	(Tag *stag = tag.GetTag("ScriptUnit"))
				if	(Script::Unit *unit = scripted_object->AddUnit(scripted_object->NewUnit()))
					unit->FromMetaTag(*stag);
	#endif
	}

	// Motion sets.
	if	(load_flag & SceneIOMotion)
		if	(Tag *ctag = tag.GetTag("SceneMotionContainer"))
		{
			if	(group)
			{
				if	(*group)
					(*group)->motion.FromMetaTag(*ctag, &uid_map);
			}
			else
				motion.FromMetaTag(*ctag, &uid_map);
		}

	// Scene properties.
	static String _bgc("BackgroundColor"), _abc("AmbientColor"), _abi("AmbientIntensity"), _tge("TargetExposure"),
				   _feb("FogEnable"), _fgn("FogNear"), _fgf("FogFar"), _fgc("FogColor"), _rad("Radiance"), _ird("Irradiance");

	if	(!group || (load_flag & SceneIOGlobals))
		if	(Tag *gtag = tag.GetTag("Globals"))
			NMLTagForeach(t, *gtag)
			{
						if	(t->name == _bgc)
						background_color.FromMetaTag(*t);
				else	if	(t->name == _abc)
						ambient_color.FromMetaTag(*t);
				else	if	(t->name == _abi)
						ambient_intensity = t->GetReal();
				else	if	(t->name == _rad)
						radiance_probe = t->GetString();
				else	if	(t->name == _ird)
						irradiance_probe = t->GetString();
				else	if	(t->name == _tge)
						target_exposure = t->GetReal();

				else	if	(t->name == _fgn)
						fog_near = t->GetReal();
				else	if	(t->name == _fgf)
						fog_far = t->GetReal();
				else	if	(t->name == _fgc)
						fog_color.FromMetaTag(*t);

				else	if	(t->name == "SkyLayer0")
						skybox_layer[0] = t->GetString();
				else	if	(t->name == "SkyLayer1")
						skybox_layer[1] = t->GetString();
				else	if	(t->name == "SkyShader")
						skybox_shader = t->GetString();

				else	if	(t->name == "TimeOfDay")
						time_of_day = t->GetReal();
			}

	// Physic system.
	if	((load_flag & SceneIOPhysic) && physic_world)
	{
		Tag *ptag = tag.GetTag("Physics;");

		if	(!ptag)
			ptag = tag.GetTag("Tau;");		// Look for a legacy tag.

		if	(ptag)
		{
			NMLTagForeach(t, *ptag)
				if	(t->name == "Frequency")
					physic_world->SetTimestep(1.f / t->GetReal());
		}
	}

	// Group stats.
	if	(group && group[0])
		__LOG__ << "Group '" << group[0]->name << "': " << group[0]->GetItemList().GetCount() << " item(s).\n";

	// Load current camera.
	if	((load_flag & SceneIOSettings) && (load_flag & SceneIOCamera))
		if	(Tag *cctag = tag.GetTag("CurrentCamera"))
		{
			MItem *i = Item(cctag->GetString());
			if	(i && (i->GetItemType() == Type_Camera))
				current_camera = (Camera *)i->GetBaseItem();
		}

	// Query the editor tag if no camera found.
	if	(!current_camera && (tool_mode != ToolEdit))
		if	(MCamera *camera = new MCamera)
		{
			camera->name = "Default";
			AddItem(camera, true);
			current_camera = camera;

			if	(Tag *pt = tag.GetTag("ViewMatrix;"))
			{
				Matrix4 m;
				if	(m.FromMetaTag(*pt))
					camera->SetMatrix(m);
			}
		}

	bench.Stop();
	__LOG__ << "Done loading scene in " << bench.GetMs() << "ms.\n";

	g_scene_messenger.BroadcastMessage(SceneMsg_Loaded, this, 0);
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Scene::FromMetaFileStoreGroup(const char *path, Group **group, uint flag, ToolMode tool_mode, int load_level)
{
	NML::File file;
	if	(!NML::Parser::Load(path, file))
		return false;

	if	(Tag *scene_tag = file.GetTag("Scene"))
	{
		if	(FromMetaTagStoreGroup(*scene_tag, group, flag, tool_mode, load_level))
			name = path;
	}
	else
		__ERR__(__LOG_E__ << "No <Scene> tag in '" << path << "'.\n", false)

	if	(group && group[0])
		group[0]->name = path;

	return true;
}
bool				Scene::IsItemToBeSaved(MItem *i, const Group *group, uint flag) const
{
	if	(!i)
		return false;

	// Filter out irrelevant items.
	if	(group && !group->IsMember(i))
		return false;

	switch (i->GetItemType())
	{
		case Type_Camera:	if	(!(flag & SceneIOCamera)) return false;		break;
		case Type_Object:	if	(!(flag & SceneIOObject)) return false;		break;
		case Type_Light:	if	(!(flag & SceneIOLight)) return false;		break;
		case Type_Trigger:	if	(!(flag & SceneIOTrigger)) return false;	break;
		case Type_Instance:	if	(!(flag & SceneIOInstance)) return false;	break;
		case Type_Emitter:	if	(!(flag & SceneIOEmitter)) return false;	break;
		case Type_Terrain:	if	(!(flag & SceneIOTerrain)) return false;	break;

		default: break;
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Tag					*Scene::LinkInfoAsMetaTag(MItem *i, const Group *group, uint flag) const
{
	if	(!IsItemToBeSaved(i, group, flag))
		return NULL;

	// Grab link managed item.
	Core::Item *link = i->GetBaseItem() ? i->GetBaseItem()->GetParent() : NULL;
	MItem *mlink;
	if	(!link || ((mlink = LocateManagedItem(link)) == NULL))
		return NULL;

	if	(!IsItemToBeSaved(mlink, group, flag))
		return NULL;

	// Save link information.
	if	(Tag *tag = new Tag("Link"))
	{
		tag->AddChild("Item", i->GetUid());
		tag->AddChild("Link", mlink->GetUid());
		return tag;
	}
	__ERR__(__LOG_E__ << "Failed to allocate link tag.\n", NULL)
}
Tag			*Scene::SkinInfoAsMetaTag(MObject *mobj, const Group *group, uint flag) const
{
	if	(group && (!group->IsMember(mobj)))
		return NULL;

	// Go to the low-level core object.
	Core::Object *obj = (Core::Object *)mobj;
	if	(!obj->HasSkin())
		return NULL;

	Tag *skin_tag = new Tag("Skin");
	if	(!skin_tag)
		__ERR__(__LOG_E__ << "Failed to allocate skin tag.\n", NULL);
	skin_tag->AddChild("UId", mobj->GetUid());

	for	(int n = 0; n < (int)obj->GetBoneCount(); ++n)
	{
		MItem *bone = Scene::LocateManagedItem(obj->GetBone(n));
		if	(!bone || (group && (!group->IsMember(bone))))
			continue;

		// Export bone informations.
		if	(Tag *bone_tag = skin_tag->AddChild("Bone"))
		{
			bone_tag->AddChild("Index", n);
			bone_tag->AddChild("UId", bone->GetUid());
			Matrix4 m;
			if	(obj->GetBindMatrix(n, m))
				bone_tag->AddChild(m.AsMetaTag("BindMatrix"));
			else
				__LOG_E__ << "Failed to serialize bone " << n << " bind matrix for object '" << mobj->name << "'.\n";
		}
		else	__LOG_E__ << "Failed to allocate bone tag.\n";
	}
	return skin_tag;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Tag			*Scene::AsMetaTag(const Group *group, uint save_flag, ToolMode tool_mode) const
{
	Tag *root = new Tag("Scene");
	if	(!root)
		__ERR__(__LOG_E__ << "Could not create scene root tag to serialize.\n", NULL);

	// Write script object.
	if	(scripted_object.IsValid() && !group)
		root->AddChild(scripted_object->AsMetaTag());

	// Write motion sets.
	if	(save_flag & SceneIOMotion)
		root->AddChild(motion.AsMetaTag());

	// Write managed items.
	if	(Tag *itag = root->AddChild("Items"))
		ListForeachPtr(MItem *, i, item_list)
		{
			if	(group && !group->IsMember(i))
				continue;

			switch (i->GetItemType())
			{
				case Type_Camera:		if	(!(save_flag & SceneIOCamera)) continue;		break;
				case Type_Object:		if	(!(save_flag & SceneIOObject)) continue;		break;
				case Type_Light:		if	(!(save_flag & SceneIOLight)) continue;			break;
				case Type_Trigger:		if	(!(save_flag & SceneIOTrigger)) continue;		break;
				case Type_Instance:		if	(!(save_flag & SceneIOInstance)) continue;		break;
				case Type_Emitter:		if	(!(save_flag & SceneIOEmitter)) continue;		break;
				case Type_Terrain:		if	(!(save_flag & SceneIOTerrain)) continue;		break;
				case Type_Constraint:	if	(!(save_flag & SceneIOConstraint)) continue;	break;

				default: break;
			}

			itag->AddChild(i->AsMetaTag());
		}
	else
		__ERR__(__LOG_E__ << "Failed to allocate <Items> root tag.\n", NULL);

	// Write link informations.
	if	(Tag *ltag = root->AddChild("Links"))
		ListForeachPtr(MItem *, i, item_list)
			ltag->AddChild(LinkInfoAsMetaTag(i, group, save_flag));
	else
		__ERR__(__LOG_E__ << "Failed to allocate <Links> root tag.\n", NULL)

	// Write skin informations.
	if	(Tag *stag = root->AddChild("Skins"))
	{
		ListForeachPtr(MItem *, i, item_list)
			if	(i->GetItemType() == Type_Object)
				stag->AddChild(SkinInfoAsMetaTag((MObject *)i, group, save_flag));

		if	(stag->GetChildCount() == 0)
		{
			root->RemoveTag(stag);
			_safe_delete(stag);
		}
	}
	else
		__ERR__(__LOG_E__ << "Failed to allocate <Skins> root tag.\n", NULL);

	// Write constraints.
	if	(Tag *constraint_tag = root->AddChild("Constraints"))
		ListForeachPtr(MItem *, i, item_list)
			if	(i->GetItemType() == Type_Constraint)
			{
				if	(group && !group->IsMember(i))
					continue;

				MConstraint *c = (MConstraint *)i;
				if	(Tag *ctag = constraint_tag->AddChild("Constraint"))
				{
					ctag->AddChild("Uid", c->GetUid());
					if	(c->desc.item_a.IsValid())
						ctag->AddChild("UidA", c->desc.item_a->GetUid());
					if	(c->desc.item_b.IsValid())
						ctag->AddChild("UidB", c->desc.item_b->GetUid());
				}
			}

	// Write scene groups.
	if	(!group)
	{
		if	((save_flag & SceneIOGroup) && group_list.GetCount())
		{
			if	(Tag *gstag = root->AddChild("Groups"))
			{
				ListForeachPtr(Group *, group, group_list)
					if	(Tag *gtag = gstag->AddChild("Group"))
					{
						gtag->AddChild("Id", group->name.c_str());
						ListForeachPtr(MItem *, item, group->GetItemList())
							gtag->AddChild("Item", item->GetUid());
					}
			}
			else
				__ERR__(__LOG_E__ << "Failed to allocate <Groups> root tag.\n", NULL);
		}

		// Output scene properties.
		if	(save_flag & SceneIOGlobals)
		{
			if	(Tag *gtag = root->AddChild("Globals"))
			{
				gtag->AddChild(background_color.AsMetaTag("BackgroundColor"));
				gtag->AddChild(ambient_color.AsMetaTag("AmbientColor"));

				if	(!irradiance_probe.IsEmpty())
					gtag->AddChild("Irradiance", irradiance_probe.c_str());
				if	(!radiance_probe.IsEmpty())
					gtag->AddChild("Radiance", radiance_probe.c_str());

				if	(!skybox_layer[0].IsEmpty())
					gtag->AddChild("SkyLayer0", skybox_layer[0].c_str());
				if	(!skybox_layer[1].IsEmpty())
					gtag->AddChild("SkyLayer1", skybox_layer[1].c_str());
				if	(!skybox_shader.IsEmpty())
					gtag->AddChild("SkyShader", skybox_shader.c_str());

				gtag->AddChild("TimeOfDay", time_of_day);

				gtag->AddChild("AmbientIntensity", ambient_intensity);
				gtag->AddChild("TargetExposure", target_exposure);

				gtag->AddChild("FogNear", fog_near);
				gtag->AddChild("FogFar", fog_far);

				gtag->AddChild(fog_color.AsMetaTag("FogColor"));
			}
			else
				__ERR__(__LOG_E__ << "Failed to allocate <Globals> root tag.\n", NULL);
		}

		// Physics.
		if	((save_flag & SceneIOPhysic) && physic_world.IsValid())
			if	(Tag *ptag = root->AddChild("Physics"))
				ptag->AddChild("Frequency", 1.f / physic_world->GetTimestep());

		// Current camera.
		if	(current_camera)
			if	(MItem *ci = LocateManagedItem(current_camera))
				root->AddChild("CurrentCamera", ci->name);
	}
	return root;
}
//------------------------------------------------------------------------------
