/* ScummVM - Scumm Interpreter
 * Copyright (C) 2006 The ScummVM project
 *
 * Copyright (C) 1999-2003 Sarien Team
 *
 * 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$
 *
 */

/*
 * Savegame support by Vasyl Tsvirkunov <vasyl@pacbell.net>
 * Multi-slots by Claudio Matsuoka <claudio@helllabs.org>
 */

#include "common/stdafx.h"
#include "common/file.h"

#include "agi/agi.h"
#include "agi/graphics.h"
#include "agi/sprite.h"
#include "agi/keyboard.h"
#include "agi/menu.h"

#define SAVEGAME_VERSION 2

/*
 * Version 0 (Sarien): view table has 64 entries
 * Version 1 (Sarien): view table has 256 entries (needed in KQ3)
 * Version 2 (ScummVM): first ScummVM version
 */

namespace Agi {

static const uint32 AGIflag=MKID_BE('AGI:');

int AgiEngine::saveGame(const char *fileName, const char *description) {
	char gameIDstring[8]="gameIDX";
	int i;
	struct ImageStackElement *ptr = _imageStack;
	Common::OutSaveFile *out;

	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName, description);
	if (!(out = _saveFileMan->openForSaving(fileName))) {
		warning("Can't create file '%s', game not saved", fileName);
		return errBadFileOpen;
	} else {
		debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for writing", fileName);
	}

	out->writeUint32BE(AGIflag);
	out->write(description, 31);

	out->writeByte(SAVEGAME_VERSION);
	debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_VERSION);

	out->writeByte(_game.state);
	debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing game state (%d)", _game.state);
	
	strcpy(gameIDstring, _game.id);
	out->write(gameIDstring, 8);
	debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing game id (%s, %s)", gameIDstring, _game.id);

	for (i = 0; i < MAX_FLAGS; i++)
		out->writeByte(_game.flags[i]);
	for (i = 0; i < MAX_VARS; i++)
		out->writeByte(_game.vars[i]);

	out->writeSint16BE((int8)_game.horizon);
	out->writeSint16BE((int16)_game.lineStatus);
	out->writeSint16BE((int16)_game.lineUserInput);
	out->writeSint16BE((int16)_game.lineMinPrint);

	out->writeSint16BE((int16)_game.inputMode);
	out->writeSint16BE((int16)_game.lognum);

	out->writeSint16BE((int16)_game.playerControl);
	out->writeSint16BE((int16)_game.quitProgNow);
	out->writeSint16BE((int16)_game.statusLine);
	out->writeSint16BE((int16)_game.clockEnabled);
	out->writeSint16BE((int16)_game.exitAllLogics);
	out->writeSint16BE((int16)_game.pictureShown);
	out->writeSint16BE((int16)_game.hasPrompt);
	out->writeSint16BE((int16)_game.gameFlags);

	out->writeSint16BE((int16)_game.inputEnabled);

	for (i = 0; i < _HEIGHT; i++)
		out->writeByte(_game.priTable[i]);
	
	out->writeSint16BE((int16)_game.gfxMode);
	out->writeByte(_game.cursorChar);
	out->writeSint16BE((int16)_game.colorFg);
	out->writeSint16BE((int16)_game.colorBg);

	/* game.hires */
	/* game.sbuf */
	/* game.ego_words */
	/* game.num_ego_words */

	out->writeSint16BE((int16)_game.numObjects);
	for (i = 0; i < (int16)_game.numObjects; i++)
		out->writeSint16BE((int16)objectGetLocation(i));

	/* game.ev_keyp */
	for (i = 0; i < MAX_STRINGS; i++)
		out->write(_game.strings[i], MAX_STRINGLEN);

	/* record info about loaded resources */
	for (i = 0; i < MAX_DIRS; i++) {
		out->writeByte(_game.dirLogic[i].flags);
		out->writeSint16BE((int16)_game.logics[i].sIP);
		out->writeSint16BE((int16)_game.logics[i].cIP);
	}
	for (i = 0; i < MAX_DIRS; i++)
		out->writeByte(_game.dirPic[i].flags);
	for (i = 0; i < MAX_DIRS; i++)
		out->writeByte(_game.dirView[i].flags);
	for (i = 0; i < MAX_DIRS; i++)
		out->writeByte(_game.dirSound[i].flags);

	/* game.pictures */
	/* game.logics */
	/* game.views */
	/* game.sounds */

	for (i = 0; i < MAX_VIEWTABLE; i++) {
		VtEntry *v = &_game.viewTable[i];

		out->writeByte(v->stepTime);
		out->writeByte(v->stepTimeCount);
		out->writeByte(v->entry);
		out->writeSint16BE(v->xPos);
		out->writeSint16BE(v->yPos);
		out->writeByte(v->currentView);

		/* v->view_data */

		out->writeByte(v->currentLoop);
		out->writeByte(v->numLoops);

		/* v->loop_data */

		out->writeByte(v->currentCel);
		out->writeByte(v->numCels);

		/* v->cel_data */
		/* v->cel_data_2 */

		out->writeSint16BE(v->xPos2);
		out->writeSint16BE(v->yPos2);

		/* v->s */

		out->writeSint16BE(v->xSize);
		out->writeSint16BE(v->ySize);
		out->writeByte(v->stepSize);
		out->writeByte(v->cycleTime);
		out->writeByte(v->cycleTimeCount);
		out->writeByte(v->direction);

		out->writeByte(v->motion);
		out->writeByte(v->cycle);
		out->writeByte(v->priority);

		out->writeUint16BE(v->flags);

		out->writeByte(v->parm1);
		out->writeByte(v->parm2);
		out->writeByte(v->parm3);
		out->writeByte(v->parm4);
	}

	/* Save image stack */

	for (i = 0; i < _imageStackPointer; i++) {
		ptr = &_imageStack[i];
		out->writeByte(ptr->type);
		out->writeSint16BE(ptr->parm1);
		out->writeSint16BE(ptr->parm2);
		out->writeSint16BE(ptr->parm3);
		out->writeSint16BE(ptr->parm4);
		out->writeSint16BE(ptr->parm5);
		out->writeSint16BE(ptr->parm6);
		out->writeSint16BE(ptr->parm7);
	}
	out->writeByte(0);

	out->finalize();
	if (out->ioFailed())
		warning("Can't write file '%s'. (Disk full?)", fileName);
	else
		debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", description, fileName);
	
	delete out;
	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName);
	return errOK;
}

