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

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

#include "gob/gob.h"
#include "gob/save/saveconverter.h"
#include "gob/save/savefile.h"
#include "gob/save/savehandler.h"

namespace Gob {

SaveConverter::SaveConverter(GobEngine *vm, const Common::String &fileName)
: _vm(vm), _fileName(fileName) {

	_data = 0;
	_stream = 0;
}

SaveConverter::~SaveConverter() {
	delete _stream;
	delete[] _data;
}

void SaveConverter::clear() {
	delete[] _data;
	delete _stream;

	_data = 0;
	_stream = 0;
}

void SaveConverter::setFileName(const Common::String &fileName) {
	clear();
	_fileName = fileName;
}

Common::InSaveFile *SaveConverter::openSave() const {
	if (_fileName.empty())
		return 0;

	Common::SaveFileManager *saveMan = g_system->getSavefileManager();
	return saveMan->openForLoading(_fileName);
}

void SaveConverter::displayWarning() const {
	warning("Old save format detected, trying to convert. If this does not work, your "
	        "save is broken and can't be used anymore. Sorry for the inconvenience");
}

char *SaveConverter::getDescription(const Common::String &fileName) {
	setFileName(fileName);
	return getDescription();
}

char *SaveConverter::getDescription() const {
	Common::InSaveFile *save;

	// Test if it's an old savd
	if (!isOldSave(&save) || !save)
		return 0;

	char *desc = getDescription(*save);

	delete save;
	return desc;
}

uint32 SaveConverter::getActualSize(Common::InSaveFile **save) const {
	Common::InSaveFile *saveFile = openSave();

	if (!saveFile)
		return false;

	// Is it a valid new save?
	if (SaveContainer::isSave(*saveFile)) {
		delete saveFile;
		return false;
	}

	int32 saveSize = saveFile->size();

	if (saveSize <= 0) {
		delete saveFile;
		return 0;
	}

	if (save)
		*save = saveFile;
	else
		delete saveFile;

	return saveSize;
}

bool SaveConverter::swapDataEndian(byte *data, const byte *sizes, uint32 count) {
	if (!data || !sizes || (count == 0))
		return false;

	while (count-- > 0) {
		if      (*sizes == 3) // 32bit value (3 additional bytes)
			WRITE_UINT32(data, SWAP_BYTES_32(READ_UINT32(data)));
		else if (*sizes == 1) // 16bit value (1 additional byte)
			WRITE_UINT16(data, SWAP_BYTES_16(READ_UINT16(data)));
		else if (*sizes != 0) // else, it has to be an 8bit value
			return false;

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

	return true;
}

SavePartInfo *SaveConverter::readInfo(Common::SeekableReadStream &stream,
	uint32 descLength, bool hasSizes) const {

	uint32 varSize = SaveHandler::getVarSize(_vm);
	if (varSize == 0)
		return 0;

	char *desc = getDescription(stream);
	if (!desc)
		return 0;

	// If it has sizes, skip them
	if (hasSizes)
		if (!stream.skip(descLength)) {
			delete[] desc;
			return 0;
		}

	SavePartInfo *info = new SavePartInfo(descLength, (uint32) _vm->getGameType(),
			0, _vm->getEndianness(), varSize);

	info->setDesc(desc);

	delete[] desc;

	return info;
}

byte *SaveConverter::readData(Common::SeekableReadStream &stream,
		uint32 count, bool endian) const {

	byte *data = new byte[count];

	// Read variable data
	if (stream.read(data, count) != count) {
		delete[] data;
		return 0;
	}

	/* Check the endianness. The old save data was always written
	 * as little endian, so we might need to swap the bytes. */

	if (endian && (_vm->getEndianness() == kEndiannessBE)) {
		// Big endian => swapping needed

		// Read variable sizes
		byte *sizes = new byte[count];
		if (stream.read(sizes, count) != count) {
			delete[] data;
			delete[] sizes;
			return 0;
		}

		// Swap bytes
		if (!swapDataEndian(data, sizes, count)) {
			delete[] data;
			delete[] sizes;
			return 0;
		}

		delete[] sizes;

	} else {
		// Little endian => just skip the sizes part

		if (!stream.skip(count)) {
			delete[] data;
			return 0;
		}
	}

	return data;
}

SavePartVars *SaveConverter::readVars(Common::SeekableReadStream &stream,
		uint32 count, bool endian) const {

	byte *data = readData(stream, count, endian);
	if (!data)
		return 0;

	SavePartVars *vars = new SavePartVars(_vm, count);

	// Read variables into part
	if (!vars->readFromRaw(data, count)) {
		delete[] data;
		delete vars;
		return 0;
	}

	delete[] data;
	return vars;
}

SavePartMem *SaveConverter::readMem(Common::SeekableReadStream &stream,
		uint32 count, bool endian) const {

	byte *data = readData(stream, count, endian);
	if (!data)
		return 0;

	SavePartMem *mem = new SavePartMem(count);

	// Read mem into part
	if (!mem->readFrom(data, 0, count)) {
		delete[] data;
		delete mem;
		return 0;
	}

	delete[] data;
	return mem;
}

SavePartSprite *SaveConverter::readSprite(Common::SeekableReadStream &stream,
		uint32 width, uint32 height, bool palette) const {

	assert((width > 0) && (height > 0));

	uint32 spriteSize = width * height;

	byte pal[768];
	if (palette)
		if (stream.read(pal, 768) != 768)
			return 0;

	byte *data = new byte[spriteSize];

	// Read variable data
	if (stream.read(data, spriteSize) != spriteSize) {
		delete[] data;
		return 0;
	}

	SavePartSprite *sprite = new SavePartSprite(width, height);

	if (!sprite->readSpriteRaw(data, spriteSize)) {
		delete[] data;
		delete sprite;
		return 0;
	}

	delete[] data;

	if (palette)
		if (!sprite->readPalette(pal))
			return 0;

	return sprite;
}

bool SaveConverter::createStream(SaveWriter &writer) {
	// Allocate memory for the internal new save data
	uint32 contSize = writer.getSize();
	_data = new byte[contSize];

	// Save the newly created new save data
	Common::MemoryWriteStream writeStream(_data, contSize);
	if (!writer.save(writeStream))
		return false;

	// Create a reading stream upon that new save data
	_stream = new Common::MemoryReadStream(_data, contSize);

	return true;
}

/* Stream functions. If the new save data stream is available, redirect the stream
 * operations to that stream. Normal stream error behavior if not. */

bool SaveConverter::err() const {
	if (!_data || !_stream)
		return true;

	return _stream->err();
}

void SaveConverter::clearErr() {
	if (!_data || !_stream)
		return;

	_stream->clearErr();
}

bool SaveConverter::eos() const {
	if (!_data || !_stream)
		return true;

	return _stream->eos();
}

uint32 SaveConverter::read(void *dataPtr, uint32 dataSize) {
	if (!_data || !_stream)
		return 0;

	return _stream->read(dataPtr, dataSize);
}

int32 SaveConverter::pos() const {
	if (!_data || !_stream)
		return -1;

	return _stream->pos();
}

int32 SaveConverter::size() const {
	if (!_data || !_stream)
		return -1;

	return _stream->size();
}

bool SaveConverter::seek(int32 offset, int whence) {
	if (!_data || !_stream)
		return false;

	return _stream->seek(offset, whence);
}


SaveConverter_Notes::SaveConverter_Notes(GobEngine *vm, uint32 notesSize,
		const Common::String &fileName) : SaveConverter(vm, fileName) {

	_size = notesSize;
}

SaveConverter_Notes::~SaveConverter_Notes() {
}

int SaveConverter_Notes::isOldSave(Common::InSaveFile **save) const {
	if (_size == 0)
		return 0;

	uint32 saveSize = getActualSize(save);
	if (saveSize == 0)
		return 0;

	// The size of the old save always follows that rule
	if (saveSize == (_size * 2))
		return 1;

	// Not an old save, clean up
	if (save) {
		delete *save;
		*save = 0;
	}

	return 0;
}

char *SaveConverter_Notes::getDescription(Common::SeekableReadStream &save) const  {
	return 0;
}

bool SaveConverter_Notes::loadFail(SavePartVars *vars, Common::InSaveFile *save) {
	delete vars;
	delete save;

	clear();

	return false;
}

// Loads the old save by constructing a new save containing the old save's data
bool SaveConverter_Notes::load() {
	if (_size == 0)
		return false;

	Common::InSaveFile *save;

	// Test if it's an old savd
	if (!isOldSave(&save) || !save)
		return false;

	displayWarning();

	SaveWriter writer(1, 0);

	SavePartVars *vars = readVars(*save, _size, false);
	if (!vars)
		return loadFail(0, save);

	// We don't need the save anymore
	delete save;

	// Write all parts
	if (!writer.writePart(0, vars))
		return loadFail(0, 0);

	// We don't need this anymore
	delete vars;

	// Create the final read stream
	if (!createStream(writer))
		return loadFail(0, 0);

	return true;
}

} // End of namespace Gob
