/* === S Y N F I G ========================================================= */
/*!	\file layer_skeletondeformation.cpp
**	\brief SkeletonDeformation layer
**
**	$Id$
**
**	\legal
**	......... ... 2014 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**	\endlegal
*/
/* ========================================================================= */

/* === H E A D E R S ======================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "layer_skeletondeformation.h"

#include <synfig/general.h>
#include <synfig/localization.h>

#include <synfig/canvas.h>
#include <synfig/context.h>
#include <synfig/paramdesc.h>
#include <synfig/string.h>
#include <synfig/time.h>
#include <synfig/value.h>
#include <synfig/valuenode.h>

#include <synfig/rendering/common/task/taskblend.h>
#include <synfig/rendering/common/task/tasklayer.h>

#include <vector>
#include <map>
#include <algorithm>

#endif

/* === U S I N G =========================================================== */

using namespace etl;
using namespace std;
using namespace synfig;

/* === M A C R O S ========================================================= */

/* === C L A S S E S ======================================================= */

/* === G L O B A L S ======================================================= */

SYNFIG_LAYER_INIT(Layer_SkeletonDeformation);
SYNFIG_LAYER_SET_NAME(Layer_SkeletonDeformation,"skeleton_deformation");
SYNFIG_LAYER_SET_LOCAL_NAME(Layer_SkeletonDeformation,N_("Skeleton Deformation"));
SYNFIG_LAYER_SET_CATEGORY(Layer_SkeletonDeformation,N_("Distortions"));
SYNFIG_LAYER_SET_VERSION(Layer_SkeletonDeformation,"0.1");
SYNFIG_LAYER_SET_CVS_ID(Layer_SkeletonDeformation,"$Id$");

/* === M E T H O D S ======================================================= */

Layer_SkeletonDeformation::Layer_SkeletonDeformation():
	param_point1(ValueBase(Point(-4,4))),
	param_point2(ValueBase(Point(4,-4))),
	param_x_subdivisions(32),
	param_y_subdivisions(32)
{
	max_texture_scale = 1.f;
	param_bones.set_list_of(std::vector<BonePair>(1));

	SET_INTERPOLATION_DEFAULTS();
	SET_STATIC_DEFAULTS();
}

Layer_SkeletonDeformation::~Layer_SkeletonDeformation()
{
}

String
Layer_SkeletonDeformation::get_local_name()const
{
	String s = Layer_MeshTransform::get_local_name();
	return s.empty() ? _("Skeleton Deformation") : '[' + s + ']';
}

Layer::Vocab
Layer_SkeletonDeformation::get_param_vocab()const
{
	Layer::Vocab ret(Layer_MeshTransform::get_param_vocab());

	ret.push_back(ParamDesc("bones")
		.set_local_name(_("Bones"))
		.set_description(_("List of bones"))
		.set_static(true)
	);

	ret.push_back(ParamDesc("point1")
		.set_local_name(_("Point 1"))
		.set_box("point2")
		.set_description(_("First corner of the bounds rectangle"))
	);

	ret.push_back(ParamDesc("point2")
		.set_local_name(_("Point 2"))
		.set_description(_("Second corner of the bounds rectangle"))
	);

	ret.push_back(ParamDesc("x_subdivisions")
		.set_local_name(_("Horizontal subdivisions"))
		.set_description(_("Count of horizontal subdivisions of the transformation grid"))
	);

	ret.push_back(ParamDesc("y_subdivisions")
		.set_local_name(_("Vertical subdivisions"))
		.set_description(_("Count of vertical subdivisions of the transformation grid"))
	);

	return ret;
}

void
Layer_SkeletonDeformation::prepare_mask()
{
 	const std::vector<ValueBase> &list = param_bones.get_list();

	static const Real precision = 0.000000001;
	int segments_count = 64;
	Real segment_angle = 2*PI/(Real)segments_count;

	mask.clear();
	for(std::vector<ValueBase>::const_iterator i = list.begin(); i != list.end(); ++i)
 	{
		if (!i->same_type_as(BonePair())) continue;
 		const BonePair &bonePair = i->get(BonePair());
 		const Bone &bone = bonePair.first;
 		Matrix matrix = bone.get_animated_matrix();
 		Vector origin = matrix.get_transformed(Vector(0.0, 0.0));
 		Vector direction = matrix.get_transformed(Vector(1.0, 0.0), false).norm();
 		Real length = bone.get_length() * bone.get_scalelx();

 		if (length < 0) {
 			length *= -1;
 			direction *= -1;
 		}

 		const Vector &p0 = origin;
 		const Vector p1 = origin + direction * length;

 		Real r0 = fabs(bone.get_width());
 		Real r1 = fabs(bone.get_tipwidth());
 		Real direction_angle = atan2(direction[1], direction[0]);

 		Real angle0_base = length - precision > fabs(r1 - r0)
 				         ? acos((r0 - r1)/length)
 				         : (r0 > r1 ? 0.0 : PI);
 		Real angle1_base = PI - angle0_base;

 		int segments_count0 = (int)round(2*angle1_base / segment_angle);
 		Real segment_angle0 = 2*angle1_base / (Real)segments_count0;

 		int segments_count1 = (int)round(2*angle0_base / segment_angle);
 		Real segment_angle1 = 2*angle0_base / (Real)segments_count1;

 		// add vertices
 		int first = (int)mask.vertices.size();
 		mask.vertices.reserve(first + segments_count0 + segments_count1 + 2);

 		int j = 0;
		Real angle = direction_angle + angle0_base;
		while(true)
		{
			mask.vertices.push_back( Point(r0*cos(angle) + p0[0], r0*sin(angle) + p0[1]) );
			if (j++ >= segments_count0) break; else angle += segment_angle0;
		}
		j = 0;
		while(true)
		{
			mask.vertices.push_back( Point(r1*cos(angle) + p1[0], r1*sin(angle) + p1[1]) );
			if (j++ >= segments_count1) break; else angle += segment_angle1;
		}

		// add triangles
		for(int i = first+2; i < (int)mask.vertices.size(); ++i)
			mask.triangles.push_back(Polygon::Triangle(first, i-1, i));
 	}
}

