//Copyright Paul Reiche, Fred Ford. 1992-2002

/*
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "libs/graphics/context.h"
#include "libs/graphics/drawable.h"
#include "libs/graphics/tfb_draw.h"
#include "libs/log.h"

//#define DEBUG_INTERSEC

static inline BOOLEAN
images_intersect (IMAGE_BOX *box1, IMAGE_BOX *box2, const RECT *rect)
{
	return TFB_DrawImage_Intersect (box1->FramePtr->image, box1->Box.corner,
			box2->FramePtr->image, box2->Box.corner, rect);
}

static TIME_VALUE
frame_intersect (INTERSECT_CONTROL *pControl0, RECT *pr0,
		INTERSECT_CONTROL *pControl1, RECT *pr1, TIME_VALUE t0,
		TIME_VALUE t1)
{
	SIZE time_error0, time_error1;
	SIZE cycle0, cycle1;
	SIZE dx_0, dy_0, dx_1, dy_1;
	SIZE xincr0, yincr0, xincr1, yincr1;
	SIZE xerror0, xerror1, yerror0, yerror1;
	RECT r_intersect;
	IMAGE_BOX IB0, IB1;
	BOOLEAN check0, check1;

	IB0.FramePtr = pControl0->IntersectStamp.frame;
	IB0.Box.corner = pr0->corner;
	IB0.Box.extent.width = GetFrameWidth (IB0.FramePtr);
	IB0.Box.extent.height = GetFrameHeight (IB0.FramePtr);
	IB1.FramePtr = pControl1->IntersectStamp.frame;
	IB1.Box.corner = pr1->corner;
	IB1.Box.extent.width = GetFrameWidth (IB1.FramePtr);
	IB1.Box.extent.height = GetFrameHeight (IB1.FramePtr);

	dx_0 = pr0->extent.width;
	dy_0 = pr0->extent.height;
	if (dx_0 >= 0)
		xincr0 = 1;
	else
	{
		xincr0 = -1;
		dx_0 = -dx_0;
	}
	if (dy_0 >= 0)
		yincr0 = 1;
	else
	{
		yincr0 = -1;
		dy_0 = -dy_0;
	}
	if (dx_0 >= dy_0)
		cycle0 = dx_0;
	else
		cycle0 = dy_0;
	xerror0 = yerror0 = cycle0;
			
	dx_1 = pr1->extent.width;
	dy_1 = pr1->extent.height;
	if (dx_1 >= 0)
		xincr1 = 1;
	else
	{
		xincr1 = -1;
		dx_1 = -dx_1;
	}
	if (dy_1 >= 0)
		yincr1 = 1;
	else
	{
		yincr1 = -1;
		dy_1 = -dy_1;
	}
	if (dx_1 >= dy_1)
		cycle1 = dx_1;
	else
		cycle1 = dy_1;
	xerror1 = yerror1 = cycle1;
			
	check0 = check1 = FALSE;
	if (t0 <= 1)
	{
		time_error0 = time_error1 = 0;
		if (t0 == 0)
		{
			++t0;
			goto CheckFirstIntersection;
		}
	}
	else
	{
		SIZE delta;
		COUNT start;
		long error;

		start = (COUNT)cycle0 * (COUNT)(t0 - 1);
		time_error0 = start & ((1 << TIME_SHIFT) - 1);
		if ((start >>= (COUNT)TIME_SHIFT) > 0)
		{
			if ((error = (long)xerror0
					- (long)dx_0 * (long)start) > 0)
				xerror0 = (SIZE)error;
			else
			{
				delta = -(SIZE)(error / (long)cycle0) + 1;
				IB0.Box.corner.x += xincr0 * delta;
				xerror0 = (SIZE)(error + (long)cycle0 * (long)delta);
			}
			if ((error = (long)yerror0
					- (long)dy_0 * (long)start) > 0)
				yerror0 = (SIZE)error;
			else
			{
				delta = -(SIZE)(error / (long)cycle0) + 1;
				IB0.Box.corner.y += yincr0 * delta;
				yerror0 = (SIZE)(error + (long)cycle0 * (long)delta);
			}
			pr0->corner = IB0.Box.corner;
		}
	
		start = (COUNT)cycle1 * (COUNT)(t0 - 1);
		time_error1 = start & ((1 << TIME_SHIFT) - 1);
		if ((start >>= (COUNT)TIME_SHIFT) > 0)
		{
			if ((error = (long)xerror1
					- (long)dx_1 * (long)start) > 0)
				xerror1 = (SIZE)error;
			else
			{
				delta = -(SIZE)(error / (long)cycle1) + 1;
				IB1.Box.corner.x += xincr1 * delta;
				xerror1 = (SIZE)(error + (long)cycle1 * (long)delta);
			}
			if ((error = (long)yerror1
					- (long)dy_1 * (long)start) > 0)
				yerror1 = (SIZE)error;
			else
			{
				delta = -(SIZE)(error / (long)cycle1) + 1;
				IB1.Box.corner.y += yincr1 * delta;
				yerror1 = (SIZE)(error + (long)cycle1 * (long)delta);
			}
			pr1->corner = IB1.Box.corner;
		}
	}

	pControl0->last_time_val = pControl1->last_time_val = t0;
	do
	{
		++t0;
		if ((time_error0 += cycle0) >= (1 << TIME_SHIFT))
		{
			if ((xerror0 -= dx_0) <= 0)
			{
				IB0.Box.corner.x += xincr0;
				xerror0 += cycle0;
			}
			if ((yerror0 -= dy_0) <= 0)
			{
				IB0.Box.corner.y += yincr0;
				yerror0 += cycle0;
			}

			check0 = TRUE;
			time_error0 -= (1 << TIME_SHIFT);
		}
			
		if ((time_error1 += cycle1) >= (1 << TIME_SHIFT))
		{
			if ((xerror1 -= dx_1) <= 0)
			{
				IB1.Box.corner.x += xincr1;
				xerror1 += cycle1;
			}
			if ((yerror1 -= dy_1) <= 0)
			{
				IB1.Box.corner.y += yincr1;
				yerror1 += cycle1;
			}

			check1 = TRUE;
			time_error1 -= (1 << TIME_SHIFT);
		}

		if (check0 || check1)
		{ /* if check0 && check1, this may not be quite right --
						 * if shapes had a pixel's separation to begin with
						 * and both moved toward each other, you would actually
						 * get a pixel overlap but since the last positions were
						 * separated by a pixel, the shapes wouldn't be touching
						 * each other.
						 */