int AgiEngine::loadGame(const char *fileName) {
	char description[31], saveVersion, loadId[8];
	int i, vtEntries = MAX_VIEWTABLE;
	uint8 t;
	int16 parm[7];
	Common::InSaveFile *in;

	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::loadGame(%s)", fileName);

	if (!(in = _saveFileMan->openForLoading(fileName))) {
		warning("Can't open file '%s', game not loaded", fileName);
		return errBadFileOpen;
	} else {
		debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName);
	}

	uint32 typea = in->readUint32BE();
	if (typea == AGIflag) {
		debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
	} else {
		warning("This doesn't appear to be an AGI savegame, game not restored");
		delete in;	
		return errOK;
	} 

	in->read(description, 31);
	
	debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Description is: %s", description);
	
	saveVersion = in->readByte();
	if (saveVersion != SAVEGAME_VERSION)
		warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_VERSION);

	_game.state = in->readByte();
	
	in->read(loadId, 8);
	if (strcmp(loadId, _game.id)) {
		delete in;	
		warning("This save seems to be from a different AGI game (save from %s, running %s), not loaded", loadId, _game.id);
		return errBadFileOpen;
	}

	for (i = 0; i < MAX_FLAGS; i++)
		_game.flags[i] = in->readByte();
	for (i = 0; i < MAX_VARS; i++)
		_game.vars[i] = in->readByte();

	_game.horizon = in->readSint16BE();
	_game.lineStatus = in->readSint16BE();
	_game.lineUserInput = in->readSint16BE();
	_game.lineMinPrint = in->readSint16BE();
	
	/* These are never saved */
	_game.cursorPos = 0;
	_game.inputBuffer[0] = 0;
	_game.echoBuffer[0] = 0;
	_game.keypress = 0;

	_game.inputMode = in->readSint16BE();
	_game.lognum = in->readSint16BE();

	_game.playerControl = in->readSint16BE();
	_game.quitProgNow = in->readSint16BE();
	_game.statusLine = in->readSint16BE();
	_game.clockEnabled = in->readSint16BE();
	_game.exitAllLogics = in->readSint16BE();
	_game.pictureShown = in->readSint16BE();
	_game.hasPrompt = in->readSint16BE();
	_game.gameFlags = in->readSint16BE();
	_game.inputEnabled = in->readSint16BE();

	for (i = 0; i < _HEIGHT; i++)
		_game.priTable[i] = in->readByte();

	if (_game.hasWindow)
		closeWindow();

	_game.msgBoxTicks = 0;
	_game.block.active = false;
	/* game.window - fixed by close_window() */
	/* game.has_window - fixed by close_window() */

	_game.gfxMode = in->readSint16BE();
	_game.cursorChar = in->readByte();
	_game.colorFg = in->readSint16BE();
	_game.colorBg = in->readSint16BE();

	/* game.hires - rebuilt from image stack */
	/* game.sbuf - rebuilt from image stack */

	/* game.ego_words - fixed by clean_input */
	/* game.num_ego_words - fixed by clean_input */

	_game.numObjects = in->readSint16BE();
	for (i = 0; i < (int16)_game.numObjects; i++)
		objectSetLocation(i, in->readSint16BE());

	/* Those are not serialized */
	for (i = 0; i < MAX_DIRS; i++) {
		_game.evKeyp[i].occured = false;
	}

	for (i = 0; i < MAX_STRINGS; i++)
		in->read(_game.strings[i], MAX_STRINGLEN);

	for (i = 0; i < MAX_DIRS; i++) {
		if (in->readByte() & RES_LOADED)
			agiLoadResource(rLOGIC, i);
		else
			agiUnloadResource(rLOGIC, i);
		_game.logics[i].sIP = in->readSint16BE();
		_game.logics[i].cIP = in->readSint16BE();
	}

	for (i = 0; i < MAX_DIRS; i++) {
		if (in->readByte() & RES_LOADED)
			agiLoadResource(rPICTURE, i);
		else
			agiUnloadResource(rPICTURE, i);
	}

	for (i = 0; i < MAX_DIRS; i++) {
		if (in->readByte() & RES_LOADED)
			agiLoadResource(rVIEW, i);
		else
			agiUnloadResource(rVIEW, i);
	}

	for (i = 0; i < MAX_DIRS; i++) {
		if (in->readByte() & RES_LOADED)
			agiLoadResource(rSOUND, i);
		else
			agiUnloadResource(rSOUND, i);
	}

	/* game.pictures - loaded above */
	/* game.logics - loaded above */
	/* game.views - loaded above */
	/* game.sounds - loaded above */

	for (i = 0; i < vtEntries; i++) {
		VtEntry *v = &_game.viewTable[i];

		v->stepTime = in->readByte();
		v->stepTimeCount = in->readByte();
		v->entry = in->readByte();
		v->xPos = in->readSint16BE();
		v->yPos = in->readSint16BE();
		v->currentView = in->readByte();

		/* v->view_data - fixed below  */

		v->currentLoop = in->readByte();
		v->numLoops = in->readByte();

		/* v->loop_data - fixed below  */

		v->currentCel = in->readByte();
		v->numCels = in->readByte();

		/* v->cel_data - fixed below  */
		/* v->cel_data_2 - fixed below  */

		v->xPos2 = in->readSint16BE();
		v->yPos2 = in->readSint16BE();

		/* v->s - fixed below */

		v->xSize = in->readSint16BE();
		v->ySize = in->readSint16BE();
		v->stepSize = in->readByte();
		v->cycleTime = in->readByte();
		v->cycleTimeCount = in->readByte();
		v->direction = in->readByte();

		v->motion = in->readByte();
		v->cycle = in->readByte();
		v->priority = in->readByte();

		v->flags = in->readUint16BE();

		v->parm1 = in->readByte();
		v->parm2 = in->readByte();
		v->parm3 = in->readByte();
		v->parm4 = in->readByte();
	}
	for (i = vtEntries; i < MAX_VIEWTABLE; i++) {
		memset(&_game.viewTable[i], 0, sizeof(VtEntry));
	}

	/* Fix some pointers in viewtable */

	for (i = 0; i < MAX_VIEWTABLE; i++) {
		VtEntry *v = &_game.viewTable[i];

		if (_game.dirView[v->currentView].offset == _EMPTY)
			continue;

		if (!(_game.dirView[v->currentView].flags & RES_LOADED))
			agiLoadResource(rVIEW, v->currentView);

		setView(v, v->currentView);	/* Fix v->view_data */
		setLoop(v, v->currentLoop);	/* Fix v->loop_data */
		setCel(v, v->currentCel);	/* Fix v->cel_data */
		v->celData2 = v->celData;
		v->s = NULL;	/* not sure if it is used... */
	}

	_sprites->eraseBoth();

	/* Clear input line */
	_gfx->clearScreen(0);
	writeStatus();

	/* Recreate background from saved image stack */
	clearImageStack();
	while ((t = in->readByte()) != 0) {
		for (i = 0; i < 7; i++)
			parm[i] = in->readSint16BE();
		replayImageStackCall(t, parm[0], parm[1], parm[2],
				parm[3], parm[4], parm[5], parm[6]);
	}

	delete in;	
	debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName);

	setflag(fRestoreJustRan, true);

	_game.hasPrompt = 0;	/* force input line repaint if necessary */
	cleanInput();

	_sprites->eraseBoth();
	_sprites->blitBoth();
	_sprites->commitBoth();
	_picture->showPic();
	_gfx->doUpdate();

	return errOK;
}

