/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 * This file contains the clipping rectangle code.
 */

#include "tinsel/cliprect.h"	// object clip rect defs
#include "tinsel/graphics.h"	// normal object drawing
#include "tinsel/object.h"
#include "tinsel/palette.h"
#include "tinsel/tinsel.h"		// for _vm

namespace Tinsel {

/**
 * Resets the clipping rectangle allocator.
 */
void ResetClipRect() {
	_vm->_clipRects.clear();
}

/**
 * Allocate a clipping rectangle from the free list.
 * @param pClip			clip rectangle dimensions to allocate
 */
void AddClipRect(const Common::Rect &pClip) {
	_vm->_clipRects.push_back(pClip);
}

const RectList &GetClipRects() {
	return _vm->_clipRects;
}

/**
 * Creates the intersection of two rectangles.
 * Returns True if there is a intersection.
 * @param pDest			Pointer to destination rectangle that is to receive the intersection
 * @param pSrc1			Pointer to a source rectangle
 * @param pSrc2			Pointer to a source rectangle
 */
bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
	pDest.left   = MAX(pSrc1.left, pSrc2.left);
	pDest.top    = MAX(pSrc1.top, pSrc2.top);
	pDest.right  = MIN(pSrc1.right, pSrc2.right);
	pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);

	return !pDest.isEmpty();
}

/**
 * Creates the union of two rectangles.
 * Returns True if there is a union.
 * @param pDest			destination rectangle that is to receive the new union
 * @param pSrc1			a source rectangle
 * @param pSrc2			a source rectangle
 */
bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
	pDest.left   = MIN(pSrc1.left, pSrc2.left);
	pDest.top    = MIN(pSrc1.top, pSrc2.top);
	pDest.right  = MAX(pSrc1.right, pSrc2.right);
	pDest.bottom = MAX(pSrc1.bottom, pSrc2.bottom);

	return !pDest.isEmpty();
}

/**
 * Check if the two rectangles are next to each other.
 * @param pSrc1			a source rectangle
 * @param pSrc2			a source rectangle
 */
static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
	Common::Rect pDest;

	pDest.left   = MAX(pSrc1.left, pSrc2.left);
	pDest.top    = MAX(pSrc1.top, pSrc2.top);
	pDest.right  = MIN(pSrc1.right, pSrc2.right);
	pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);

	return pDest.isValidRect();
}

/**
 * Adds velocities and creates clipping rectangles for all the
 * objects that have moved on the specified object list.
 * @param pObjList			Playfield display list to draw
 * @param pWin				Playfield window top left position
 * @param pClip				Playfield clipping rectangle
 * @param bNoVelocity		When reset, objects pos is updated with velocity
 * @param bScrolled)		When set, playfield has scrolled
 */
void FindMovingObjects(OBJECT **pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) {
	OBJECT *pObj;			// object list traversal pointer

	for (pObj = *pObjList; pObj != NULL; pObj = pObj->pNext) {
		if (!bNoVelocity) {
			// we want to add velocities to objects position

			if (bScrolled) {
				// this playfield has scrolled

				// indicate change
				pObj->flags |= DMA_CHANGED;
			}
		}

		if ((pObj->flags & DMA_CHANGED) ||	// object changed
			HasPalMoved(pObj->pPal)) {	// or palette moved
			// object has changed in some way

			Common::Rect rcClip;	// objects clipped bounding rectangle
			Common::Rect rcObj;	// objects bounding rectangle

			// calc intersection of objects previous bounding rectangle
			// NOTE: previous position is in screen co-ords
			if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) {
				// previous position is within clipping rect
				AddClipRect(rcClip);
			}

			// calc objects current bounding rectangle
			if (pObj->flags & DMA_ABS) {
				// object position is absolute
				rcObj.left = fracToInt(pObj->xPos);
				rcObj.top  = fracToInt(pObj->yPos);
			} else {
				// object position is relative to window
				rcObj.left = fracToInt(pObj->xPos) - pWin->x;
				rcObj.top  = fracToInt(pObj->yPos) - pWin->y;
			}
			rcObj.right  = rcObj.left + pObj->width;
			rcObj.bottom = rcObj.top  + pObj->height;

			// calc intersection of object with clipping rect
			if (IntersectRectangle(rcClip, rcObj, *pClip)) {
				// current position is within clipping rect
				AddClipRect(rcClip);

				// update previous position
				pObj->rcPrev = rcClip;
			} else {
				// clear previous position
				pObj->rcPrev = Common::Rect();
			}

			// clear changed flag
			pObj->flags &= ~DMA_CHANGED;
		}
	}
}

/**
 * Merges any clipping rectangles that overlap to try and reduce
 * the total number of clip rectangles.
 */
