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

#ifdef ENABLE_HE

#include "scumm/actor.h"
#include "scumm/he/intern_he.h"
#include "scumm/scumm.h"
#include "scumm/util.h"

namespace Scumm {

#define OPCODE(i, x)	_opcodes[i]._OPCODE(ScummEngine_v71he, x)

void ScummEngine_v71he::setupOpcodes() {
	ScummEngine_v70he::setupOpcodes();

	OPCODE(0xc9, o71_kernelSetFunctions);
	OPCODE(0xec, o71_copyString);
	OPCODE(0xed, o71_getStringWidth);
	OPCODE(0xef, o71_appendString);
	OPCODE(0xf0, o71_concatString);
	OPCODE(0xf1, o71_compareString);
	OPCODE(0xf5, o71_getStringLenForWidth);
	OPCODE(0xf6, o71_getCharIndexInString);
	OPCODE(0xf7, o71_findBox);
	OPCODE(0xfb, o71_polygonOps);
	OPCODE(0xfc, o71_polygonHit);
}

byte *ScummEngine_v71he::heFindResourceData(uint32 tag, byte *ptr) {
	ptr = heFindResource(tag, ptr);

	if (ptr == NULL)
		return NULL;
	return ptr + _resourceHeaderSize;
}

byte *ScummEngine_v71he::heFindResource(uint32 tag, byte *searchin) {
	uint32 curpos, totalsize, size;

	debugC(DEBUG_RESOURCE, "heFindResource(%s, %p)", tag2str(tag), (const void *)searchin);

	assert(searchin);
	searchin += 4;
	_resourceLastSearchSize = totalsize = READ_BE_UINT32(searchin);
	curpos = 8;
	searchin += 4;

	while (curpos < totalsize) {
		if (READ_BE_UINT32(searchin) == tag) {
			return searchin;
		}

		if (READ_BE_UINT32(searchin) == MKTAG('R', 'I', 'F', 'F'))
			size = READ_LE_UINT32(searchin + 4);
		else
			size = READ_BE_UINT32(searchin + 4);

		if ((int32)size <= 0) {
			error("(%s) Not found in %d... illegal block len %d", tag2str(tag), 0, size);
			return NULL;
		}

		curpos += size;
		searchin += size;
	}

	return NULL;
}

byte *ScummEngine_v71he::findWrappedBlock(uint32 tag, byte *ptr, int state, bool errorFlag) {
	if (READ_BE_UINT32(ptr) == MKTAG('M','U','L','T')) {
		byte *offs, *wrap;
		uint32 size;

		wrap = heFindResource(MKTAG('W','R','A','P'), ptr);
		if (wrap == NULL)
			return NULL;

		offs = heFindResourceData(MKTAG('O','F','F','S'), wrap);
		if (offs == NULL)
			return NULL;

		size = getResourceDataSize(offs) / 4;
		assert((uint32)state <= (uint32)size);


		offs += READ_LE_UINT32(offs + state * sizeof(uint32));
		offs = heFindResourceData(tag, offs - 8);
		if (offs)
			return offs;

		offs = heFindResourceData(MKTAG('D','E','F','A'), ptr);
		if (offs == NULL)
			return NULL;

		return heFindResourceData(tag, offs - 8);
	} else {
		return heFindResourceData(tag, ptr);
	}
}

int ScummEngine_v71he::getStringCharWidth(byte chr) {
	int charset = _string[0]._default.charset;

	byte *ptr = getResourceAddress(rtCharset, charset);
	assert(ptr);
	ptr += 29;

	int spacing = 0;

	int offs = READ_LE_UINT32(ptr + chr * 4 + 4);
	if (offs) {
		spacing = ptr[offs] + (signed char)ptr[offs + 2];
	}

	return spacing;
}

int ScummEngine_v71he::setupStringArray(int size) {
	writeVar(0, 0);
	defineArray(0, kStringArray, 0, size + 1);
	writeArray(0, 0, 0, 0);
	return readVar(0);
}

void ScummEngine_v71he::appendSubstring(int dst, int src, int srcOffs, int len) {
	int dstOffs, value;
	int i = 0;

	if (len == -1) {
		len = resStrLen(getStringAddress(src));
		srcOffs = 0;
	}

	dstOffs = resStrLen(getStringAddress(dst));

	len -= srcOffs;
	len++;

	while (i < len) {
		writeVar(0, src);
		value = readArray(0, 0, srcOffs + i);
		writeVar(0, dst);
		writeArray(0, 0, dstOffs + i, value);
		i++;
	}

	writeArray(0, 0, dstOffs + i, 0);
}

void ScummEngine_v71he::o71_kernelSetFunctions() {
	int args[29];
	int num;
	ActorHE *a;

	num = getStackList(args, ARRAYSIZE(args));

	switch (args[0]) {
	case 1:
		// Used to restore images when decorating cake in
		// Fatty Bear's Birthday Surprise
		virtScreenLoad(args[1], args[2], args[3], args[4], args[5]);
		break;
	case 20: // HE72+
		a = (ActorHE *)derefActor(args[1], "o71_kernelSetFunctions: 20");
		heQueueEraseAuxActor(a);
		break;
	case 21:
		_skipDrawObject = 1;
		break;
	case 22:
		_skipDrawObject = 0;
		break;
	case 23:
		clearCharsetMask();
		_fullRedraw = true;
		break;
	case 24:
		_disableActorDrawingFlag = true;
		for (int i = 1; i < _numActors; ++i) {
			_actors[i]->_needRedraw = false;
			_actors[i]->_needBgReset = false;
		}
		break;
	case 25:
		_disableActorDrawingFlag = false;
		redrawAllActors();
		break;
	case 26:
		a = (ActorHE *)derefActor(args[1], "o71_kernelSetFunctions: 26");
		a->_auxEraseX1 = 0;
		a->_auxEraseY1 = -1;
		a->_auxEraseX2 = 0;
		a->_auxEraseY2 = -2;
		break;
	case 30:
		a = (ActorHE *)derefActor(args[1], "o71_kernelSetFunctions: 30");
		a->_clipOverride.bottom = args[2];
		break;
	case 42:
		_wiz->_useWizClipRect = true;
		_wiz->_wizClipRect.left = args[1];
		_wiz->_wizClipRect.top = args[2];
		_wiz->_wizClipRect.right = args[3];
		_wiz->_wizClipRect.bottom = args[4];
		break;
	case 43:
		_wiz->_useWizClipRect = false;
		break;
	default:
		error("o71_kernelSetFunctions: default case %d (param count %d)", args[0], num);
	}
}

void ScummEngine_v71he::o71_copyString() {
	int dst, size;
	int src = pop();

	size = resStrLen(getStringAddress(src)) + 1;
	dst = setupStringArray(size);

	appendSubstring(dst, src, -1, -1);

	push(dst);
}

void ScummEngine_v71he::o71_getStringWidth() {
	int array, pos, len;
	int chr, width = 0;

	len = pop();
	pos = pop();
	array = pop();

	if (len == -1) {
		pos = 0;
		len = resStrLen(getStringAddress(array));
	}

	writeVar(0, array);
	while (pos <= len) {
		chr = readArray(0, 0, pos);
		if (chr == 0)
			break;
		width += getStringCharWidth(chr);
		pos++;
	}

	push(width);
}

void ScummEngine_v71he::o71_appendString() {
	int dst, size;

	int len = pop();
	int srcOffs = pop();
	int src = pop();

	size = len - srcOffs + 2;
	dst = setupStringArray(size);

	appendSubstring(dst, src, srcOffs, len);

	push(dst);
}

void ScummEngine_v71he::o71_concatString() {
	int dst, size;

	int src2 = pop();
	int src1 = pop();

	size = resStrLen(getStringAddress(src1));
	size += resStrLen(getStringAddress(src2)) + 1;
	dst = setupStringArray(size);

	appendSubstring(dst, src1, 0, -1);
	appendSubstring(dst, src2, 0, -1);

	push(dst);
}

void ScummEngine_v71he::o71_compareString() {
	int result;

	int array1 = pop();
	int array2 = pop();

	byte *string1 = getStringAddress(array1);
	if (!string1)
		error("o71_compareString: Reference to zeroed array pointer (%d)", array1);

	byte *string2 = getStringAddress(array2);
	if (!string2)
		error("o71_compareString: Reference to zeroed array pointer (%d)", array2);

	while (*string1 == *string2) {
		if (*string2 == 0) {
			push(0);
			return;
		}

		string1++;
		string2++;
	}

	result = (*string1 > *string2) ? -1 : 1;
	push(result);
}

void ScummEngine_v71he::o71_getStringLenForWidth() {
	int chr, max;
	int array, len, pos, width = 0;

	max = pop();
	pos = pop();
	array = pop();

	len = resStrLen(getStringAddress(array));

	writeVar(0, array);
	while (pos <= len) {
		chr = readArray(0, 0, pos);
		width += getStringCharWidth(chr);
		if (width >= max) {
			push(pos);
			return;
		}
		pos++;
	}

	push(len);
}

void ScummEngine_v71he::o71_getCharIndexInString() {
	int array, end, len, pos, value;

	value = pop();
	end = pop();
	pos = pop();
	array = pop();

	if (end >= 0) {
		len = resStrLen(getStringAddress(array));
		if (len < end)
			end = len;
	} else {
		end = 0;
	}

	if (pos < 0)
		pos = 0;

	writeVar(0, array);
	if (end > pos) {
		while (end >= pos) {
			if (readArray(0, 0, pos) == value) {
				push(pos);
				return;
			}
			pos++;
		}
	} else {
		while (end <= pos) {
			if (readArray(0, 0, pos) == value) {
				push(pos);
				return;
			}
			pos--;
		}
	}

	push(-1);
}

void ScummEngine_v71he::o71_findBox() {
	int y = pop();
	int x = pop();
	push(getSpecialBox(x, y));
}

void ScummEngine_v71he::o71_polygonOps() {
	int vert1x, vert1y, vert2x, vert2y, vert3x, vert3y, vert4x, vert4y;
	int id, fromId, toId;
	bool flag;

	byte subOp = fetchScriptByte();

	switch (subOp) {
	case ScummEngine_v100he::SO_SET_POLYGON: // HE 100
	case ScummEngine_v100he::SO_SET_POLYGON_LOCAL: // HE 100
	case SO_SET_POLYGON:
	case SO_SET_POLYGON_LOCAL:
		vert4y = pop();
		vert4x = pop();
		vert3y = pop();
		vert3x = pop();
		vert2y = pop();
		vert2x = pop();
		vert1y = pop();
		vert1x = pop();
		flag = (subOp == ScummEngine_v100he::SO_SET_POLYGON_LOCAL || subOp == SO_SET_POLYGON_LOCAL);
		id = pop();
		_wiz->set4Polygon(id, flag, vert1x, vert1y, vert2x, vert2y, vert3x, vert3y, vert4x, vert4y);
		break;
	case ScummEngine_v100he::SO_DELETE_POLYGON: // HE 100
	case SO_DELETE_POLYGON:
		toId = pop();
		fromId = pop();
		_wiz->deletePolygon(fromId, toId);
		break;
	default:
		error("o71_polygonOps: default case %d", subOp);
	}
}

void ScummEngine_v71he::o71_polygonHit() {
	int y = pop();
	int x = pop();
	push(_wiz->findPolygon(x, y));
}

} // End of namespace Scumm

#endif // ENABLE_HE
