/* ScummVM - Scumm Interpreter
 * Copyright (C) 2004 Ivan Dubrov
 * Copyright (C) 2004-2006 The ScummVM project
 *
 * 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 "common/stdafx.h"
#include "common/endian.h"
#include "common/file.h"

#include "gob/gob.h"
#include "gob/saveload.h"
#include "gob/global.h"
#include "gob/draw.h"
#include "gob/video.h"

namespace Gob {

SaveLoad::SaveLoad(GobEngine *vm, const char *targetName) : _vm(vm) {
	_curSlot = -1;

	_stagesCount = 0;
	_buffer = 0;

	_tempSprite = 0;
	memset(_tempPal, 0, 768);
	_tempSpriteSize = -1;

	_saveFiles = new char*[5];

	assert(_saveFiles);

	_saveFiles[0] = new char[strlen(targetName) + 5];
	_saveFiles[1] = 0;
	_saveFiles[2] = new char[strlen(targetName) + 5];
	_saveFiles[3] = _saveFiles[0];
	_saveFiles[4] = 0;

	assert(_saveFiles[0] && _saveFiles[2]);

	sprintf(_saveFiles[0], "%s.s00", targetName);
	sprintf(_saveFiles[2], "%s.blo", targetName);
}

SaveLoad::~SaveLoad() {
	for (int i = 0; i < _stagesCount; i++)
		delete[] _buffer[i];
	delete[] _buffer;

	delete _tempSprite;

	delete[] _saveFiles[0];
	delete[] _saveFiles[2];
	delete[] _saveFiles; 
}

const char *SaveLoad::setCurSlot(int slot) {
	static char *slotBase = _saveFiles[0] + strlen(_saveFiles[0]) - 2;

	if (_curSlot != slot) {
		_curSlot = slot;

		if (_curSlot >= 0)
			snprintf(slotBase, 3, "%02d", slot);
	}

	return _saveFiles[0];
}

uint32 SaveLoad::read(Common::ReadStream &in, byte *buf,
		byte *sizes, uint32 count) {
	uint32 nRead;

	nRead = in.read(buf, count);
	if (nRead != count) {
		warning("Can't read data: requested %d, got %d", count, nRead);
		return 0;
	}

	nRead = in.read(sizes, count);
	if (nRead != count) {
		warning("Can't read data sizes: requested %d, got %d", count, nRead);
		return 0;
	}

	return count;
}

uint32 SaveLoad::write(Common::WriteStream &out, byte *buf,
		byte *sizes, uint32 count) {
	uint32 written;

	written = out.write(buf, count);
	if (written != count) {
		warning("Can't write data: requested %d, wrote %d", count, written);
		return 0;
	}

	written = out.write(sizes, count);
	if (written != count) {
		warning("Can't write data: requested %d, wrote %d", count, written);
		return 0;
	}

	return count;
}

bool SaveLoad::loadDataEndian(Common::ReadStream &in,
		int16 dataVar, uint32 size) {

	bool retVal = false;

	byte *varBuf = new byte[size];
	byte *sizeBuf = new byte[size];

	assert(varBuf && sizeBuf);

	if (read(in, varBuf, sizeBuf, size) == size) {
		if (fromEndian(varBuf, sizeBuf, size)) {
			memcpy(_vm->_global->_inter_variables + dataVar, varBuf, size);
			memcpy(_vm->_global->_inter_variablesSizes + dataVar, sizeBuf, size);
			retVal = true;
		}
	}

	delete[] varBuf;
	delete[] sizeBuf;

	return retVal;
}

bool SaveLoad::saveDataEndian(Common::WriteStream &out,
		int16 dataVar, uint32 size) {

	bool retVal = false;

	byte *varBuf = new byte[size];
	byte *sizeBuf = new byte[size];

	assert(varBuf && sizeBuf);

	memcpy(varBuf, _vm->_global->_inter_variables + dataVar, size);
	memcpy(sizeBuf, _vm->_global->_inter_variablesSizes + dataVar, size);

	if (toEndian(varBuf, sizeBuf, size))
		if (write(out, varBuf, sizeBuf, size) == size)
			retVal = true;

	delete[] varBuf;
	delete[] sizeBuf;

	return retVal;
}

int32 SaveLoad::getSize(SaveType type) {
	switch(type) {
	case kSaveNone:
		return -1;
		break;

	case kSaveGame:
		return getSizeGame();
		break;

	case kSaveTempSprite:
		return getSizeTempSprite();
		break;

	case kSaveNotes:
		return getSizeNotes();
		break;

	case kSaveScreenshot:
		return getSizeScreenshot();
		break;

	case kSaveIgnore:
		return -1;
		break;
	}

	return -1;
}

bool SaveLoad::load(SaveType type, int16 dataVar, int32 size, int32 offset) {
	switch(type) {
	case kSaveNone:
		return false;
		break;

	case kSaveGame:
		return loadGame(dataVar, size, offset);
		break;

	case kSaveTempSprite:
		return loadTempSprite(dataVar, size, offset);
		break;

	case kSaveNotes:
		return loadNotes(dataVar, size, offset);
		break;

	case kSaveScreenshot:
		return loadScreenshot(dataVar, size, offset);
		break;

	case kSaveIgnore:
		return true;
		break;
	}

	return false;
}

bool SaveLoad::save(SaveType type, int16 dataVar, int32 size, int32 offset) {
	switch(type) {
	case kSaveNone:
		return false;
		break;

	case kSaveGame:
		return saveGame(dataVar, size, offset);
		break;

	case kSaveTempSprite:
		return saveTempSprite(dataVar, size, offset);
		break;

	case kSaveNotes:
		return saveNotes(dataVar, size, offset);
		break;

	case kSaveScreenshot:
		return saveScreenshot(dataVar, size, offset);
		break;

	case kSaveIgnore:
		return true;
		break;
	}

	return false;
}

int32 SaveLoad::getSizeTempSprite() {
	return _tempSpriteSize;
}

bool SaveLoad::loadTempSprite(int16 dataVar, int32 size, int32 offset) {
	int index;
	bool readPal;

	if (size >= 0) {
		warning("Invalid attempt at loading from the temporary sprite");
		return false;
	}

	index = getSpriteIndex(size);
	readPal = getSpritePalette(size);

	if ((index < 0) || (index >= SPRITES_COUNT)) {
		warning("Index out of range while loading from the temporary "
				"sprite (%d)", index);
		return false;
	}

	return loadTempSprite(index, readPal);
}

bool SaveLoad::saveTempSprite(int16 dataVar, int32 size, int32 offset) {
	int index;
	bool readPal;

	if (size >= 0) {
		warning("Invalid attempt at saving to the temporary sprite");
		return false;
	}

	index = getSpriteIndex(size);
	readPal = getSpritePalette(size);

	if ((index < 0) || (index >= SPRITES_COUNT)) {
		warning("Index out of range while saving to the temporary sprite (%d)",
				index);
		return false;
	}

	return saveTempSprite(index, readPal);
}

bool SaveLoad::loadTempSprite(uint32 index, bool palette) {
	SurfaceDesc *sprite;

	if (palette) {
		memcpy((char *) _vm->_global->_pPaletteDesc->vgaPal,
				(char *) _tempPal, 768);
		_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
	}

	sprite = _vm->_draw->_spritesArray[index];

	if (!sprite) {
		warning("Couldn't load from the temporary sprite: "
				"No such sprite %d", index);
		return false;
	}

	if ((sprite->getWidth() != _tempSprite->getWidth()) ||
			(sprite->getHeight() != _tempSprite->getHeight())) {
		warning("Resolution doesn't match while loading from the "
				"temporary sprite (%d: %dx%d vs. %dx%d)", index,
				sprite->getWidth(), sprite->getHeight(),
				_tempSprite->getWidth(), _tempSprite->getHeight());
		return false;
	}

	_vm->_video->drawSprite(_tempSprite, sprite, 0, 0,
			sprite->getWidth() - 1, sprite->getHeight() - 1, 0, 0, 0);

	if (index == 21) {
		_vm->_draw->forceBlit();
		_vm->_video->retrace();
	}

	return true;
}

bool SaveLoad::saveTempSprite(uint32 index, bool palette) {
	SurfaceDesc *sprite = _vm->_draw->_spritesArray[index];

	if (!sprite) {
		warning("Couldn't save to the temporary sprite: "
				"No such sprite %d", index);
		return false;
	}

	delete _tempSprite;
	_tempSprite = _vm->_video->initSurfDesc(_vm->_global->_videoMode,
			sprite->getWidth(), sprite->getHeight(), 0);

	_vm->_video->drawSprite(sprite, _tempSprite, 0, 0,
			sprite->getWidth() - 1, sprite->getHeight() - 1, 0, 0, 0);

	_tempSpriteSize = _vm->_draw->getSpriteRectSize(index);

	if (palette) {
		memcpy((char *) _tempPal,
				(char *) _vm->_global->_pPaletteDesc->vgaPal, 768);
		_tempSpriteSize += 768;
	}

	return true;
}

bool SaveLoad::loadSprite(Common::ReadStream &in, int32 size) {
	SurfaceDesc *sprite;
	byte *buf;
	int nRead;
	int index;
	bool readPal;

	if (size >= 0) {
		warning("Invalid attempt at loading a sprite");
		return false;
	}

	index = getSpriteIndex(size);
	readPal = getSpritePalette(size);

	if ((index < 0) || (index >= SPRITES_COUNT)) {
		warning("Index out of range while loading a sprite (%d)",
				index);
		return false;
	}

	size = _vm->_draw->getSpriteRectSize(index);
	sprite = _vm->_draw->_spritesArray[index];

	if (!sprite) {
		warning("Couldn't load sprite: No such sprite %d", index);
		return false;
	}

	buf = new byte[MAX<int>(768, size)];
	assert(buf);

	if (readPal) {
		nRead = in.read(buf, 768);
		if (nRead != 768) {
			warning("Couldn't read a palette: requested 768, got %d", nRead);
			delete[] buf;
			return false;
		}

		memcpy((char *) _vm->_global->_pPaletteDesc->vgaPal,
				(char *) buf, 768);
		_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
	}

	nRead = in.read(buf, size);
	if (nRead != size) {
		warning("Couldn't read sprite data: requested %d, got %d", size, nRead);
		delete[] buf;
		return false;
	}

	memcpy((char *) sprite->getVidMem(), buf, size);

	delete[] buf;
	return true;
}

bool SaveLoad::saveSprite(Common::WriteStream &out, int32 size) {
	SurfaceDesc *sprite;
	int written;
	int index;
	bool readPal;

	if (size >= 0) {
		warning("Invalid attempt at saving a sprite");
		return false;
	}

	index = getSpriteIndex(size);
	readPal = getSpritePalette(size);

	if ((index < 0) || (index >= SPRITES_COUNT)) {
		warning("Index out of range while saving a sprite (%d)",
				index);
		return false;
	}

	size = _vm->_draw->getSpriteRectSize(index);
	sprite = _vm->_draw->_spritesArray[index];

	if (!sprite) {
		warning("Couldn't save sprite: No such sprite %d", index);
		return false;
	}

	if (readPal) {
		written = out.write((char *) _vm->_global->_pPaletteDesc->vgaPal, 768);
		if (written != 768) {
			warning("Couldn't write a palette: requested 768, wrote %d", written);
			return false;
		}
	}

	written = out.write((char *) sprite->getVidMem(), size);
	if (written != size) {
		warning("Couldn't write a sprite: requested %d, wrote %d",
				size, written);
		return false;
	}

	return true;
}

bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) {
	while (count-- > 0) {
		if (*sizes == 3)
			*((uint32 *) buf) = READ_LE_UINT32(buf);
		else if (*sizes == 1)
			*((uint16 *) buf) = READ_LE_UINT16(buf);
		else if (*sizes != 0) {
			warning("SaveLoad::fromEndian(): Corrupted variables sizes");
			return false;
		}

		count -= *sizes;
		buf += *sizes + 1;
		sizes += *sizes + 1;
	}

	return true;
}

bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count) {
	while (count-- > 0) {
		if (*sizes == 3)
			WRITE_LE_UINT32(buf, *((uint32 *) buf));
		else if (*sizes == 1)
			WRITE_LE_UINT16(buf, *((uint16 *) buf));
		else if (*sizes != 0) {
			warning("SaveLoad::toEndian(): Corrupted variables sizes");
			return false;
		}

		count -= *sizes;
		buf += *sizes + 1;
		sizes += *sizes + 1;
	}

	return true;
}

} // End of namespace Gob