/* 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/>.
 *
 */


#include "common/endian.h"
#include "common/memstream.h"
#include "common/util.h"

#include "cine/cine.h"
#include "cine/object.h"
#include "cine/part.h"
#include "cine/various.h"

namespace Cine {

/** Resets all elements in the object table. */
void resetObjectTable() {
	for (auto &obj : g_cine->_objectTable) {
		obj.clear();
	}
}

int16 loadObject(char *pObjectName) {
	debug(5, "loadObject(\"%s\")", pObjectName);
	uint16 numEntry;
	uint16 entrySize;
	uint16 i;
	byte *ptr, *dataPtr;

	checkDataDisk(-1);

	int16 foundFileIdx = findFileInBundle(pObjectName);
	if (foundFileIdx < 0) {
		return -1;
	}

	ptr = dataPtr = readBundleFile(foundFileIdx);

	setMouseCursor(MOUSE_CURSOR_DISK);

	numEntry = READ_BE_UINT16(ptr); ptr += 2;

	entrySize = READ_BE_UINT16(ptr); ptr += 2;

	assert(numEntry <= NUM_MAX_OBJECT);

	for (i = 0; i < numEntry; i++) {
		bool overwrite =
			(g_cine->getGameType() == Cine::GType_FW && g_cine->_objectTable[i].costume != -2) ||
			(g_cine->getGameType() == Cine::GType_OS && g_cine->_objectTable[i].costume != -3);

		// HACK: Fix handling of electric razor and cable in Amiga version of Operation Stealth
		// when entering the Dr. Why's control room.
		if (hacksEnabled && g_cine->getPlatform() == Common::kPlatformAmiga &&
			g_cine->getGameType() == Cine::GType_OS && (i == 231 || i == 232) &&
			scumm_stricmp(pObjectName, "SALLE59.REL") == 0) {
			overwrite = false;
		}

		if (overwrite) {
			Common::MemoryReadStream readS(ptr, entrySize);

			g_cine->_objectTable[i].x = readS.readSint16BE();
			g_cine->_objectTable[i].y = readS.readSint16BE();
			g_cine->_objectTable[i].mask = readS.readUint16BE();
			g_cine->_objectTable[i].frame = readS.readSint16BE();
			g_cine->_objectTable[i].costume = readS.readSint16BE();
			readS.read(g_cine->_objectTable[i].name, 20);
			g_cine->_objectTable[i].part = readS.readUint16BE();
		}
		ptr += entrySize;
	}

	if (!strcmp(pObjectName, "INTRO.OBJ")) {
		for (i = 0; i < 10; i++) {
			g_cine->_objectTable[i].costume = 0;
		}
	}

	free(dataPtr);
	return 0;
}

/**
 * Remove overlay sprite from the list
 * @param objIdx Remove overlay associated with this object
 * @param param Remove overlay of this type
 */
int removeOverlay(uint16 objIdx, uint16 param) {
	Common::List<overlay>::iterator it;

	for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
		if (it->objIdx == objIdx && it->type == param) {
			g_cine->_overlayList.erase(it);
			return 1;
		}
	}

	return 0;
}

/**
 * Add new overlay sprite to the list
 * @param objIdx Associate the overlay with this object
 * @param type Type of new overlay
 * @todo Why are x, y, width and color left uninitialized?
 */
void addOverlay(uint16 objIdx, uint16 type) {
	Common::List<overlay>::iterator it;
	overlay tmp;

	for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
		// This is done for both Future Wars and Operation Stealth
		if (g_cine->_objectTable[it->objIdx].mask >= g_cine->_objectTable[objIdx].mask) {
			break;
		}

		// There are additional checks in Operation Stealth's implementation
		if (g_cine->getGameType() == Cine::GType_OS && (it->type == 2 || it->type == 3)) {
			break;
		}
	}

	// In Operation Stealth's implementation we might bail out early
	if (g_cine->getGameType() == Cine::GType_OS && it != g_cine->_overlayList.end() && it->objIdx == objIdx && it->type == type) {
		return;
	}
	
	if (g_cine->getGameType() == Cine::GType_OS && g_cine->_objectTable[it->objIdx].frame == 22 && scumm_stricmp(currentPrcName, "STARTA.PRC") == 0) {
		g_cine->sayText("Washington D.C.\nCIA Headquarters", Common::TextToSpeechManager::QUEUE);
	}

	tmp.objIdx = objIdx;
	tmp.type = type;
	tmp.x = 0;
	tmp.y = 0;
	tmp.width = 0;
	tmp.color = 0;

	g_cine->_overlayList.insert(it, tmp);
}

/**
 * Add new background mask overlay
 * @param objIdx Associate the overlay with this object
 * @param param source background index
 */
void addGfxElement(int16 objIdx, int16 param, int16 type) {
	Common::List<overlay>::iterator it;
	overlay tmp;

	for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
		if (g_cine->_objectTable[it->objIdx].mask >= g_cine->_objectTable[objIdx].mask || it->type == 2 || it->type == 3) {
			break;
		}
	}

	if (it != g_cine->_overlayList.end() && it->objIdx == objIdx && it->type == type && it->x == param) {
		return;
	}

	tmp.objIdx = objIdx;
	tmp.type = type;
	tmp.x = param;
	tmp.y = 0;
	tmp.width = 0;
	tmp.color = 0;

	g_cine->_overlayList.insert(it, tmp);
}