struct Layer_SkeletonDeformation::GridPoint {
	Vector initial_position;
	Vector summary_position;
	Real summary_depth;
	Real summary_weight;
	Real average_depth;
	bool used;

	inline GridPoint():
		summary_depth(0.0), summary_weight(0.0), average_depth(0.0), used(false) { }
	inline explicit GridPoint(const Vector &initial_position):
		initial_position(initial_position), summary_depth(0.0), summary_weight(0.0), average_depth(0.0), used(false) { }
	static bool compare_triagles(const std::pair<Real, Mesh::Triangle> &a, const std::pair<Real, Mesh::Triangle> &b)
	{
		return a.first < b.first ? false
			 : b.first < a.first ? true
			 : a.second.vertices[0] < b.second.vertices[0] ? true
			 : b.second.vertices[0] < a.second.vertices[0] ? false
			 : a.second.vertices[1] < b.second.vertices[1] ? true
			 : b.second.vertices[1] < a.second.vertices[1] ? false
			 : a.second.vertices[2] < b.second.vertices[2];
	}
};

Real Layer_SkeletonDeformation::distance_to_line(const Vector &p0, const Vector &p1, const Vector &x)
{
	const Real epsilon = 1e-10;

	Real distance_to_p0 = (x - p0).mag();
	Real distance_to_p1 = (x - p1).mag();
	Real distance_to_line = INFINITY;

	Vector line = p1 - p0;
	Real line_length = line.mag();
	if (line_length > epsilon)
	{
		Real dist = fabs((x - p0) * line.perp() / line_length);
		Real pos = (x - p0) * line / line_length;
		if (pos > 0.0 && pos < line_length)
			distance_to_line = dist;
	}

	return std::min(distance_to_line, std::min(distance_to_p0, distance_to_p1) );
}