#define NUM_SLOTS 12

const char *AgiEngine::getSavegameFilename(int num) {
	static char saveLoadSlot[12];
	sprintf(saveLoadSlot, "%s.%.3d", _targetName.c_str(), num);
	return saveLoadSlot;
}
	
int AgiEngine::selectSlot() {
	int i, key, active = 0;
	int rc = -1;
	int hm = 2, vm = 3;	/* box margins */
	int xmin, xmax, slotClicked;
	char desc[NUM_SLOTS][40];
	int textCentre, buttonLength, buttonX[2], buttonY;
	const char *buttonText[] = { "  OK  ", "Cancel", NULL };

	for (i = 0; i < NUM_SLOTS; i++) {
		char fileName[MAX_PATH];
		Common::InSaveFile *in;
		
		debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str());
		sprintf(fileName, "%s", getSavegameFilename(i));
		if (!(in = _saveFileMan->openForLoading(fileName))) {
			debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName);
			strcpy(desc[i], "          (empty slot)");
		} else {
			debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName);
			uint32 type = in->readUint32BE();
			if (type == AGIflag) {
				debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
				in->read(desc[i], 31);
			} else {
				warning("This doesn't appear to be an AGI savegame");
				strcpy(desc[i], "(corrupt file)");
			} 

			delete in;
		}
	}

	textCentre = GFX_WIDTH / CHAR_LINES / 2;
	buttonLength = 6;
	buttonX[0] = (textCentre - 3 * buttonLength / 2) * CHAR_COLS;
	buttonX[1] = (textCentre + buttonLength / 2) * CHAR_COLS;
	buttonY = (vm + 17) * CHAR_LINES;
	
	for (i = 0; i < 2; i++)
	_gfx->drawButton(buttonX[i], buttonY, buttonText[i], 0, 0, MSG_BOX_TEXT, MSG_BOX_COLOUR);

	for (;;) {
		char dstr[64];
		for (i = 0; i < NUM_SLOTS; i++) {
			sprintf(dstr, "[%-32.32s]", desc[i]);
			printText(dstr, 0, hm + 1, vm + 4 + i,
					(40 - 2 * hm) - 1, i == active ? MSG_BOX_COLOUR : MSG_BOX_TEXT,
					i == active ? MSG_BOX_TEXT : MSG_BOX_COLOUR);
		}
		
		_gfx->pollTimer();	/* msdos driver -> does nothing */
		key = doPollKeyboard();
		switch (key) {
		case KEY_ENTER:
			rc = active;
			strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
			goto press;
		case KEY_ESCAPE:
			rc = -1;
			goto getout;
		case BUTTON_LEFT:
			if (_gfx->testButton(buttonX[0], buttonY, buttonText[0])) {
				rc = active;
				strncpy(_game.strings[MAX_STRINGS], desc[i], MAX_STRINGLEN);
				goto press;
			}
			if (_gfx->testButton(buttonX[1], buttonY, buttonText[1])) {
				rc = -1;
				goto getout;
			}
			xmin = (hm + 1) * CHAR_COLS;
			xmax = xmin + CHAR_COLS * 34;
			if ((int)g_mouse.x >= xmin && (int)g_mouse.x <= xmax) {
				slotClicked = ((int)g_mouse.y-1)/CHAR_COLS-(vm+4);
				if (slotClicked >= 0 && slotClicked < NUM_SLOTS) 
					active = slotClicked;
			}
			break;
		case KEY_DOWN:
			active++;
			active %= NUM_SLOTS;
			break;
		case KEY_UP:
			active--;
			if (active < 0)
				active = NUM_SLOTS - 1;
			break;
		}
		_gfx->doUpdate();
	}