void MergeClipRect() {
	RectList &s_rectList = _vm->_clipRects;

	if (s_rectList.size() <= 1)
		return;

	RectList::iterator rOuter, rInner;

	for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) {
		rInner = rOuter;
		while (++rInner != s_rectList.end()) {

			if (LooseIntersectRectangle(*rOuter, *rInner)) {
				// these two rectangles overlap or
				// are next to each other - merge them

				UnionRectangle(*rOuter, *rOuter, *rInner);

				// remove the inner rect from the list
				s_rectList.erase(rInner);

				// move back to beginning of list
				rInner = rOuter;
			}
		}
	}
}

/**
 * Redraws single objects within this clipping rectangle.
 * @param pObjList		Object list to draw
 * @param pWin			Window top left position
 * @param pClip			Pointer to clip rectangle
 */
void UpdateClipRectSingle(OBJECT *pObj, Common::Point *pWin, Common::Rect *pClip) {
	int x, y, right, bottom;	// object corners
	int hclip, vclip;			// total size of object clipping
	DRAWOBJECT currentObj;		// filled in to draw the current object in list

	// Initialize the fields of the drawing object to empty
	memset(&currentObj, 0, sizeof(DRAWOBJECT));

	if (pObj->flags & DMA_ABS) {
		// object position is absolute
		x = fracToInt(pObj->xPos);
		y = fracToInt(pObj->yPos);
	} else {
		// object position is relative to window
		x = fracToInt(pObj->xPos) - pWin->x;
		y = fracToInt(pObj->yPos) - pWin->y;
	}

	// calc object right
	right = x + pObj->width;
	if (right < 0)
		// totally clipped if negative
		return;

	// calc object bottom
	bottom = y + pObj->height;
	if (bottom < 0)
		// totally clipped if negative
		return;

	// bottom clip = low right y - clip low right y
	currentObj.botClip = bottom - pClip->bottom;
	if (currentObj.botClip < 0) {
		// negative - object is not clipped
		currentObj.botClip = 0;
	}

	// right clip = low right x - clip low right x
	currentObj.rightClip = right - pClip->right;
	if (currentObj.rightClip < 0) {
		// negative - object is not clipped
		currentObj.rightClip = 0;
	}

	// top clip = clip top left y - top left y
	currentObj.topClip = pClip->top - y;
	if (currentObj.topClip < 0) {
		// negative - object is not clipped
		currentObj.topClip = 0;
	} else {	// clipped - adjust start position to top of clip rect
		y = pClip->top;
	}

	// left clip = clip top left x - top left x
	currentObj.leftClip = pClip->left - x;
	if (currentObj.leftClip < 0) {
		// negative - object is not clipped
		currentObj.leftClip = 0;
	} else {
		// NOTE: This else statement is disabled in tinsel v1
		// clipped - adjust start position to left of clip rect
		x = pClip->left;
	}

	// calc object total horizontal clipping
	hclip = currentObj.leftClip + currentObj.rightClip;

	// calc object total vertical clipping
	vclip = currentObj.topClip + currentObj.botClip;

	if (hclip + vclip != 0) {
		// object is clipped in some way

		if (pObj->width <= hclip)
			// object totally clipped horizontally - ignore
			return;

		if (pObj->height <= vclip)
			// object totally clipped vertically - ignore
			return;

		// set clip bit in object flags
		currentObj.flags = pObj->flags | DMA_CLIP;
	} else {	// object is not clipped - copy flags
		currentObj.flags = pObj->flags;
	}

	// copy object properties to local object
	currentObj.width          = pObj->width;
	currentObj.height         = pObj->height;
	currentObj.xPos           = (short)x;
	currentObj.yPos           = (short)y;
	if (TinselVersion != 3) {
		currentObj.pPal       = pObj->pPal;
	} else {
		currentObj.isRLE      = pObj->isRLE;
		currentObj.colorFlags = pObj->colorFlags;
	}
	currentObj.constant       = pObj->constant;
	currentObj.hBits          = pObj->hBits;

	// draw the object
	DrawObject(&currentObj);
}

/**
 * Redraws all objects within this clipping rectangle.
 * @param pObjList		Object list to draw
 * @param pWin			Window top left position
 * @param pClip			Pointer to clip rectangle
 */
void UpdateClipRect(OBJECT **pObjList, Common::Point *pWin, Common::Rect *pClip) {
	OBJECT *pObj;				// object list iterator

	for (pObj = *pObjList; pObj != NULL; pObj = pObj->pNext) {
		UpdateClipRectSingle(pObj, pWin, pClip);
	}
}

} // End of namespace Tinsel