/**
 * Remove background mask overlay
 * @param objIdx Remove overlay associated with this object
 * @param param Remove overlay using this background
 * @todo Check that it works
 */
void removeGfxElement(int16 objIdx, int16 param, int16 type) {
	Common::List<overlay>::iterator it;

	for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
		if (it->objIdx == objIdx && it->type == type && it->x == param) {
			g_cine->_overlayList.erase(it);
			return;
		}
	}
}

void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4) {
	g_cine->_objectTable[objIdx].x = param1;
	g_cine->_objectTable[objIdx].y = param2;
	g_cine->_objectTable[objIdx].mask = param3;
	g_cine->_objectTable[objIdx].frame = param4;

	if (g_cine->getGameType() == Cine::GType_OS) {
		resetGfxEntityEntry(objIdx);
	} else { // Future Wars
		if (removeOverlay(objIdx, 0)) {
			addOverlay(objIdx, 0);
		}
	}
}

void subObjectParam(byte objIdx, byte paramIdx, int16 newValue) {
	addObjectParam(objIdx, paramIdx, -newValue);
}

void addObjectParam(byte objIdx, byte paramIdx, int16 newValue) {
	int16 currentValue = getObjectParam(objIdx, paramIdx);
	modifyObjectParam(objIdx, paramIdx, currentValue + newValue);
}

void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) {
	// Operation Stealth checks object index range, Future Wars doesn't.
	if (g_cine->getGameType() == Cine::GType_OS && objIdx >= NUM_MAX_OBJECT)
		return;

	switch (paramIdx) {
	case 1:
		g_cine->_objectTable[objIdx].x = newValue;
		break;
	case 2:
		g_cine->_objectTable[objIdx].y = newValue;
		break;
	case 3:
		g_cine->_objectTable[objIdx].mask = newValue;

		if (g_cine->getGameType() == Cine::GType_OS) { // Operation Stealth specific
			resetGfxEntityEntry(objIdx);
		} else { // Future Wars specific
			if (removeOverlay(objIdx, 0)) {
				addOverlay(objIdx, 0);
			}
		}
		break;
	case 4:
		g_cine->_objectTable[objIdx].frame = newValue;
		break;
	case 5:
		// TODO: Test if this really breaks the newspaper machine on the airport in Operation Stealth.
		if (g_cine->getGameType() == Cine::GType_FW && newValue == -1) {
			g_cine->_objectTable[objIdx].costume = g_cine->_globalVars[0];
		} else {
			g_cine->_objectTable[objIdx].costume = newValue;
		}
		break;
	case 6:
		g_cine->_objectTable[objIdx].part = newValue;
		break;
	default:
		break;
	}
}

/**
 * Check if at least one of the range B's endpoints is inside range A,
 * not counting the starting and ending points of range A.
 * Used at least by Operation Stealth's opcode 0x8D i.e. 141.
 */
bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd) {
	return (bStart > aStart && bStart < aEnd) || (bEnd > aStart && bEnd < aEnd);
}

uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2) {
	assert(objIdx1 < NUM_MAX_OBJECT && objIdx2 < NUM_MAX_OBJECT);
	const ObjectStruct &obj1 = g_cine->_objectTable[objIdx1];
	const ObjectStruct &obj2 = g_cine->_objectTable[objIdx2];

	if (compareRanges(obj1.x,    obj1.x    + xAdd1,    obj2.x,    obj2.x    + xAdd2) &&
		compareRanges(obj1.y,    obj1.y    + yAdd1,    obj2.y,    obj2.y    + yAdd2) &&
		compareRanges(obj1.mask, obj1.mask + maskAdd1, obj2.mask, obj2.mask + maskAdd2)) {
		return kCmpEQ;
	} else {
		return 0;
	}
}

uint16 compareObjectParam(byte objIdx, byte type, int16 value) {
	uint16 compareResult = 0;
	int16 objectParam = getObjectParam(objIdx, type);

	if (objectParam > value) {
		compareResult |= kCmpGT;
	} else if (objectParam < value) {
		compareResult |= kCmpLT;
	} else {
		compareResult |= kCmpEQ;
	}

	return compareResult;
}

/**
 * @bug In Operation Stealth, if you try to go downstairs to the sea in the
 * location between bank and hotel, getObjectParam is called with paramIdx 16
 * and crashes
 */
int16 getObjectParam(uint16 objIdx, uint16 paramIdx) {
	assert(objIdx <= NUM_MAX_OBJECT);

	paramIdx--;

	assert(paramIdx <= 5);

	switch (paramIdx) {
	case 0:
		return g_cine->_objectTable[objIdx].x;
	case 1:
		return g_cine->_objectTable[objIdx].y;
	case 2:
		return g_cine->_objectTable[objIdx].mask;
	case 3:
		return g_cine->_objectTable[objIdx].frame;
	case 4:
		return g_cine->_objectTable[objIdx].costume;
	case 5:
		return g_cine->_objectTable[objIdx].part;
	default:
		break;
	}

	return 0;
}

} // End of namespace Cine