press:
	debugC(8, kDebugLevelMain | kDebugLevelInput, "Button pressed: %d", rc);

getout:
	closeWindow();
	return rc;
}

int AgiEngine::saveGameDialog() {
	char fileName[MAX_PATH];
	char *desc;
	const char *buttons[] = { "Do as I say!", "I regret", NULL };
	char dstr[200];
	int rc, slot = 0;
	int hm, vm, hp, vp;	
	int w;

	hm = 2;
	vm = 3;
	hp = hm * CHAR_COLS;
	vp = vm * CHAR_LINES;
	w = (40 - 2 * hm) - 1;

	sprintf(fileName, "%s", getSavegameFilename(slot));

	drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp);
	printText("Select a slot in which you wish to save the game:",
			0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOUR);

	slot = selectSlot();
	if (slot < 0)	
		return errOK;

	drawWindow(hp, vp + 5 * CHAR_LINES, GFX_WIDTH - hp,
			GFX_HEIGHT - vp - 9 * CHAR_LINES);
	printText("Enter a description for this game:",
			0, hm + 1, vm + 6, w, MSG_BOX_TEXT, MSG_BOX_COLOUR);
	_gfx->drawRectangle(3 * CHAR_COLS, 11 * CHAR_LINES - 1,
			37 * CHAR_COLS, 12 * CHAR_LINES, MSG_BOX_TEXT);
	_gfx->flushBlock(3 * CHAR_COLS, 11 * CHAR_LINES - 1,
			37 * CHAR_COLS, 12 * CHAR_LINES);

	getString(2, 11, 31, MAX_STRINGS);
	_gfx->printCharacter(3, 11, _game.cursorChar, MSG_BOX_COLOUR, MSG_BOX_TEXT);
	do {
		mainCycle();
	} while (_game.inputMode == INPUT_GETSTRING);
	closeWindow();

	desc = _game.strings[MAX_STRINGS];
	sprintf(dstr, "Are you sure you want to save the game "
			"described as:\n\n%s\n\nin slot %d?\n\n\n", desc, slot);

	rc = selectionBox(dstr, buttons);

	if (rc != 0) {
		messageBox("Game NOT saved.");
		return errOK;
	}

	sprintf(fileName, "%s", getSavegameFilename(slot));
	debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName);

	saveGame(fileName, desc);

	messageBox("Game saved.");

	return errOK;
}

