/*
 * Rect.cpp, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */

#include "StdInc.h"
#include "Rect.h"
#include "int3.h"

VCMI_LIB_NAMESPACE_BEGIN

Point::Point(const int3 & a)
	: x(a.x)
	, y(a.y)
{
}

/// Returns rect union - rect that covers both this rect and provided rect
Rect Rect::include(const Rect & other) const
{
	Point topLeft{
		std::min(this->left(), other.left()),
		std::min(this->top(), other.top())
	};

	Point bottomRight{
		std::max(this->right(), other.right()),
		std::max(this->bottom(), other.bottom())
	};

	return Rect(topLeft, bottomRight - topLeft);
}

Rect Rect::createCentered( const Point & around, const Point & dimensions )
{
	return Rect(around - dimensions/2, dimensions);
}

Rect Rect::createAround(const Rect &r, int width)
{
	return Rect(r.x - width, r.y - width, r.w + width * 2, r.h + width * 2);
}

Rect Rect::createCentered( const Rect & rect, const Point & dimensions)
{
	return createCentered(rect.center(), dimensions);
}

bool Rect::intersectionTest(const Rect & other) const
{
	// this rect is above other rect
	if(this->bottom() < other.top())
		return false;

	// this rect is below other rect
	if(this->top() > other.bottom() )
		return false;

	// this rect is to the left of other rect
	if(this->right() < other.left())
		return false;

	// this rect is to the right of other rect
	if(this->left() > other.right())
		return false;

	return true;
}

/// Algorithm to test whether line segment between points line1-line2 will intersect with
/// rectangle specified by top-left and bottom-right points
/// Note that in order to avoid floating point rounding errors algorithm uses integers with no divisions
bool Rect::intersectionTest(const Point & line1, const Point & line2) const
{
	// check whether segment is located to the left of our rect
	if (line1.x < left() && line2.x < left())
		return false;

	// check whether segment is located to the right of our rect
	if (line1.x > right() && line2.x > right())
		return false;

	// check whether segment is located on top of our rect
	if (line1.y < top() && line2.y < top())
		return false;

	// check whether segment is located below of our rect
	if (line1.y > bottom() && line2.y > bottom())
		return false;

	Point vector { line2.x - line1.x, line2.y - line1.y};

	// compute position of corners relative to our line
	int tlTest = vector.y*topLeft().x - vector.x*topLeft().y + (line2.x*line1.y-line1.x*line2.y);
	int trTest = vector.y*bottomRight().x - vector.x*topLeft().y + (line2.x*line1.y-line1.x*line2.y);
	int blTest = vector.y*topLeft().x - vector.x*bottomRight().y + (line2.x*line1.y-line1.x*line2.y);
	int brTest = vector.y*bottomRight().x - vector.x*bottomRight().y + (line2.x*line1.y-line1.x*line2.y);

	// if all points are on the left of our line then there is no intersection
	if ( tlTest > 0 && trTest > 0 && blTest > 0 && brTest > 0 )
		return false;

	// if all points are on the right of our line then there is no intersection
	if ( tlTest < 0 && trTest < 0 && blTest < 0 && brTest < 0 )
		return false;

	// if all previous checks failed, this means that there is an intersection between line and AABB
	return true;
}


Rect Rect::intersect(const Rect & other) const
{
	if(intersectionTest(other))
	{
		Point topLeft{
			std::max(this->left(), other.left()),
			std::max(this->top(), other.top())
		};

		Point bottomRight{
			std::min(this->right(), other.right()),
			std::min(this->bottom(), other.bottom())
		};

		return Rect(topLeft, bottomRight - topLeft);
	}
	else
	{
		return Rect();
	}
}

int Rect::distanceTo(const Point & target) const
{
	int distanceX = std::max({left() - target.x, 0, target.x - right()});
	int distanceY = std::max({top() - target.y, 0, target.y - bottom()});

	return Point(distanceX, distanceY).length();
}

VCMI_LIB_NAMESPACE_END
