/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * This file contains utilities to handle multi-part objects.
 */

#include "tinsel/multiobj.h"
#include "tinsel/handle.h"
#include "tinsel/object.h"
#include "tinsel/tinsel.h"

namespace Tinsel {

/**
 * Initialize a multi-part object using a list of images to init
 * each object piece. One object is created for each image in the list.
 * All objects are given the same palette as the first image. A pointer
 * to the first (master) object created is returned.
 * @param pInitTbl			Pointer to multi-object initialisation table
 */
OBJECT *MultiInitObject(const MULTI_INIT *pInitTbl) {
	OBJ_INIT obj_init;	// object init table
	OBJECT *pFirst, *pObj;	// object pointers
	FRAME *pFrame;		// list of images for the multi-part object

	if (FROM_32(pInitTbl->hMulFrame)) {
		// we have a frame handle
		pFrame = (FRAME *)LockMem(FROM_32(pInitTbl->hMulFrame));

		obj_init.hObjImg  = READ_32(pFrame);	// first objects shape
	} else {	// this must be a animation list for a NULL object
		pFrame = NULL;
		obj_init.hObjImg = 0;	// first objects shape
	}

	// init the object init table
	obj_init.objFlags = (int)FROM_32(pInitTbl->mulFlags);	// all objects have same flags
	obj_init.objID    = (int)FROM_32(pInitTbl->mulID);	// all objects have same ID
	obj_init.objX     = (int)FROM_32(pInitTbl->mulX);	// all objects have same X ani pos
	obj_init.objY     = (int)FROM_32(pInitTbl->mulY);	// all objects have same Y ani pos
	obj_init.objZ     = (int)FROM_32(pInitTbl->mulZ);	// all objects have same Z pos

	// create and init the first object
	pObj = pFirst = InitObject(&obj_init);

	if (pFrame) {
		// if we have any animation frames

		pFrame++;

		while (READ_32(pFrame) != 0) {
			// set next objects shape
			obj_init.hObjImg = READ_32(pFrame);

			// create next object and link to previous
			pObj = pObj->pSlave = InitObject(&obj_init);

			pFrame++;
		}
	}

	// null end of list for final object
	pObj->pSlave = NULL;

	// return master object
	return pFirst;
}

/**
 * Inserts the multi-part object onto the specified object list.
 * @param pObjList			List to insert multi-part object onto
* @param  pInsObj			Head of multi-part object to insert

 */

void MultiInsertObject(OBJECT **pObjList, OBJECT *pInsObj) {
	// validate object pointer
	assert(isValidObject(pInsObj));

	// for all the objects that make up this multi-part
	do {
		// add next part to the specified list
		InsertObject(pObjList, pInsObj);

		// next obj in list
		pInsObj = pInsObj->pSlave;
	} while (pInsObj != NULL);
}

/**
 * Deletes all the pieces of a multi-part object from the
 * specified object list.
 * @param pObjList			List to delete multi-part object from
 * @param pMultiObj			Multi-part object to be deleted
 */

void MultiDeleteObject(OBJECT **pObjList, OBJECT *pMultiObj) {
	// validate object pointer
	assert(isValidObject(pMultiObj));

	// for all the objects that make up this multi-part
	do {
		// delete object
		DelObject(pObjList, pMultiObj);

		// next obj in list
		pMultiObj = pMultiObj->pSlave;
	}
	while (pMultiObj != NULL);
}

/**
 * Hides a multi-part object by giving each object a "NullImage"
 * image pointer.
 * @param pMultiObj			Multi-part object to be hidden
 */

void MultiHideObject(OBJECT *pMultiObj) {
	// validate object pointer
	assert(isValidObject(pMultiObj));

	// set master shape to null animation frame
	pMultiObj->hShape = 0;

	// change all objects
	MultiReshape(pMultiObj);
}

/**
 * Horizontally flip a multi-part object.
 * @param pFlipObj			Head of multi-part object to flip
 */

void MultiHorizontalFlip(OBJECT *pFlipObj) {
	// validate object pointer
	assert(isValidObject(pFlipObj));

	// for all the objects that make up this multi-part
	do {
		// horizontally flip the next part
		AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPH,
			pFlipObj->hImg);

		// next obj in list
		pFlipObj = pFlipObj->pSlave;
	} while (pFlipObj != NULL);
}

/**
 * Vertically flip a multi-part object.
 * @param pFlipObj			Head of multi-part object to flip
 */

void MultiVerticalFlip(OBJECT *pFlipObj) {
	// validate object pointer
	assert(isValidObject(pFlipObj));

	// for all the objects that make up this multi-part
	do {
		// vertically flip the next part
		AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPV,
			pFlipObj->hImg);

		// next obj in list
		pFlipObj = pFlipObj->pSlave;
	}
	while (pFlipObj != NULL);
}