void
Layer_SkeletonDeformation::prepare_mesh()
{
	static const Real precision = 1e-10;

	mesh.clear();

	// TODO: build grid with dynamic size

	const Point grid_p0 = param_point1.get(Point());
	const Point grid_p1 = param_point2.get(Point());
	const int grid_side_count_x = std::max(1, param_x_subdivisions.get(int())) + 1;
	const int grid_side_count_y = std::max(1, param_y_subdivisions.get(int())) + 1;

	const Real grid_step_x = (grid_p1[0] - grid_p0[0]) / (Real)(grid_side_count_x - 1);
	const Real grid_step_y = (grid_p1[1] - grid_p0[1]) / (Real)(grid_side_count_y - 1);
	const Real grid_step_diagonal = sqrt(grid_step_x*grid_step_x + grid_step_y*grid_step_y);

	// build grid
	std::vector<GridPoint> grid;
	grid.reserve(grid_side_count_x * grid_side_count_y);
	for(int j = 0; j < grid_side_count_y; ++j)
		for(int i = 0; i < grid_side_count_x; ++i)
			grid.push_back(GridPoint(Vector(
				grid_p0[0] + i*grid_step_x,
				grid_p0[1] + j*grid_step_y )));

	// apply deformation
	if (param_bones.can_get(ValueBase::List()))
	{
		const ValueBase::List &bones = param_bones.get_list();
		for(ValueBase::List::const_iterator i = bones.begin(); i != bones.end(); ++i)
		{
			if (i->can_get(BonePair()))
			{
				const BonePair &bone_pair = i->get(BonePair());
				Bone::Shape shape0 = bone_pair.first.get_shape();
				Bone::Shape shape1 = bone_pair.second.get_shape();
				Bone::Shape expandedShape0 = shape0;
				expandedShape0.r0 += 2.0*grid_step_diagonal;
				expandedShape0.r1 += 2.0*grid_step_diagonal;
				Real depth = bone_pair.second.get_depth();

				Matrix into_bone(
					shape0.p1[0] - shape0.p0[0], shape0.p1[1] - shape0.p0[1], 0.0,
					shape0.p0[1] - shape0.p1[1], shape0.p1[0] - shape0.p0[0], 0.0,
					shape0.p0[0], shape0.p0[1], 1.0
				);
				into_bone.invert();
				Matrix from_bone(
					shape1.p1[0] - shape1.p0[0], shape1.p1[1] - shape1.p0[1], 0.0,
					shape1.p0[1] - shape1.p1[1], shape1.p1[0] - shape1.p0[0], 0.0,
					shape1.p0[0], shape1.p0[1], 1.0
				);
				Matrix matrix = into_bone * from_bone;

				for(std::vector<GridPoint>::iterator j = grid.begin(); j != grid.end(); ++j)
				{
					Real percent = Bone::distance_to_shape_center_percent(expandedShape0, j->initial_position);
					if (percent > precision) {
						Real distance = distance_to_line(shape0.p0, shape0.p1, j->initial_position);
						if (distance < precision) distance = precision;
						Real weight =
							percent/(distance*distance);
							// 1.0/distance;
							// 1.0/(distance*distance);
							// 1.0/(distance*distance*distance);
							// exp(-4.0*distance);
						j->summary_position += matrix.get_transformed(j->initial_position) * weight;
						j->summary_depth += depth * weight;
						j->summary_weight += weight;
						j->used = true;
					}
				}
			}
		}
	}

	// build vertices
	mesh.vertices.reserve(grid.size());
	for(std::vector<GridPoint>::iterator i = grid.begin(); i != grid.end(); ++i) {
		Vector average_position = i->summary_weight > precision ? i->summary_position/i->summary_weight : i->initial_position;
		i->average_depth = i->summary_weight > precision ? i->summary_depth/i->summary_weight : 0.0;
		mesh.vertices.push_back(Mesh::Vertex(average_position, i->initial_position));
	}

	// build triangles
	std::vector< std::pair<Real, Mesh::Triangle> > triangles;
	triangles.reserve(2*(grid_side_count_x-1)*(grid_side_count_y-1));
	for(int j = 1; j < grid_side_count_y; ++j)
	{
		for(int i = 1; i < grid_side_count_x; ++i)
		{
			int v[] = {
				(j-1)*grid_side_count_x + (i-1),
				(j-1)*grid_side_count_x +  i,
				 j   *grid_side_count_x +  i,
				 j   *grid_side_count_x + (i-1),
			};
			if (grid[v[0]].used && grid[v[1]].used && grid[v[2]].used && grid[v[3]].used)
			{
				Real depth = 0.25*(grid[v[0]].average_depth
						         + grid[v[1]].average_depth
								 + grid[v[2]].average_depth
								 + grid[v[3]].average_depth);
				triangles.push_back(std::make_pair(depth, Mesh::Triangle(v[0], v[1], v[3])));
				triangles.push_back(std::make_pair(depth, Mesh::Triangle(v[1], v[2], v[3])));
			}
		}
	}

	// sort triangles
	std::sort(triangles.begin(), triangles.end(), GridPoint::compare_triagles);
	mesh.triangles.reserve(triangles.size());
	for(std::vector< std::pair<Real, Mesh::Triangle> >::iterator i = triangles.begin(); i != triangles.end(); ++i)
		mesh.triangles.push_back(i->second);

	prepare_mask();
	update_mesh_and_mask();
}

bool
Layer_SkeletonDeformation::set_param(const String & param, const ValueBase &value)
{
    IMPORT_VALUE_PLUS(param_bones, prepare_mesh());
	IMPORT_VALUE_PLUS(param_point1, prepare_mesh());
	IMPORT_VALUE_PLUS(param_point2, prepare_mesh());
	IMPORT_VALUE_PLUS(param_x_subdivisions, prepare_mesh());
	IMPORT_VALUE_PLUS(param_y_subdivisions, prepare_mesh());
    return Layer_MeshTransform::set_param(param,value);
}

ValueBase
Layer_SkeletonDeformation::get_param(const String& param)const
{
	EXPORT_VALUE(param_bones);
	EXPORT_VALUE(param_point1);
	EXPORT_VALUE(param_point2);
	EXPORT_VALUE(param_x_subdivisions);
	EXPORT_VALUE(param_y_subdivisions);

	EXPORT_NAME();
	EXPORT_VERSION();

	return Layer_MeshTransform::get_param(param);
}

rendering::Task::Handle
Layer_SkeletonDeformation::build_rendering_task_vfunc(Context context)const
{
	// TODO: This is not thread-safe
	rendering::TaskLayer::Handle task = new rendering::TaskLayer();
	task->layer = const_cast<Layer_SkeletonDeformation*>(this);
	task->layer->set_canvas(get_canvas());
	task->sub_task() = context.build_rendering_task();
	return task;
}