CheckFirstIntersection:
			if (BoxIntersect (&IB0.Box, &IB1.Box, &r_intersect)
					&& images_intersect (&IB0, &IB1, &r_intersect))
				return (t0);
			
			if (check0)
			{
				pr0->corner = IB0.Box.corner;
				pControl0->last_time_val = t0;
				check0 = FALSE;
			}
			if (check1)
			{
				pr1->corner = IB1.Box.corner;
				pControl1->last_time_val = t0;
				check1 = FALSE;
			}
		}
	} while (t0 <= t1);

	return ((TIME_VALUE)0);
}

TIME_VALUE
DrawablesIntersect (INTERSECT_CONTROL *pControl0,
		INTERSECT_CONTROL *pControl1, TIME_VALUE max_time_val)
{
	SIZE dy;
	SIZE time_y_0, time_y_1;
	RECT r0, r1;
	FRAME FramePtr0, FramePtr1;

	if (!ContextActive () || max_time_val == 0)
		return ((TIME_VALUE)0);
	else if (max_time_val > MAX_TIME_VALUE)
		max_time_val = MAX_TIME_VALUE;

	pControl0->last_time_val = pControl1->last_time_val = 0;

	r0.corner = pControl0->IntersectStamp.origin;
	r1.corner = pControl1->IntersectStamp.origin;

	r0.extent.width = pControl0->EndPoint.x - r0.corner.x;
	r0.extent.height = pControl0->EndPoint.y - r0.corner.y;
	r1.extent.width = pControl1->EndPoint.x - r1.corner.x;
	r1.extent.height = pControl1->EndPoint.y - r1.corner.y;
		
	FramePtr0 = pControl0->IntersectStamp.frame;
	if (FramePtr0 == 0)
		return(0);
	r0.corner.x -= FramePtr0->HotSpot.x;
	r0.corner.y -= FramePtr0->HotSpot.y;

	FramePtr1 = pControl1->IntersectStamp.frame;
	if (FramePtr1 == 0)
		return(0);
	r1.corner.x -= FramePtr1->HotSpot.x;
	r1.corner.y -= FramePtr1->HotSpot.y;

	dy = r1.corner.y - r0.corner.y;
	time_y_0 = dy - GetFrameHeight (FramePtr0) + 1;
	time_y_1 = dy + GetFrameHeight (FramePtr1) - 1;
	dy = r0.extent.height - r1.extent.height;

	if ((time_y_0 <= 0 && time_y_1 >= 0)
			|| (time_y_0 > 0 && dy >= time_y_0)
			|| (time_y_1 < 0 && dy <= time_y_1))
	{
		SIZE dx;
		SIZE time_x_0, time_x_1;

		dx = r1.corner.x - r0.corner.x;
		time_x_0 = dx - GetFrameWidth (FramePtr0) + 1;
		time_x_1 = dx + GetFrameWidth (FramePtr1) - 1;
		dx = r0.extent.width - r1.extent.width;

		if ((time_x_0 <= 0 && time_x_1 >= 0)
				|| (time_x_0 > 0 && dx >= time_x_0)
				|| (time_x_1 < 0 && dx <= time_x_1))
		{
			TIME_VALUE intersect_time;

			if (dx == 0 && dy == 0)
				time_y_0 = time_y_1 = 0;
			else
			{
				SIZE t;
				long time_beg, time_end, fract;

				if (time_y_1 < 0)
				{
					t = time_y_0;
					time_y_0 = -time_y_1;
					time_y_1 = -t;
				}
				else if (time_y_0 <= 0)
				{
					if (dy < 0)
						time_y_1 = -time_y_0;
					time_y_0 = 0;
				}
				if (dy < 0)
					dy = -dy;
				if (dy < time_y_1)
					time_y_1 = dy;
					/* just to be safe, widen search area */
				--time_y_0;
				++time_y_1;

				if (time_x_1 < 0)
				{
					t = time_x_0;
					time_x_0 = -time_x_1;
					time_x_1 = -t;
				}
				else if (time_x_0 <= 0)
				{
					if (dx < 0)
						time_x_1 = -time_x_0;
					time_x_0 = 0;
				}
				if (dx < 0)
					dx = -dx;
				if (dx < time_x_1)
					time_x_1 = dx;
					/* just to be safe, widen search area */
				--time_x_0;
				++time_x_1;

#ifdef DEBUG_INTERSEC
				log_add (log_Debug, "FramePtr0<%d, %d> --> <%d, %d>",
						GetFrameWidth (FramePtr0), GetFrameHeight (FramePtr0),
						r0.corner.x, r0.corner.y);
				log_add (log_Debug, "FramePtr1<%d, %d> --> <%d, %d>",
						GetFrameWidth (FramePtr1), GetFrameHeight (FramePtr1),
						r1.corner.x, r1.corner.y);
				log_add (log_Debug, "time_x(%d, %d)-%d, time_y(%d, %d)-%d",
						time_x_0, time_x_1, dx, time_y_0, time_y_1, dy);
#endif /* DEBUG_INTERSEC */
				if (dx == 0)
				{
					time_beg = time_y_0;
					time_end = time_y_1;
					fract = dy;
				}
				else if (dy == 0)
				{
					time_beg = time_x_0;
					time_end = time_x_1;
					fract = dx;
				}
				else
				{
					long time_x, time_y;

					time_x = (long)time_x_0 * (long)dy;
					time_y = (long)time_y_0 * (long)dx;
					time_beg = time_x < time_y ? time_y : time_x;

					time_x = (long)time_x_1 * (long)dy;
					time_y = (long)time_y_1 * (long)dx;
					time_end = time_x > time_y ? time_y : time_x;

					fract = (long)dx * (long)dy;
				}

				if ((time_beg <<= TIME_SHIFT) < fract)
					time_y_0 = 0;
				else
					time_y_0 = (SIZE)(time_beg / fract);

				if (time_end >= fract /* just in case of overflow */
						|| (time_end <<= TIME_SHIFT) >=
						fract * (long)max_time_val)
					time_y_1 = max_time_val - 1;
				else
					time_y_1 = (SIZE)((time_end + fract - 1) / fract) - 1;
			}

#ifdef DEBUG_INTERSEC
			log_add (log_Debug, "start_time = %d, end_time = %d",
					time_y_0, time_y_1);
#endif /* DEBUG_INTERSEC */
			if (time_y_0 <= time_y_1
					&& (intersect_time = frame_intersect (
					pControl0, &r0, pControl1, &r1,
					(TIME_VALUE)time_y_0, (TIME_VALUE)time_y_1)))
			{
				FramePtr0 = pControl0->IntersectStamp.frame;
				pControl0->EndPoint.x = r0.corner.x + FramePtr0->HotSpot.x;
				pControl0->EndPoint.y = r0.corner.y + FramePtr0->HotSpot.y;
				FramePtr1 = pControl1->IntersectStamp.frame;
				pControl1->EndPoint.x = r1.corner.x + FramePtr1->HotSpot.x;
				pControl1->EndPoint.y = r1.corner.y + FramePtr1->HotSpot.y;

				return (intersect_time);
			}
		}
	}

	return ((TIME_VALUE)0);
}