/**
 * Adjusts the coordinates of a multi-part object.	The adjustments
 * take into account the orientation of the object.
 * @param pMultiObj			Multi-part object to be adjusted
 * @param deltaX			X adjustment
 * @param deltaY			Y adjustment
 */

void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
	// validate object pointer
	assert(isValidObject(pMultiObj));

	if (deltaX == 0 && deltaY == 0)
		return;		// ignore no change

	if (!TinselV2) {
		// *** This may be wrong!!!
		if (pMultiObj->flags & DMA_FLIPH) {
			// image is flipped horizontally - flip the x direction
			deltaX = -deltaX;
		}

		if (pMultiObj->flags & DMA_FLIPV) {
			// image is flipped vertically - flip the y direction
			deltaY = -deltaY;
		}
	}

	// for all the objects that make up this multi-part
	do {
		// signal a change in the object
		pMultiObj->flags |= DMA_CHANGED;

		// adjust the x position
		pMultiObj->xPos += intToFrac(deltaX);

		// adjust the y position
		pMultiObj->yPos += intToFrac(deltaY);

		// next obj in list
		pMultiObj = pMultiObj->pSlave;

	} while (pMultiObj != NULL);
}

/**
 * Moves all the pieces of a multi-part object by the specified
 * amount. Does not take into account the objects orientation.
 * @param pMultiObj			Multi-part object to be adjusted
 * @param deltaX			X movement
 * @param deltaY			Y movement
 */

void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
	// validate object pointer
	assert(isValidObject(pMultiObj));

	if (deltaX == 0 && deltaY == 0)
		return;		// ignore no change

	// for all the objects that make up this multi-part
	do {
		// signal a change in the object
		pMultiObj->flags |= DMA_CHANGED;

		// adjust the x position
		pMultiObj->xPos += intToFrac(deltaX);

		// adjust the y position
		pMultiObj->yPos += intToFrac(deltaY);

		// next obj in list
		pMultiObj = pMultiObj->pSlave;

	} while (pMultiObj != NULL);
}

/**
 * Sets the x & y anim position of all pieces of a multi-part object.
 * @param pMultiObj			Multi-part object whose position is to be changed
 * @param newAniX			New x animation position
 * @param newAniY			New y animation position
 */

void MultiSetAniXY(OBJECT *pMultiObj, int newAniX, int newAniY) {
	int curAniX, curAniY;	// objects current animation position

	// validate object pointer
	assert(isValidObject(pMultiObj));

	// get master objects current animation position
	GetAniPosition(pMultiObj, &curAniX, &curAniY);

	// calc difference between current and new positions
	newAniX -= curAniX;
	newAniY -= curAniY;

	// move all pieces by the difference
	MultiMoveRelXY(pMultiObj, newAniX, newAniY);
}

/**
 * Sets the x anim position of all pieces of a multi-part object.
 * @param pMultiObj			Multi-part object whose x position is to be changed
 * @param newAniX			New x animation position
 */

void MultiSetAniX(OBJECT *pMultiObj, int newAniX) {
	int curAniX, curAniY;	// objects current animation position

	// validate object pointer
	assert(isValidObject(pMultiObj));

	// get master objects current animation position
	GetAniPosition(pMultiObj, &curAniX, &curAniY);

	// calc x difference between current and new positions
	newAniX -= curAniX;
	curAniY = 0;

	// move all pieces by the difference
	MultiMoveRelXY(pMultiObj, newAniX, curAniY);
}

/**
 * Sets the y anim position of all pieces of a multi-part object.
 * @param pMultiObj			Multi-part object whose x position is to be changed
 * @param newAniX			New y animation position
 */

void MultiSetAniY(OBJECT *pMultiObj, int newAniY) {
	int curAniX, curAniY;	// objects current animation position

	// validate object pointer
	assert(isValidObject(pMultiObj));

	// get master objects current animation position
	GetAniPosition(pMultiObj, &curAniX, &curAniY);

	// calc y difference between current and new positions
	curAniX = 0;
	newAniY -= curAniY;

	// move all pieces by the difference
	MultiMoveRelXY(pMultiObj, curAniX, newAniY);
}

/**
 * Sets the Z position of all pieces of a multi-part object.
 * @param pMultiObj		Multi-part object to be adjusted
 * @param newZ			New Z order
 */

void MultiSetZPosition(OBJECT *pMultiObj, int newZ) {
	// validate object pointer
	assert(isValidObject(pMultiObj));

	// for all the objects that make up this multi-part
	do {
		// signal a change in the object
		pMultiObj->flags |= DMA_CHANGED;

		// set the new z position
		pMultiObj->zPos = newZ;

		// next obj in list
		pMultiObj = pMultiObj->pSlave;
	}
	while (pMultiObj != NULL);
}

/**
 * Reshape a multi-part object.
 * @param pMultiObj			Multi-part object to re-shape
 */

