/******************************************************************************
 * Spine Runtimes License Agreement
 * Last updated July 28, 2023. Replaces all prior versions.
 *
 * Copyright (c) 2013-2023, Esoteric Software LLC
 *
 * Integration of the Spine Runtimes into software or otherwise creating
 * derivative works of the Spine Runtimes is permitted under the terms and
 * conditions of Section 2 of the Spine Editor License Agreement:
 * http://esotericsoftware.com/spine-editor-license
 *
 * Otherwise, it is permitted to integrate the Spine Runtimes into software or
 * otherwise create derivative works of the Spine Runtimes (collectively,
 * "Products"), provided that each user of the Products must obtain their own
 * Spine Editor license and redistribution of the Products in any form must
 * include this license and copyright notice.
 *
 * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
 * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
 * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

#include <spine/SkeletonBounds.h>

#include <spine/Bone.h>
#include <spine/BoundingBoxAttachment.h>
#include <spine/Skeleton.h>

#include <spine/Slot.h>

#include <float.h>

using namespace spine;

SkeletonBounds::SkeletonBounds() : _minX(0), _minY(0), _maxX(0), _maxY(0) {
}

SkeletonBounds::~SkeletonBounds() {
	for (size_t i = 0, n = _polygons.size(); i < n; i++)
		_polygonPool.free(_polygons[i]);
	_polygons.clear();
}

void SkeletonBounds::update(Skeleton &skeleton, bool updateAabb) {
	Vector<Slot *> &slots = skeleton.getSlots();
	size_t slotCount = slots.size();

	_boundingBoxes.clear();
	for (size_t i = 0, n = _polygons.size(); i < n; ++i) {
		_polygonPool.free(_polygons[i]);
	}

	_polygons.clear();

	for (size_t i = 0; i < slotCount; i++) {
		Slot *slot = slots[i];
		if (!slot->getBone().isActive()) continue;

		Attachment *attachment = slot->getAttachment();
		if (attachment == NULL || !attachment->getRTTI().instanceOf(BoundingBoxAttachment::rtti)) continue;
		BoundingBoxAttachment *boundingBox = static_cast<BoundingBoxAttachment *>(attachment);
		_boundingBoxes.add(boundingBox);

		spine::Polygon *polygonP = _polygonPool.obtain();
		_polygons.add(polygonP);

		Polygon &polygon = *polygonP;

		size_t count = boundingBox->getWorldVerticesLength();
		polygon._count = (int) count;
		if (polygon._vertices.size() < count) {
			polygon._vertices.setSize(count, 0);
		}
		boundingBox->computeWorldVertices(*slot, polygon._vertices);
	}

	if (updateAabb)
		aabbCompute();
	else {
		_minX = FLT_MIN;
		_minY = FLT_MIN;
		_maxX = FLT_MAX;
		_maxY = FLT_MAX;
	}
}

bool SkeletonBounds::aabbcontainsPoint(float x, float y) {
	return x >= _minX && x <= _maxX && y >= _minY && y <= _maxY;
}

bool SkeletonBounds::aabbintersectsSegment(float x1, float y1, float x2, float y2) {
	float minX = _minX;
	float minY = _minY;
	float maxX = _maxX;
	float maxY = _maxY;

	if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) ||
		(y1 >= maxY && y2 >= maxY)) {
		return false;
	}

	float m = (y2 - y1) / (x2 - x1);
	float y = m * (minX - x1) + y1;
	if (y > minY && y < maxY) return true;
	y = m * (maxX - x1) + y1;
	if (y > minY && y < maxY) return true;
	float x = (minY - y1) / m + x1;
	if (x > minX && x < maxX) return true;
	x = (maxY - y1) / m + x1;
	if (x > minX && x < maxX) return true;
	return false;
}

bool SkeletonBounds::aabbIntersectsSkeleton(SkeletonBounds &bounds) {
	return _minX < bounds._maxX && _maxX > bounds._minX && _minY < bounds._maxY && _maxY > bounds._minY;
}

bool SkeletonBounds::containsPoint(spine::Polygon *polygon, float x, float y) {
	Vector<float> &vertices = polygon->_vertices;
	int nn = polygon->_count;

	int prevIndex = nn - 2;
	bool inside = false;
	for (int ii = 0; ii < nn; ii += 2) {
		float vertexY = vertices[ii + 1];
		float prevY = vertices[prevIndex + 1];
		if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
			float vertexX = vertices[ii];
			if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) {
				inside = !inside;
			}
		}
		prevIndex = ii;
	}
	return inside;
}

BoundingBoxAttachment *SkeletonBounds::containsPoint(float x, float y) {
	for (size_t i = 0, n = _polygons.size(); i < n; ++i)
		if (containsPoint(_polygons[i], x, y)) return _boundingBoxes[i];
	return NULL;
}

BoundingBoxAttachment *SkeletonBounds::intersectsSegment(float x1, float y1, float x2, float y2) {
	for (size_t i = 0, n = _polygons.size(); i < n; ++i)
		if (intersectsSegment(_polygons[i], x1, y1, x2, y2)) return _boundingBoxes[i];
	return NULL;
}

bool SkeletonBounds::intersectsSegment(spine::Polygon *polygon, float x1, float y1, float x2, float y2) {
	Vector<float> &vertices = polygon->_vertices;
	size_t nn = polygon->_count;

	float width12 = x1 - x2, height12 = y1 - y2;
	float det1 = x1 * y2 - y1 * x2;
	float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
	for (size_t ii = 0; ii < nn; ii += 2) {
		float x4 = vertices[ii], y4 = vertices[ii + 1];
		float det2 = x3 * y4 - y3 * x4;
		float width34 = x3 - x4, height34 = y3 - y4;
		float det3 = width12 * height34 - height12 * width34;
		float x = (det1 * width34 - width12 * det2) / det3;
		if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
			float y = (det1 * height34 - height12 * det2) / det3;
			if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) {
				return true;
			}
		}
		x3 = x4;
		y3 = y4;
	}

	return false;
}

spine::Polygon *SkeletonBounds::getPolygon(BoundingBoxAttachment *attachment) {
	int index = _boundingBoxes.indexOf(attachment);
	return index == -1 ? NULL : _polygons[index];
}

BoundingBoxAttachment *SkeletonBounds::getBoundingBox(Polygon *polygon) {
	int index = _polygons.indexOf(polygon);
	return index == -1 ? NULL : _boundingBoxes[index];
}

Vector<spine::Polygon *> &SkeletonBounds::getPolygons() {
	return _polygons;
}

Vector<BoundingBoxAttachment *> &SkeletonBounds::getBoundingBoxes() {
	return _boundingBoxes;
}

float SkeletonBounds::getWidth() {
	return _maxX - _minX;
}

float SkeletonBounds::getHeight() {
	return _maxY - _minY;
}

void SkeletonBounds::aabbCompute() {
	float minX = FLT_MAX;
	float minY = FLT_MAX;
	float maxX = FLT_MIN;
	float maxY = FLT_MIN;

	for (size_t i = 0, n = _polygons.size(); i < n; ++i) {
		spine::Polygon *polygon = _polygons[i];
		Vector<float> &vertices = polygon->_vertices;
		for (int ii = 0, nn = polygon->_count; ii < nn; ii += 2) {
			float x = vertices[ii];
			float y = vertices[ii + 1];
			minX = MathUtil::min(minX, x);
			minY = MathUtil::min(minY, y);
			maxX = MathUtil::max(maxX, x);
			maxY = MathUtil::max(maxY, y);
		}
	}
	_minX = minX;
	_minY = minY;
	_maxX = maxX;
	_maxY = maxY;
}
