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


		#include "tools/scene_merge_object_list.h"
		#include "tools/geometry_merge.h"
		#include "scene3d/scene.h"
		#include "scene3d/mobject.h"
		#include "physic/physic_world.h"
		#include "core/graphic_resource_factory.h"

		using namespace GS;
		using namespace GS::Core;
		using namespace GS::S3D;


//------------------------------------------------------------------------------
struct	ItemTransform
{
	sMItem		i;
	Matrix4	m;

	ItemTransform(MItem *_i, const Matrix4 &_m) : i(_i), m(_m) {}
};
struct	GeometryTransform
{
	sMItem		i;
	Matrix4	m;
	sGeometry	g;

	GeometryTransform(MItem *_i, Geometry *_g, const Matrix4 &_m) : i(_i), m(_m), g(_g) {}
};
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static	void	MergePhysicShape(MItem *to, MItem *from)
{
	if	(!from->physic_item)
		return;

	ListForeachPtr(PhysicShape *, shape, from->physic_item_desc.shape_list)
		if	(PhysicShape *new_shape = new PhysicShape)
		{
			new_shape->SetMatrix(from->GetBaseItem()->GetMatrix() * shape->GetMatrix());
			new_shape->mass = shape->mass;

			switch (shape->GetType())
			{
				case PhysicShape::TypeNone:
					break;

				case PhysicShape::TypeHeightmap:
					__LOG_W__ << "Merge heightmap physic shape STUB.\n";
					break;

				case PhysicShape::TypeBox:
				case PhysicShape::TypeCapsule:
				case PhysicShape::TypeCylinder:
				case PhysicShape::TypeCone:
				case PhysicShape::TypeSphere:
					new_shape->Set(shape->GetType(), shape->dimensions);
					break;

				case PhysicShape::TypeConvex:
				case PhysicShape::TypeMesh:
					new_shape->Set(shape->GetType(), shape->path);
					break;
			}

			to->physic_item_desc.shape_list.Add(new_shape);
		}

	to->physic_item_desc.physic_mode = from->physic_item_desc.physic_mode;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool			SceneMergeObjectList::Merge(Scene *scene, const SharedList <MItem *> &item_list, Core::ResourceFactory &gf, sMItem &out_item, sGeometry &out_geo)
{
	// Unlink all children, store matrix beforehand.
	AutoList <ItemTransform *>	child_list;

	ListForeachPtr(MItem *, item, item_list)
		ListForeachPtr(Item *, child, item->GetBaseItem()->GetChildren())
		{
			MItem *cchild = Scene::LocateManagedItem(child);

			// Do not store item if it is going to be merged.
			if	(!item_list.Find(cchild))
				child_list.Add(new ItemTransform(cchild, cchild->GetBaseItem()->GetMatrix()));
		}
	
	Vector4 average_vec(0.0,0.0,0.0);
	int counter_average = 0;
	ListForeachPtr(MItem *, item, item_list)
		if	(item->GetItemType() == Type_Object)
		{
			++counter_average;
			average_vec += item->GetBaseItem()->GetPosition();
		}

	average_vec /= counter_average;

	ListForeachPtr(MItem *, item, item_list)
		if	(item->GetItemType() == Type_Object)
			item->GetBaseItem()->SetPosition(item->GetBaseItem()->GetPosition() - average_vec);

	// Build merge list.
	AutoList <GeometryTransform *> geo_list;
	ListForeachPtr(MItem *, item, item_list)
		if	(item->GetItemType() == Type_Object)
			geo_list.Add(new GeometryTransform(item, gf.LoadGeometry(((MObject *)item)->geometry), item->GetBaseItem()->GetMatrix()));

	if	(geo_list.GetCount() < 2)
		return true; // nothing to be done

	// Count total steps.
	int total_step_count = 0;
	for	(int count = item_list.GetCount(); count > 1; count /= 2)
		total_step_count += count;

	// Merge list.
	AutoList <GeometryTransform *> tgt_list, *in_list = &geo_list, *out_list = &tgt_list;

	int step_count = 0;
	for (bool running = true; (in_list->GetCount() > 1) && running; )
	{
		for	(uint n = 0; (n < in_list->GetCount()) && running; n += 2)
		{
			if	(n == (in_list->GetCount() - 1))
			{
				GeometryTransform *t = (*in_list)[n];
				out_list->Add(new GeometryTransform(t->i, t->g, t->m));
				continue;	// nothing to merge
			}

			// Merge geometries.
			GeometryTransform *left = (*in_list)[n], *right = (*in_list)[n + 1];
			sGeometry merged_geometry(MergeGeometry(left->g, right->g, &left->m, &right->m));
			if	(merged_geometry.IsNull())
				continue;

			// Merge physic shapes.
			MObject *merged_object = new MObject;
			scene->SetupItemComponents(merged_object);
			MergePhysicShape(merged_object, left->i);
			MergePhysicShape(merged_object, right->i);

			out_list->Add(new GeometryTransform(merged_object, merged_geometry, Matrix4::IdentityMatrix()));

			running = OnProgress(++step_count, total_step_count);
		}
		in_list->Clear();

		AutoList <GeometryTransform *> *tmp_list = in_list; in_list = out_list; out_list = tmp_list;
	}
	if	(in_list->GetCount() != 1)
		return false;

	// Remove merged items from scene.
	ListForeachPtr(MItem *, item, item_list)
		if	(item->GetItemType() == Type_Object)
			scene->RemoveItem(item);

	// Store output...
	out_item = (*in_list)[0]->i;	
	out_geo = (*in_list)[0]->g;

	out_item->GetBaseItem()->SetPosition(average_vec);

	out_item->name = "Merged Object";
	scene->AddItem(out_item, false);

	// ...restore parenting.
	ListForeachPtr(ItemTransform *, t, child_list)
	{
		t->i->GetBaseItem()->SetParent(out_item->GetBaseItem());
		t->i->GetBaseItem()->SnapshotTransformation(t->m);
	}
	child_list.Clear();

	return true;
}
//------------------------------------------------------------------------------