void MultiReshape(OBJECT *pMultiObj) {
	SCNHANDLE hFrame;

	// validate object pointer
	assert(isValidObject(pMultiObj));

	// get objects current anim frame
	hFrame = pMultiObj->hShape;

	if (hFrame != 0 && hFrame != pMultiObj->hMirror) {
		// a valid shape frame which is different from previous

		// get pointer to frame
		const FRAME *pFrame = (const FRAME *)LockMem(hFrame);

		// update previous
		pMultiObj->hMirror = hFrame;

		while (READ_32(pFrame) != 0 && pMultiObj != NULL) {
			// a normal image - update the current object with this image
			AnimateObject(pMultiObj, READ_32(pFrame));

			// move to next image for this frame
			pFrame++;

			// move to next part of object
			pMultiObj = pMultiObj->pSlave;
		}

		// null the remaining object parts
		while (pMultiObj != NULL) {
			// set a null image for this object part
			AnimateObject(pMultiObj, 0);

			// move to next part of object
			pMultiObj = pMultiObj->pSlave;
		}
	} else if (hFrame == 0) {
		// update previous
		pMultiObj->hMirror = hFrame;

		// null all the object parts
		while (pMultiObj != NULL) {
			// set a null image for this object part
			AnimateObject(pMultiObj, 0);

			// move to next part of object
			pMultiObj = pMultiObj->pSlave;
		}
	}
}

/**
 * Returns the left-most point of a multi-part object.
 * @param pMulti			Multi-part object
 */

int MultiLeftmost(OBJECT *pMulti) {
	int left;

	// validate object pointer
	assert(isValidObject(pMulti));

	// init leftmost point to first object
	left = fracToInt(pMulti->xPos);

	// for all the objects in this multi
	while ((pMulti = pMulti->pSlave) != NULL) {
		if (pMulti->hImg != 0) {
			// non null object part

			if (fracToInt(pMulti->xPos) < left)
				// this object is further left
				left = fracToInt(pMulti->xPos);
		}
	}

	// return left-most point
	return left;
}

/**
 * Returns the right-most point of a multi-part object.
 * @param pMulti			Multi-part object
 */

int MultiRightmost(OBJECT *pMulti) {
	int right;

	// validate object pointer
	assert(isValidObject(pMulti));

	// init right-most point to first object
	right = fracToInt(pMulti->xPos) + pMulti->width;

	// for all the objects in this multi
	while ((pMulti = pMulti->pSlave) != NULL) {
		if (pMulti->hImg != 0) {
			// non null object part

			if (fracToInt(pMulti->xPos) + pMulti->width > right)
				// this object is further right
				right = fracToInt(pMulti->xPos) + pMulti->width;
		}
	}

	// return right-most point
	return right - 1;
}

/**
 * Returns the highest point of a multi-part object.
 * @param pMulti			Multi-part object
 */

int MultiHighest(OBJECT *pMulti) {
	int highest;

	// validate object pointer
	assert(isValidObject(pMulti));

	// init highest point to first object
	highest = fracToInt(pMulti->yPos);

	// for all the objects in this multi
	while ((pMulti = pMulti->pSlave) != NULL) {
		if (pMulti->hImg != 0) {
			// non null object part

			if (fracToInt(pMulti->yPos) < highest)
				// this object is higher
				highest = fracToInt(pMulti->yPos);
		}
	}

	// return highest point
	return highest;
}

/**
 * Returns the lowest point of a multi-part object.
 * @param pMulti			Multi-part object
 */

int MultiLowest(OBJECT *pMulti) {
	int lowest;

	// validate object pointer
	assert(isValidObject(pMulti));

	// init lowest point to first object
	lowest = fracToInt(pMulti->yPos) + pMulti->height;

	// for all the objects in this multi
	while ((pMulti = pMulti->pSlave) != NULL) {
		if (pMulti->hImg != 0) {
			// non null object part

			if (fracToInt(pMulti->yPos) + pMulti->height > lowest)
				// this object is lower
				lowest = fracToInt(pMulti->yPos) + pMulti->height;
		}
	}

	// return lowest point
	return lowest - 1;
}

/**
 * Returns TRUE if the object currently has an image.
 * @param pMulti		Multi-part object
 */

bool MultiHasShape(POBJECT pMulti) {
	return (pMulti->hShape != 0);
}

/**
 * Bodge for text on movies. Makes sure it appears for it's lifetime.
 * @param pMultiObj			Multi-part object to be adjusted
 */

void MultiForceRedraw(POBJECT pMultiObj) {
	// validate object pointer
	assert(isValidObject(pMultiObj));

	// for all the objects that make up this multi-part
	do {
		// signal a change in the object
		pMultiObj->flags |= DMA_CHANGED;

		// next obj in list
		pMultiObj = pMultiObj->pSlave;
	} while (pMultiObj != NULL);
}

} // End of namespace Tinsel