/* === S Y N F I G ========================================================= */
/*!	\file synfig/rendering/common/optimizer/optimizerblendassociative.cpp
**	\brief OptimizerBlendAssociative
**
**	\legal
**	......... ... 2015-2018 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 <synfig/general.h>
#include <synfig/localization.h>

#include "optimizerblendassociative.h"

#include "../task/taskblend.h"

#endif

using namespace synfig;
using namespace rendering;

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

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

/* === P R O C E D U R E S ================================================= */

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

OptimizerBlendAssociative::OptimizerBlendAssociative()
{
	category_id = CATEGORY_ID_SPECIALIZED;
	depends_from = CATEGORY_COORDS;
	mode = MODE_REPEAT_PARENT;
	deep_first = true;
	for_task = true;
}

void
OptimizerBlendAssociative::run(const RunParams& params) const
{
	//
	// in some cases we can do this transformation:
	//   blend1(a, blend0(b, c)) -> blend1(blend0(a, b), c)
	// result chain may be converted to list
	//
	// as result actually we will do following:
	//   blend1(a, list(...)) -> list(a, ...)
	// if each task in list suits for this optimization
	//

	int max_count = 50;

	if ( !params.parent
	  || !params.parent->ref_task.type_is<TaskList>()
	  || (int)params.parent->ref_task->sub_tasks.size() >= max_count )
		return;

	max_count -= params.parent->ref_task->sub_tasks.size();

	TaskBlend::Handle blend = TaskBlend::Handle::cast_dynamic(params.ref_task);
	if ( blend
	  && ( !blend->sub_task_a()
		|| blend->sub_task_a()->target_surface == blend->target_surface )
	  && ((1 << blend->blend_method) & Color::BLEND_METHODS_ASSOCIATIVE)
	  && approximate_equal_lp(blend->amount, ColorReal(1.0)) )
	{
		if (TaskList::Handle list = TaskList::Handle::cast_dynamic(blend->sub_task_b()))
		if ((int)list->sub_tasks.size() <= max_count)
		{
			// check each task in list
			bool fix_list = false;
			for(Task::List::iterator i = list->sub_tasks.begin(); i != list->sub_tasks.end(); ++i)
			{
				if (!*i) { fix_list = true; continue; }
				if (TaskBlend::Handle sub_blend = TaskBlend::Handle::cast_dynamic(*i))
					if ( sub_blend->blend_method == blend->blend_method )
						continue;
				if (TaskInterfaceBlendToTarget *interface = i->type_pointer<TaskInterfaceBlendToTarget>()) {
					if (!interface->blend) { fix_list = true; continue; }
					if (interface->blend_method == blend->blend_method )
						continue;
				}
				return;
			}

			Task::Handle new_list = replace_target(blend, list);

			// enable blending for TaskInterfaceBlendToTarget
			if (fix_list)
			{
				if (new_list == list) new_list = list->clone();
				new_list->sub_tasks.insert(new_list->sub_tasks.begin(), blend->sub_task_a());
				for(Task::List::iterator i = new_list->sub_tasks.begin(); i != new_list->sub_tasks.end();)
				{
					if (!*i) { i = new_list->sub_tasks.erase(i); continue; }
					if (!i->type_is<TaskBlend>())
						if (TaskInterfaceBlendToTarget *interface = i->type_pointer<TaskInterfaceBlendToTarget>())
							if (!interface->blend) {
								*i = (*i)->clone();
								interface = i->type_pointer<TaskInterfaceBlendToTarget>();
								interface->blend = true;
								interface->blend_method = blend->blend_method;
								interface->amount = 1.0;
								Task::Handle surface = new TaskSurface();
								surface->assign_target(**i);
								interface->target_subtask() = surface;
								interface->on_target_set_as_source();
							}
					++i;
				}
			}

			// insert sub-task A in front
			if ( blend->sub_task_a()
			 && !blend->sub_task_a().type_is<TaskSurface>() )
			{
				if (new_list == list) new_list = list->clone();
				new_list->sub_tasks.insert(new_list->sub_tasks.begin(), blend->sub_task_a());
			}

			apply(params, new_list);
		}
	}
}

/* === E N T R Y P O I N T ================================================= */
