/* 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.
 *
 * $URL$
 * $Id$
 *
 */

#include "scumm/scumm_v4.h"
#include "scumm/object.h"

namespace Scumm {

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

void ScummEngine_v4::setupOpcodes() {
	ScummEngine_v5::setupOpcodes();

	OPCODE(0x25, o5_drawObject);
	OPCODE(0x45, o5_drawObject);
	OPCODE(0x65, o5_drawObject);
	OPCODE(0xa5, o5_drawObject);
	OPCODE(0xc5, o5_drawObject);
	OPCODE(0xe5, o5_drawObject);

	OPCODE(0x50, o4_pickupObject);
	OPCODE(0xd0, o4_pickupObject);

	OPCODE(0x5c, o4_oldRoomEffect);
	OPCODE(0xdc, o4_oldRoomEffect);

	OPCODE(0x0f, o4_ifState);
	OPCODE(0x4f, o4_ifState);
	OPCODE(0x8f, o4_ifState);
	OPCODE(0xcf, o4_ifState);

	OPCODE(0x2f, o4_ifNotState);
	OPCODE(0x6f, o4_ifNotState);
	OPCODE(0xaf, o4_ifNotState);
	OPCODE(0xef, o4_ifNotState);

	OPCODE(0xa7, o4_saveLoadVars);

	OPCODE(0x22, o4_saveLoadGame);
	OPCODE(0xa2, o4_saveLoadGame);
}

void ScummEngine_v4::o4_ifState() {
	int a = getVarOrDirectWord(PARAM_1);
	int b = getVarOrDirectByte(PARAM_2);

	jumpRelative(getState(a) == b);
}

void ScummEngine_v4::o4_ifNotState() {
	int a = getVarOrDirectWord(PARAM_1);
	int b = getVarOrDirectByte(PARAM_2);

	jumpRelative(getState(a) != b);
}

void ScummEngine_v4::o4_pickupObject() {
	int obj = getVarOrDirectWord(PARAM_1);

	if (obj < 1) {
		error("pickupObjectOld received invalid index %d (script %d)", obj, vm.slot[_currentScript].number);
	}

	if (getObjectIndex(obj) == -1)
		return;

	if (whereIsObject(obj) == WIO_INVENTORY)	// Don't take an object twice
		return;

	// debug(0, "adding %d from %d to inventoryOld", obj, _currentRoom);
	addObjectToInventory(obj, _roomResource);
	markObjectRectAsDirty(obj);
	putOwner(obj, VAR(VAR_EGO));
	putClass(obj, kObjectClassUntouchable, 1);
	putState(obj, 1);
	clearDrawObjectQueue();
	runInventoryScript(1);
}

void ScummEngine_v4::o4_oldRoomEffect() {
	int a;

	_opcode = fetchScriptByte();
	if ((_opcode & 0x1F) == 3) {
		a = getVarOrDirectWord(PARAM_1);

#if 1
		if (_game.platform == Common::kPlatformFMTowns && _game.version == 3) {
			// FIXME / TODO: OK the first thing to note is: at least in Zak256,
			// maybe also in other games, this opcode does a bit more. I added
			// some stubs here, but somebody with a full IDA or more knowledge
			// about this will have to fill in the gaps. At least now we know
			// that something is missing here :-)

			if (a == 4) {
				//printf("o5_oldRoomEffect ODDBALL: _opcode = 0x%x, a = 0x%x\n", _opcode, a);
				// No idea what byte_2FCCF is, but it's a globale boolean flag.
				// I only add it here as a temporary hack to make the pseudo code compile.
				// Maybe it is just there as a reentry protection guard, given
				// how it is used? It might also correspond to _screenEffectFlag.
				int byte_2FCCF = 0;

				// For now, we force a redraw of the screen background. This
				// way the Zak end credits seem to work mostly correct.
				VirtScreen *vs = &_virtscr[kMainVirtScreen];
				restoreBackground(Common::Rect(0, vs->topline, vs->w, vs->topline + vs->h));
				vs->setDirtyRange(0, vs->h);
				updateDirtyScreen(kMainVirtScreen);

				if (byte_2FCCF) {
					// Here now "sub_1C44" is called, which sets byte_2FCCF to 0 then
					// calls yet another sub (which also reads byte_2FCCF):

					byte_2FCCF = 0;
					//call sub_0BB3


					// Now sub_085C is called. This is quite simply: it sets
					// 0xF000 bytes. starting at 0x40000 to 0. No idea what that
					// buffer is, maybe a screen buffer, though. Note that
					// 0xF000 = 320*192.
					// Maybe this is also the charset mask being cleaned?

					// call sub_085C


					// And then sub_1C54 is called, which is almost identical to
					// the above sub_1C44, only it sets byte_2FCCF to 1:

					byte_2FCCF = 1;
					// call sub_0BB3

				} else {
					// Here only sub_085C is called (see comment above)

					// call sub_085C
				}
			return;
			}
#endif

		}
		if (a) {
			_switchRoomEffect = (byte)(a & 0xFF);
			_switchRoomEffect2 = (byte)(a >> 8);
		} else {
			fadeIn(_newEffect);
		}
	}
}

void ScummEngine_v4::o4_saveLoadVars() {
	if (fetchScriptByte() == 1)
		saveVars();
	else
		loadVars();
}

enum StringIds {
	// The string IDs used by Indy3 to store the episode resp. series IQ points.
	// Note that we save the episode IQ points but load the series IQ points,
	// which matches the original Indy3 save/load code. See also the notes
	// on Feature Request #1666521.
	STRINGID_IQ_EPISODE = 7,
	STRINGID_IQ_SERIES = 9,
	// The string IDs of the first savegame name, used as an offset to determine
	// the IDs of all savenames.
	// Loom is the only game whose savenames start with a different ID.
	STRINGID_SAVENAME1 = 10,
	STRINGID_SAVENAME1_LOOM = 9
};

void ScummEngine_v4::saveVars() {
	int a, b;

	while ((_opcode = fetchScriptByte()) != 0) {
		switch (_opcode & 0x1F) {
		case 0x01: // write a range of variables
			getResultPos();
			a = _resultVarNumber;
			getResultPos();
			b = _resultVarNumber;
			debug(0, "stub saveVars: vars %d -> %d", a, b);
			break;
		case 0x02: // write a range of string variables
			a = getVarOrDirectByte(PARAM_1);
			b = getVarOrDirectByte(PARAM_2);

			if (a == STRINGID_IQ_EPISODE && b == STRINGID_IQ_EPISODE) {
				if (_game.id == GID_INDY3) {
					saveIQPoints();
				}
				break;
			}
			// FIXME: changing savegame-names not supported
			break;
		case 0x03: // open file
			a = resStrLen(_scriptPointer);
			strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
			_saveLoadVarsFilename[a] = '\0';
			_scriptPointer += a + 1;
			break;
		case 0x04:
			return;
		case 0x1F: // close file
			_saveLoadVarsFilename[0] = '\0';
			return;
		}
	}
}

void ScummEngine_v4::loadVars() {
	int a, b;

	while ((_opcode = fetchScriptByte()) != 0) {
		switch (_opcode & 0x1F) {
		case 0x01: // read a range of variables
			getResultPos();
			a = _resultVarNumber;
			getResultPos();
			b = _resultVarNumber;
			debug(0, "stub loadVars: vars %d -> %d", a, b);
			break;
		case 0x02: // read a range of string variables
			a = getVarOrDirectByte(PARAM_1);
			b = getVarOrDirectByte(PARAM_2);

			int slot;
			int slotSize;
			byte* slotContent;
			int savegameId;
			bool avail_saves[100];

			if (a == STRINGID_IQ_SERIES && b == STRINGID_IQ_SERIES) {
				// Zak256 loads the IQ script-slot but does not use it -> ignore it
				if (_game.id == GID_INDY3) {
					byte *ptr = getResourceAddress(rtString, STRINGID_IQ_SERIES);
					if (ptr) {
						int size = getResourceSize(rtString, STRINGID_IQ_SERIES);
						loadIQPoints(ptr, size);
					}
				}
				break;
			}

			listSavegames(avail_saves, ARRAYSIZE(avail_saves));
			for (slot = a; slot <= b; ++slot) {
				slotSize = getResourceSize(rtString, slot);
				slotContent = getResourceAddress(rtString, slot);

				// load savegame names
				savegameId = slot - a + 1;
				Common::String name;
				if (avail_saves[savegameId] && getSavegameName(savegameId, name)) {
					int pos;
					const char *ptr = name.c_str();
					// slotContent ends with {'\0','@'} -> max. length = slotSize-2
					for (pos = 0; pos < slotSize - 2; ++pos) {
						if (!ptr[pos])
							break;
						// replace special characters
						if (ptr[pos] >= 32 && ptr[pos] <= 122 && ptr[pos] != 64)
							slotContent[pos] = ptr[pos];
						else
							slotContent[pos] = '_';
					}
					slotContent[pos] = '\0';
				} else {
					slotContent[0] = '\0';
				}
			}
			break;
		case 0x03: // open file
			a = resStrLen(_scriptPointer);
			strncpy(_saveLoadVarsFilename, (const char *)_scriptPointer, a);
			_saveLoadVarsFilename[a] = '\0';
			_scriptPointer += a + 1;
			break;
		case 0x04:
			return;
		case 0x1F: // close file
			_saveLoadVarsFilename[0] = '\0';
			return;
		}
	}
}

/**
 * IQ Point calculation for Indy3.
 * The scripts that perform this task are
 * - script-9 (save/load dialog initialization, loads room 14),
 * - room-14-204 (load series IQ string),
 * - room-14-205 (save series IQ string),
 * - room-14-206 (calculate series IQ string).
 * Unfortunately script-9 contains lots of GUI stuff so calling this script
 * directly is not possible. The other scripts depend on script-9.
 */
void ScummEngine_v4::updateIQPoints() {
	int seriesIQ;
	// IQString[0..72] corresponds to each puzzle's IQ.
	// IQString[73] indicates that the IQ-file was loaded successfully and is always 0 when
	// the IQ is calculated, hence it will be ignored here.
	const int NUM_PUZZLES = 73;
	byte seriesIQString[NUM_PUZZLES];
	byte *episodeIQString;
	int episodeIQStringSize;

	// load string with IQ points given per puzzle in any savegame
	// IMPORTANT: the resource string STRINGID_IQ_SERIES is only valid while
	// the original save/load dialog is executed, so do not use it here.
	memset(seriesIQString, 0, sizeof(seriesIQString));
	loadIQPoints(seriesIQString, sizeof(seriesIQString));

	// string with IQ points given per puzzle in current savegame
	episodeIQString = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
	if (!episodeIQString)
		return;
	episodeIQStringSize = getResourceSize(rtString, STRINGID_IQ_EPISODE);
	if (episodeIQStringSize < NUM_PUZZLES)
		return;

	// merge episode and series IQ strings and calculate series IQ
	seriesIQ = 0;
	// iterate over puzzles
	for (int i = 0; i < NUM_PUZZLES ; ++i) {
		byte puzzleIQ = seriesIQString[i];
		// if puzzle is solved copy points to episode string
		if (puzzleIQ > 0)
			episodeIQString[i] = puzzleIQ;
		// add puzzle's IQ-points to series IQ
		seriesIQ += episodeIQString[i];
	}
	_scummVars[245] = seriesIQ;

	// save series IQ string
	saveIQPoints();
}

void ScummEngine_v4::saveIQPoints() {
	// save Indy3 IQ-points
	Common::OutSaveFile *file;
	Common::String filename = _targetName + ".iq";

	file = _saveFileMan->openForSaving(filename);
	if (file != NULL) {
		byte *ptr = getResourceAddress(rtString, STRINGID_IQ_EPISODE);
		if (ptr) {
			int size = getResourceSize(rtString, STRINGID_IQ_EPISODE);
			file->write(ptr, size);
		}
		delete file;
	}
}

void ScummEngine_v4::loadIQPoints(byte *ptr, int size) {
	// load Indy3 IQ-points
	Common::InSaveFile *file;
	Common::String filename = _targetName + ".iq";

	file = _saveFileMan->openForLoading(filename);
	if (file != NULL) {
		byte *tmp = (byte*)malloc(size);
		int nread = file->read(tmp, size);
		if (nread == size) {
			memcpy(ptr, tmp, size);
		}
		free(tmp);
		delete file;
	}
}

void ScummEngine_v4::o4_saveLoadGame() {
	getResultPos();
	byte slot;
	byte a = getVarOrDirectByte(PARAM_1);
	byte result = 0;

	if ((_game.id == GID_MANIAC && _game.version <= 1) || (_game.id == GID_ZAK && _game.platform == Common::kPlatformC64)) {
		// Convert V0/V1 load/save screen (they support only one savegame per disk)
		// 1 Load
		// 2 Save
		slot = 1;
		if (a == 1)
			_opcode = 0x40;
		else if ((a == 2) || (_game.platform == Common::kPlatformNES))
			_opcode = 0x80;
	} else {
		slot = a & 0x1F;
		// Slot numbers in older games start with 0, in newer games with 1
		if (_game.version <= 2)
			slot++;
		_opcode = a & 0xE0;
	}

	switch (_opcode) {
	case 0x00: // num slots available
		result = 100;
		break;
	case 0x20: // drive
		if (_game.version <= 3) {
			// 0 = ???
			// [1,2] = disk drive [A:,B:]
			// 3 = hard drive
			result = 3;
		} else {
			// set current drive
			result = 1;
		}
		break;
	case 0x40: // load
		if (loadState(slot, false))
			result = 3; // sucess
		else
			result = 5; // failed to load
		break;
	case 0x80: // save
		if (_game.version <= 3) {
			char name[32];
			if (_game.version <= 2) {
				// use generic name
				sprintf(name, "Game %c", 'A'+slot-1);
			} else {
				// use name entered by the user
				char* ptr;
				int firstSlot = (_game.id == GID_LOOM) ? STRINGID_SAVENAME1_LOOM : STRINGID_SAVENAME1;
				ptr = (char*)getStringAddress(slot + firstSlot - 1);
				strncpy(name, ptr, sizeof(name));
			}

			if (savePreparedSavegame(slot, name))
				result = 0;
			else
				result = 2;
		} else {
			result = 2; // failed to save
		}
		break;
	case 0xC0: // test if save exists
		{
		Common::InSaveFile *file;
		bool avail_saves[100];

		listSavegames(avail_saves, ARRAYSIZE(avail_saves));
		Common::String filename = makeSavegameName(slot, false);
		if (avail_saves[slot] && (file = _saveFileMan->openForLoading(filename))) {
			result = 6; // save file exists
			delete file;
		} else
			result = 7; // save file does not exist
		}
		break;
	default:
		error("o4_saveLoadGame: unknown subopcode %d", _opcode);
	}

	setResult(result);
}

} // End of namespace Scumm