// This file is part of VSTGUI. It is subject to the license terms 
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#pragma once

#include "cpoint.h"
#include "crect.h"

#ifndef M_PI
#define M_PI        3.14159265358979323846264338327950288
#endif

namespace VSTGUI {

static inline double radians (double degrees) { return degrees * M_PI / 180; }

//-----------------------------------------------------------------------------
/// @brief Graphics Transform Matrix
/// @ingroup new_in_4_0
//-----------------------------------------------------------------------------
struct CGraphicsTransform
{
	double m11 {1.};
	double m12 {0.};
	double m21 {0.};
	double m22 {1.};
	double dx {0.};
	double dy {0.};
	
	CGraphicsTransform () = default;
	CGraphicsTransform (double _m11, double _m12, double _m21, double _m22, double _dx, double _dy)
	: m11 (_m11), m12 (_m12), m21 (_m21), m22 (_m22), dx (_dx), dy (_dy)
	{}
	
	CGraphicsTransform& translate (double x, double y)
	{
		*this = CGraphicsTransform (1, 0, 0, 1, x, y) * this;
		return *this;
	}
	
	CGraphicsTransform& translate (const CPoint& p)
	{
		return translate (p.x, p.y);
	}
	
	CGraphicsTransform& scale (double x, double y)
	{
		*this = CGraphicsTransform (x, 0., 0., y, 0., 0.) * this;
		return *this;
	}

	CGraphicsTransform& scale (const CPoint& p)
	{
		return scale (p.x, p.y);
	}
	
	CGraphicsTransform& rotate (double angle)
	{
		angle = radians (angle);
		*this = CGraphicsTransform (cos (angle), -sin (angle), sin (angle), cos (angle), 0, 0) * this;
		return *this;
	}

	CGraphicsTransform& rotate (double angle, const CPoint& center)
	{
		return translate (-center.x, -center.y).rotate (angle).translate (center.x, center.y);
	}

	CGraphicsTransform& skewX (double angle)
	{
		*this = CGraphicsTransform (1, std::tan (radians (angle)), 0, 1, 0, 0) * *this;
		return *this;
	}

	CGraphicsTransform& skewY (double angle)
	{
		*this = CGraphicsTransform (1, 0, std::tan (radians (angle)), 1, 0, 0) * *this;
		return *this;
	}
	
	bool isInvariant () const
	{
		return *this == CGraphicsTransform ();
	}

	void transform (CCoord& x, CCoord& y) const
	{
		CCoord x2 = m11*x + m12*y + dx;
		CCoord y2 = m21*x + m22*y + dy;
		x = x2;
		y = y2;
	}
		
	void transform (CCoord& left, CCoord& right, CCoord& top, CCoord& bottom) const
	{
		transform (left, top);
		transform (right, bottom);
	}
	
	CPoint& transform (CPoint& p) const
	{
		transform (p.x, p.y);
		return p;
	}

	CRect& transform (CRect& r) const
	{
		transform (r.left, r.right, r.top, r.bottom);
		return r;
	}

	CGraphicsTransform inverse () const
	{
		CGraphicsTransform result;
		const double denominator = m11 * m22 - m12 * m21;
		if (denominator != 0)
		{
			result.m11 = m22 / denominator;
			result.m12 = -m12 / denominator;
			result.m21 = -m21 / denominator;
			result.m22 = m11 / denominator;
			result.dx = ((m12 * dy) - (m22 * dx)) / denominator;
			result.dy = ((m21 * dx) - (m11 * dy)) / denominator;
		}
		return result;
	}

	CGraphicsTransform operator* (const CGraphicsTransform& t) const
	{
		CGraphicsTransform result;
		result.m11 = (m11 * t.m11) + (m12 * t.m21);
		result.m21 = (m21 * t.m11) + (m22 * t.m21);
		result.dx = (m11 * t.dx) + (m12 * t.dy) + dx;
		result.m12 = (m11 * t.m12) + (m12 * t.m22);
		result.m22 = (m21 * t.m12) + (m22 * t.m22);
		result.dy = (m21 * t.dx) + (m22 * t.dy) + dy;
		return result;
	}
	
	CGraphicsTransform operator* (const CGraphicsTransform* t) const { return *this * *t; }

	bool operator== (const CGraphicsTransform& t) const
	{
		return m11 == t.m11 && m12 == t.m12 && m21 == t.m21 && m22 == t.m22 && dx == t.dx && dy == t.dy;
	}

	bool operator!= (const CGraphicsTransform& t) const
	{
		return m11 != t.m11 || m12 != t.m12 || m21 != t.m21 || m22 != t.m22 || dx != t.dx || dy != t.dy;
	}
};

} // VSTGUI