int AgiEngine::saveGameSimple() {
	char fileName[MAX_PATH];

	sprintf(fileName, "%s", getSavegameFilename(0));
	saveGame(fileName, "Default savegame");

	return errOK;
}

int AgiEngine::loadGameDialog() {
	char fileName[MAX_PATH];
	int rc, slot = 0;
	int hm, vm, hp, vp;	/* box margins */
	int w;

	hm = 2;
	vm = 3;
	hp = hm * CHAR_COLS;
	vp = vm * CHAR_LINES;
	w = (40 - 2 * hm) - 1;

	sprintf(fileName, "%s", getSavegameFilename(slot));

	_sprites->eraseBoth();
	_sound->stopSound();

	drawWindow(hp, vp, GFX_WIDTH - hp, GFX_HEIGHT - vp);
	printText("Select a game which you wish to\nrestore:",
			0, hm + 1, vm + 1, w, MSG_BOX_TEXT, MSG_BOX_COLOUR);

	slot = selectSlot();

	if (slot < 0) {
		messageBox("Game NOT restored.");
		return errOK;
	}

	sprintf(fileName, "%s", getSavegameFilename(slot));

	if ((rc = loadGame(fileName)) == errOK) {
		messageBox("Game restored.");
		_game.exitAllLogics = 1;
		_menu->enableAll();
	} else {
		messageBox("Error restoring game.");
	}

	return rc;
}

int AgiEngine::loadGameSimple() {
	char fileName[MAX_PATH];
	int rc = 0;

	sprintf(fileName, "%s", getSavegameFilename(0));

	_sprites->eraseBoth();
	_sound->stopSound();
	closeWindow();

	if ((rc = loadGame(fileName)) == errOK) {
		messageBox("Game restored.");
		_game.exitAllLogics = 1;
		_menu->enableAll();
	} else {
		messageBox("Error restoring game.");
	}

	return rc;
}

} // End of namespace Agi