/* === S Y N F I G ========================================================= */
/*!	\file layer_skeletondeformation.cpp
**	\brief SkeletonDeformation layer
**
**	\legal
**	......... ... 2014 Ivan Mahonin
**
**	This file is part of Synfig.
**
**	Synfig 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.
**
**	Synfig 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.
**
**	You should have received a copy of the GNU General Public License
**	along with Synfig.  If not, see <https://www.gnu.org/licenses/>.
**	\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/localization.h>

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

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

#include <vector>
#include <algorithm>

#endif

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

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.2");

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

Layer_SkeletonDeformation::Layer_SkeletonDeformation():
	Layer_MeshTransform(1.0, Color::BLEND_STRAIGHT),
	param_point1(ValueBase(Point(-4,4))),
	param_point2(ValueBase(Point(4,-4))),
	param_x_subdivisions(32),
	param_y_subdivisions(32)
{
	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 bone pairs. The first value in the pair is the bone in rest position; the second one represents the pose"))
		.set_static(true)
	);

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

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

	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()
{
	rendering::Contour::Handle mask(new rendering::Contour());
	mask->antialias = true;
  
	const std::vector<ValueBase> &list = param_bones.get_list();
	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());
		
		if (approximate_greater_or_equal(r0, length + r1)) {
			mask->arc(p0, r0, 0.0, 2*PI, false);
			mask->close();
		} else
		if (approximate_greater_or_equal(r1, length + r0)) {
			mask->arc(p1, r1, 0.0, 2*PI, false);
			mask->close();
		} else {
			Real dr = r1 - r0;
			Real direction_angle = atan2(direction[1], direction[0]);
			Real da = PI - atan2(sqrt(length*length - dr*dr), dr);
			mask->arc(p0, r0, direction_angle + da, direction_angle + 2*PI - da, false);
			mask->arc(p1, r1, direction_angle - da, direction_angle + da, true);
			mask->close();
		}
	}
	this->mask = mask;
}

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, rendering::Mesh::Triangle> &a,
		const std::pair<Real, rendering::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;

	rendering::Mesh::Handle mesh(new rendering::Mesh());

	// 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 = from_bone * into_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( rendering::Mesh::Vertex(
			average_position, i->initial_position ));
	}

	// build triangles
	std::vector< std::pair<Real, rendering::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, rendering::Mesh::Triangle(v[0], v[1], v[3])));
				triangles.push_back(std::make_pair(depth, rendering::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, rendering::Mesh::Triangle> >::iterator i = triangles.begin(); i != triangles.end(); ++i)
		mesh->triangles.push_back(i->second);

	prepare_mask();
	this->mesh = mesh;
}

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);
}

