/* === S Y N F I G ========================================================= */
/*!	\file synfig/renddesc.cpp
**	\brief Class that defines the parameters needed by the Renderer to
* render a context to a surface.
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2008 Chris Moore
**
**	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 "renddesc.h"
#include <synfig/misc.h>

#endif

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

using namespace synfig;

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

#undef FLAGS
#define FLAGS(x,y)		(((x)&(y))==(y))

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

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

const Color &
RendDesc::get_bg_color()const
{
	return background;
}

RendDesc &
RendDesc::set_bg_color(const Color &bg)
{
	background=bg; return *this;
}

Real
RendDesc::get_physical_w()const
{
	return (Real)get_w()/get_x_res();
}

Real
RendDesc::get_physical_h()const
{
	return (Real)get_h()/get_y_res();
}

RendDesc&
RendDesc::set_physical_w(Real w)
{
	set_w(round_to_int(w*get_x_res()));
	return *this;
}

RendDesc&
RendDesc::set_physical_h(Real h)
{
	set_h(round_to_int(h*get_y_res()));
	return *this;
}

int
RendDesc::get_w()const
{
	return w_;
}

RendDesc &
RendDesc::set_w(int x)
{
	if(FLAGS(flags,LINK_IM_ASPECT)) // "Width and Height ratio"
	{
		int new_h = h_ratio_*x/w_ratio_;
		if (new_h == 0) new_h = 1; // default to 1 like our GUI Synfig Studio
		if(FLAGS(flags,PX_ASPECT))
		{
			br_[1]-=focus[1];
			br_[1]=h_?br_[1]/h_*new_h:0.0;
			br_[1]+=focus[1];
			tl_[1]-=focus[1];
			tl_[1]=h_?tl_[1]/h_*new_h:0.0;
			tl_[1]+=focus[1];

			br_[0]-=focus[0];
			br_[0]=w_?br_[0]/w_*x:0.0;
			br_[0]+=focus[0];
			tl_[0]-=focus[0];
			tl_[0]=w_?tl_[0]/w_*x:0.0;
			tl_[0]+=focus[0];
		}
		h_=new_h;
		w_=x;

		return *this;
	}

	if(FLAGS(flags,LINK_PX_ASPECT)) // never set
	{
		h_=w_?h_*x/w_:0.0;
		w_=x;
	}
	else if(FLAGS(flags,LINK_PX_AREA)) // never set
	{
		//! \writeme
		w_=x;
	}
	else if(FLAGS(flags,PX_ASPECT)) // "Pixel Aspect"
	{
		Vector d=br_-tl_;
		float old_span=get_span();

		// If we should preserve image width
		if(		FLAGS(flags,IM_W)							// "Image Width"
			|| (FLAGS(flags,IM_ZOOMIN)  && x && d[1]>d[1]/x*w_)	// never set
			|| (FLAGS(flags,IM_ZOOMOUT) && x && d[1]<d[1]/x*w_)) // never set
		{
			br_[1]-=focus[1];
			br_[1]=x?br_[1]/x*w_:0.0;
			br_[1]+=focus[1];
			tl_[1]-=focus[1];
			tl_[1]=x?tl_[1]/x*w_:0.0;
			tl_[1]+=focus[1];
		} else
		{
			br_[0]-=focus[0];
			br_[0]=w_?br_[0]/w_*x:0.0;
			br_[0]+=focus[0];
			tl_[0]-=focus[0];
			tl_[0]=w_?tl_[0]/w_*x:0.0;
			tl_[0]+=focus[0];
		}

		w_=x;

		if(FLAGS(flags,IM_SPAN)) // "Image Span"
			set_span(old_span);
	}
	else if(FLAGS(flags,PX_AREA)) // never set
	{
		//! \writeme
		w_=x;
	}
	else
		w_=x;

	return *this;
}

int
RendDesc::get_h()const
{
	return h_;
}

RendDesc &
RendDesc::set_h(int y)
{
	if(FLAGS(flags,LINK_IM_ASPECT)) // "Width and Height ratio"
	{
		int new_w = w_ratio_*y/h_ratio_;
		if (new_w == 0) new_w = 1; // default to 1 like our GUI Synfig Studio
		if(FLAGS(flags,PX_ASPECT))
		{
			br_[0]-=focus[0];
			br_[0]=w_?br_[0]/w_*new_w:0.0;
			br_[0]+=focus[0];
			tl_[0]-=focus[0];
			tl_[0]=w_?tl_[0]/w_*new_w:0.0;
			tl_[0]+=focus[0];

			br_[1]-=focus[1];
			br_[1]=h_?br_[1]/h_*y:0.0;
			br_[1]+=focus[1];
			tl_[1]-=focus[1];
			tl_[1]=h_?tl_[1]/h_*y:0.0;
			tl_[1]+=focus[1];
		}
		w_=new_w;
		h_=y;

		return *this;
	}

	if(FLAGS(flags,LINK_PX_ASPECT)) // never set
	{
		w_=h_?w_*y/h_:0.0;
		h_=y;
	}
	else if(FLAGS(flags,LINK_PX_AREA)) // never set
	{
		//! \writeme
		h_=y;
	}
	else if(FLAGS(flags,PX_ASPECT)) // "Pixel Aspect"
	{
		Vector d=br_-tl_;
		float old_span=get_span();

		// If we should preserve image width
		if(		FLAGS(flags,IM_W)							// "Image Width"
			|| (FLAGS(flags,IM_ZOOMIN)  && y && d[0]>d[0]/y*h_)	// never set
			|| (FLAGS(flags,IM_ZOOMOUT) && y && d[0]<d[0]/y*h_)) // never set
		{
			br_[0]-=focus[0];
			br_[0]=y?br_[0]/y*h_:0.0;
			br_[0]+=focus[0];
			tl_[0]-=focus[0];
			tl_[0]=y?tl_[0]/y*h_:0.0;
			tl_[0]+=focus[0];
		} else
		{
			br_[1]-=focus[1];
			br_[1]=h_?br_[1]/h_*y:0.0;
			br_[1]+=focus[1];
			tl_[1]-=focus[1];
			tl_[1]=h_?tl_[1]/h_*y:0.0;
			tl_[1]+=focus[1];
		}

		h_=y;

		if(FLAGS(flags,IM_SPAN)) // "Image Span"
			set_span(old_span);
	}
	else if(FLAGS(flags,PX_AREA)) // never set
	{
		//! \writeme
		h_=y;
	}
	else
		h_=y;

	return *this;
}

RendDesc &
RendDesc::set_wh(int x, int y)
{
	// FIXME: This is a working hack...
	set_w(x);
	set_h(y);

	return *this;
}

Real
RendDesc::get_x_res()const
{
	return x_res;
}

RendDesc &
RendDesc::set_x_res(Real x)
{
	if(FLAGS(flags,LINK_RES)) // "Resolution ratio"
	{
		y_res = y_res_ratio_*x/x_res_ratio_;
	}

	x_res=x; return *this;
}

Real
RendDesc::get_y_res()const
{
	return y_res;
}

RendDesc &
RendDesc::set_y_res(Real y)
{
	if(FLAGS(flags,LINK_RES)) // "Resolution ratio"
	{
		x_res = x_res_ratio_*y/y_res_ratio_;
	}

	y_res=y; return *this;
}

int
RendDesc::get_frame_start()const
{
	return round_to_int(time_begin*frame_rate);
}

RendDesc &
RendDesc::set_frame_start(int x)
{
	return set_time_start(Time(x)/frame_rate);
}

int
RendDesc::get_frame_end()const
{
	return round_to_int(time_end*frame_rate);
}

RendDesc &
RendDesc::set_frame_end(int x)
{
	return set_time_end(Time(x)/frame_rate);
}


const Time
RendDesc::get_time_start()const
{
	return time_begin;
}

RendDesc &
RendDesc::set_time_start(Time x)
{
	if(x>time_end)
		time_begin=time_end=x;
	else
		time_begin=x;
	return *this;
}


const Time
RendDesc::get_time_end()const
{
	return time_end;
}

RendDesc &
RendDesc::set_time_end(Time x)
{
	if(x<time_begin)
		time_end=time_begin=x;
	else
		time_end=x;
	return *this;
}

RendDesc &
RendDesc::set_time(Time x)
{
	time_end=time_begin=x;
	return *this;
}

RendDesc &
RendDesc::set_frame(int x)
{
	return set_time(Time(x)/frame_rate);
}

const float &
RendDesc::get_frame_rate()const
{
	return frame_rate;
}

RendDesc &
RendDesc::set_frame_rate(float x)
{
	frame_rate=x;
	return *this;
}

const bool &
RendDesc::get_interlaced()const
{
	return interlaced;
}

RendDesc &
RendDesc::set_interlaced(bool x)
{ interlaced=x; return *this; }

//! Return the status of the clamp flag
const bool &
RendDesc::get_clamp()const
{ return clamp; }

//! Set the clamp flag
RendDesc &
RendDesc::set_clamp(bool x)
{ clamp=x; return *this; }

//! Return the status of the render_excluded_contexts flag
const bool &
RendDesc::get_render_excluded_contexts()const
{ return render_excluded_contexts; }

//! Set the render_excluded_contexts flag
RendDesc &
RendDesc::set_render_excluded_contexts(bool x)
{ render_excluded_contexts=x; return *this; }

//! Set constraint flags
RendDesc &
RendDesc::set_flags(const int &x)
{ flags=x; return *this; }

//! Clear constraint flags
RendDesc &
RendDesc::clear_flags()
{ flags=0; return *this; }

int
RendDesc::get_flags()const
{ return flags; }


//!	Return the aspect ratio of a single pixel
Real
RendDesc::get_pixel_aspect()const
{
	if (!w_ || !h_) return 1.0;
	Vector tmp=br_-tl_;
	tmp[0]/=tmp[0];
	tmp[1]/=tmp[1];
	tmp[0]/=tmp[1];
	if(tmp[0]<0.0)
		return -tmp[0];
	return tmp[0];
}

//!	Return the aspect ratio of the entire image
Real
RendDesc::get_image_aspect()const
{
	Point tmp=br_-tl_;
	tmp[0]/=tmp[1];
	if(tmp[0]<0.0)
		return -tmp[0];
	return tmp[0];
}


//! Affect the pixel ratio for LINK_IM_ASPECT flag
void
RendDesc::set_pixel_ratio(const int &x, const int &y)
{
	w_ratio_ = x;
	h_ratio_ = y;
}

//! Get the reduced pixel ratio (based on euclide reduction)
void
RendDesc::get_pixel_ratio_reduced(int &w_ratio_reduced, int &h_ratio_reduced)
{
	int w = w_;
	int h = h_;
	int last_rem = h_;
	int bigger_commun_div;

	div_t dv;

	if(!w_ || !h_)
	{
		w_ratio_reduced = h_ratio_reduced = 0;
		return;
	}

	if(w_ == h_)
	{
		w_ratio_reduced = h_ratio_reduced = 1;
		return;
	}

	while (last_rem != 0)
	{
		dv = div(w, h);
		w = h;
		bigger_commun_div = last_rem;
		last_rem = h = dv.rem;
	}

	w_ratio_reduced = w_ / bigger_commun_div;
	h_ratio_reduced = h_ / bigger_commun_div;
}

//! Affect the resolution ratio for LINK_RES flag
void
RendDesc::set_res_ratio(const Real &x, const Real &y)
{
	x_res_ratio_ = x;
	y_res_ratio_ = y;
}

//! Return the antialias amount
const int &
RendDesc::get_antialias()const
{ return a; }

//! Set the antialias amount
RendDesc &
RendDesc::set_antialias(const int &x)
{ a=x; return *this; }


//! Return the distance from the bottom-right to the top-left
Real
RendDesc::get_span()const
{
	return (br_-tl_).mag();
}

//! Set the span distance
RendDesc &
RendDesc::set_span(const Real &x)
{
	Vector::value_type ratio=x/get_span();

	//! \todo this looks wrong.  I suspect the intention was to check
	//		  "(not IM_W) AND (not IM_H)", ie "not(IM_W OR IM_H)" but
	//		  this check does "not(IM_W AND IM_H)"
	if(!FLAGS(flags,IM_W|IM_H) || FLAGS(flags,IM_ASPECT)) // (not "Image Width") or (not "Image Height") or "Image Aspect"
	{
		br_-=focus;
		br_=br_*ratio;
		br_+=focus;
		tl_-=focus;
		tl_=tl_*ratio;
		tl_+=focus;
	}
	else if(FLAGS(flags,IM_W))	// "Image Width"
	{
		//! \writeme or fix me
		br_-=focus;
		br_=br_*ratio;
		br_+=focus;
		tl_-=focus;
		tl_=tl_*ratio;
		tl_+=focus;
	}else // IM_H				// "Image Height"
	{
		//! \writeme or fix me
		br_-=focus;
		br_=br_*ratio;
		br_+=focus;
		tl_-=focus;
		tl_=tl_*ratio;
		tl_+=focus;
	}

	return *this;
}


const Point &
RendDesc::get_focus()const
{ return focus; }

RendDesc &
RendDesc::set_focus(const Point &x)
{ focus=x; return *this; }


const Point &
RendDesc::get_tl()const
{ return tl_; }

const Point &
RendDesc::get_br()const
{ return br_; }

RendDesc &
RendDesc::set_tl(const Point &x)
{
	if(FLAGS(flags,PX_ASPECT)) // "Pixel Aspect"
	{
		Vector new_size(x-br_);
		new_size[0]=std::fabs(new_size[0]);
		new_size[1]=std::fabs(new_size[1]);

		Vector old_size(tl_-br_);
		old_size[0]=std::fabs(old_size[0]);
		old_size[1]=std::fabs(old_size[1]);

		if(new_size[0]!=old_size[0])
			w_=round_to_int(new_size[0]*w_/old_size[0]);

		if(new_size[1]!=old_size[1])
			h_=round_to_int(new_size[1]*h_/old_size[1]);
	}

	tl_=x; return *this;
}

RendDesc &
RendDesc::set_br(const Point &x)
{
	if(FLAGS(flags,PX_ASPECT)) // "Pixel Aspect"
	{
		Vector new_size(x-tl_);
		new_size[0]=std::fabs(new_size[0]);
		new_size[1]=std::fabs(new_size[1]);

		Vector old_size(tl_-br_);
		old_size[0]=std::fabs(old_size[0]);
		old_size[1]=std::fabs(old_size[1]);

		if(new_size[0]!=old_size[0])
			w_=round_to_int(new_size[0]*w_/old_size[0]);

		if(new_size[1]!=old_size[1])
			h_=round_to_int(new_size[1]*h_/old_size[1]);
	}
	br_=x; return *this;
}

RendDesc &
RendDesc::set_tl_br(const Point &x, const Point &y)
{
	if(FLAGS(flags, PX_ASPECT))
	{
		Vector new_size(y-x);
		new_size[0]=std::fabs(new_size[0]);
		new_size[1]=std::fabs(new_size[1]);
		
		Vector old_size(tl_-br_);
		old_size[0]=std::fabs(old_size[0]);
		old_size[1]=std::fabs(old_size[1]);
		
		if(new_size[0]!=old_size[0])
			w_=round_to_int(new_size[0]*w_/old_size[0]);
		
		if(new_size[1]!=old_size[1])
			h_=round_to_int(new_size[1]*h_/old_size[1]);
	}
	tl_=x;
	br_=y;
	return *this;
}


RendDesc &
RendDesc::set_viewport(const Point &__tl, const Point &__br)
{ tl_=__tl; br_=__br; return *this; }

RendDesc &
RendDesc::set_viewport(Vector::value_type a, Vector::value_type b, Vector::value_type c, Vector::value_type d)
{ tl_=Point(a,b); br_=Point(c,d); return *this; }

Real
RendDesc::get_pw()const
{
	return w_ ? (br_[0] - tl_[0]) / w_ : 0;
}

Real
RendDesc::get_ph()const
{
	return h_ ? (br_[1] - tl_[1]) / h_ : 0;
}

RendDesc &
RendDesc::set_subwindow(int x, int y, int w, int h)
{
	const Real pw(get_pw());
	const Real ph(get_ph());

	tl_[0]+=pw*x;
	tl_[1]+=ph*y;

	br_[0]-=pw*(w_-(x+w));
	br_[1]-=ph*(h_-(y+h));

	w_=w;
	h_=h;

	return *this;
}

RendDesc &
RendDesc::set_duration(Time duration)
{
	if(get_frame_rate())
	{
		if(duration > Time(0.0))
			set_time_end(get_time_start() + duration  - Time(1/get_frame_rate()));
		else
			set_time_end(get_time_start());
	}
		
	return *this;
}

const Time
RendDesc::get_duration()
{
	if(get_frame_rate())		
		return (get_time_end() - get_time_start() + Time(1/get_frame_rate()));
	return Time(0.0);
}
