/* ScummVM - Scumm Interpreter
 * Copyright (C) 2003-2005 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 *
 */

#include "stdafx.h"
#include "scummsys.h"
#include "sword1/memman.h"
#include "sword1/resman.h"
#include "sword1/sworddefs.h"
#include "base/engine.h"
#include "common/config-manager.h"
#include "common/util.h"
#include "common/str.h"
#include "sword1/swordres.h"

#include "gui/message.h"
#include "gui/newgui.h"

namespace Sword1 {
	void guiFatalError(char *msg) {
		// Displays a dialog on-screen before terminating the engine.
		// TODO: We really need to setup a special palette for cases when
		// the engine is erroring before setting one... otherwise invisible cursor :)

		GUI::MessageDialog dialog(msg);
		dialog.runModal();
		error(msg);
}

#define MAX_PATH_LEN 260

ResMan::ResMan(const char *fileName) {
	_openCluStart = _openCluEnd = NULL;
	_openClus = 0;
	_memMan = new MemMan();
	loadCluDescript(fileName);
}

ResMan::~ResMan(void) {
#if 0
	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++) {
		Clu *cluster = _prj.clu[clusCnt];
		if (cluster) {
			for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++) {
				Grp *group = cluster->grp[grpCnt];
				if (group) {
					for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++) {
						if (group->resHandle[resCnt].cond == MEM_DONT_FREE) {
							warning("ResMan::~ResMan: Resource %02X.%04X.%02X is still open",
								clusCnt + 1, grpCnt, resCnt);
						}
					}
				}
			}
		}
	}
	debug(0, "ResMan closed\n");
#endif
	flush();
	freeCluDescript();
	delete _memMan;
}

void ResMan::loadCluDescript(const char *fileName) {
	Common::File file;
	file.open(fileName);

	if (!file.isOpen()) {
		char msg[512];
		sprintf(msg, "Couldn't open CLU description '%s'\n\nIf you are running from CD, please ensure you have read the ScummVM documentation regarding multi-cd games.", fileName);
		guiFatalError(msg);
	}

	
	_prj.noClu = file.readUint32LE();
	_prj.clu = new Clu[_prj.noClu];
	memset(_prj.clu, 0, _prj.noClu * sizeof(Clu));

	uint32 *cluIndex = (uint32*)malloc(_prj.noClu * 4);
	file.read(cluIndex, _prj.noClu * 4);

	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++)
		if (cluIndex[clusCnt]) {
			Clu *cluster = _prj.clu + clusCnt;
			file.read(cluster->label, MAX_LABEL_SIZE);

			cluster->file = NULL;
			cluster->noGrp = file.readUint32LE();
			cluster->grp = new Grp[cluster->noGrp];
			cluster->nextOpen = NULL;
			memset(cluster->grp, 0, cluster->noGrp * sizeof(Grp));
			cluster->refCount = 0;

			uint32 *grpIndex = (uint32*)malloc(cluster->noGrp * 4);
			file.read(grpIndex, cluster->noGrp * 4);

			for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++)
				if (grpIndex[grpCnt]) {
					Grp *group = cluster->grp + grpCnt;
					group->noRes = file.readUint32LE();
					group->resHandle = new MemHandle[group->noRes];
					group->offset = new uint32[group->noRes];
					group->length = new uint32[group->noRes];
					uint32 *resIdIdx = (uint32*)malloc(group->noRes * 4);
					file.read(resIdIdx, group->noRes * 4);

					for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++) {
						if (resIdIdx[resCnt]) {
							group->offset[resCnt] = file.readUint32LE();
							group->length[resCnt] = file.readUint32LE();
							_memMan->initHandle(group->resHandle + resCnt);
						} else {
							group->offset[resCnt] = 0xFFFFFFFF;
							group->length[resCnt] = 0;
							_memMan->initHandle(group->resHandle + resCnt);
						}
					}
					free(resIdIdx);
				}
			free(grpIndex);
		}
	free(cluIndex);
	
	if (_prj.clu[3].grp[5].noRes == 29)
		for (uint8 cnt = 0; cnt < 29; cnt++)
			_srIdList[cnt] = 0x04050000 | cnt;
}

void ResMan::freeCluDescript(void) {
	
	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++) {
		Clu *cluster = _prj.clu + clusCnt;
		for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++) {
			Grp *group = cluster->grp + grpCnt;
			if (group->resHandle != NULL) {
				for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++)
					_memMan->freeNow(group->resHandle + resCnt);

				delete[] group->resHandle;
				delete[] group->offset;
				delete[] group->length;
			}
		}
		delete[] cluster->grp;

		if (cluster->file != NULL)
			delete cluster->file;
	}
	delete[] _prj.clu;
}

void ResMan::flush(void) {
	for (uint32 clusCnt = 0; clusCnt < _prj.noClu; clusCnt++) {
		Clu *cluster = _prj.clu + clusCnt;
		for (uint32 grpCnt = 0; grpCnt < cluster->noGrp; grpCnt++) {
			Grp *group = cluster->grp + grpCnt;
			for (uint32 resCnt = 0; resCnt < group->noRes; resCnt++)
				if (group->resHandle[resCnt].cond != MEM_FREED) {
					_memMan->setCondition(group->resHandle + resCnt, MEM_CAN_FREE);
					group->resHandle[resCnt].refCount = 0;
				}
		}
		if (cluster->file) {
			cluster->file->close();
			delete cluster->file;
			cluster->file = NULL;
			cluster->refCount = 0;
		}
	}
	_openClus = 0;
	_openCluStart = _openCluEnd = NULL;
	// the memory manager cached the blocks we asked it to free, so explicitly make it free them
	_memMan->flush();
}

void *ResMan::fetchRes(uint32 id) {
	MemHandle *memHandle = resHandle(id);
	if (!memHandle->data)
		error("fetchRes:: resource %d is not open!", id);
	return memHandle->data;
}

void *ResMan::openFetchRes(uint32 id) {
	resOpen(id);
	return fetchRes(id);
}

void ResMan::dumpRes(uint32 id) {
	char outn[30];
	sprintf(outn, "DUMP%08X.BIN", id);
	Common::File outf;
	if (outf.open(outn, Common::File::kFileWriteMode)) {
		resOpen(id);
		MemHandle *memHandle = resHandle(id);
		outf.write(memHandle->data, memHandle->size);
		outf.close();
		resClose(id);
	}
}

Header *ResMan::lockScript(uint32 scrID) {
	if (!_scriptList[scrID / ITM_PER_SEC])
		error("Script id %d not found.\n", scrID);
	scrID = _scriptList[scrID / ITM_PER_SEC];
#ifdef SCUMM_BIG_ENDIAN
	MemHandle *memHandle = resHandle(scrID);
	if (memHandle->cond == MEM_FREED)
		openScriptResourceBigEndian(scrID);
	else
		resOpen(scrID);
#else
	resOpen(scrID);
#endif
	return (Header*)resHandle(scrID)->data;
}

void ResMan::unlockScript(uint32 scrID) {
	resClose(_scriptList[scrID / ITM_PER_SEC]);
}

void *ResMan::cptResOpen(uint32 id) {
#ifdef SCUMM_BIG_ENDIAN
	MemHandle *memHandle = resHandle(id);
	if (memHandle->cond == MEM_FREED)
		openCptResourceBigEndian(id);
	else
		resOpen(id);
#else
	resOpen(id);
#endif
	return resHandle(id)->data;
}

void ResMan::resOpen(uint32 id) {  // load resource ID into memory
	MemHandle *memHandle = resHandle(id);
	if (memHandle->cond == MEM_FREED) { // memory has been freed
		uint32 size = resLength(id);
		_memMan->alloc(memHandle, size);
		Common::File *clusFile = resFile(id);
		assert(clusFile);
		clusFile->seek( resOffset(id) );
		clusFile->read( memHandle->data, size);
		if (clusFile->ioFailed())
			error("Can't read %d bytes from cluster %d\n", size, id);
	} else
		_memMan->setCondition(memHandle, MEM_DONT_FREE);

	memHandle->refCount++;
	if (memHandle->refCount > 20) {
		debug(1, "%d references to id %d. Guess there's something wrong.", memHandle->refCount, id);
	}
}

void ResMan::resClose(uint32 id) {
	MemHandle *handle = resHandle(id);
	if (!handle->refCount) {
		warning("Resource Manager fail: unlocking object with refCount 0. Id: %d\n", id);		
	} else {
		handle->refCount--;
		if (!handle->refCount)
			_memMan->setCondition( handle, MEM_CAN_FREE);
	}
}

FrameHeader *ResMan::fetchFrame(void *resourceData, uint32 frameNo) {
	uint8 *frameFile = (uint8*)resourceData;
	uint8 *idxData = frameFile + sizeof(Header);
	if (frameNo >= READ_LE_UINT32(idxData))
		error("fetchFrame:: frame %d doesn't exist in resource.", frameNo);
	frameFile += READ_LE_UINT32(idxData + (frameNo+1) * 4);
	return (FrameHeader*)frameFile;
}

Common::File *ResMan::resFile(uint32 id) {
	Clu *cluster = _prj.clu + ((id >> 24) - 1);
	if (cluster->file == NULL) {
		_openClus++;
		if (_openCluEnd == NULL) {
			_openCluStart = _openCluEnd = cluster;
		} else {
			_openCluEnd->nextOpen = cluster;
			_openCluEnd = cluster;
		}
		cluster->file = new Common::File();
		char fileName[15];
		sprintf(fileName, "%s.CLU", _prj.clu[(id >> 24)-1].label);
		cluster->file->open(fileName);

		if (!cluster->file->isOpen()) {
			char msg[512];
			sprintf(msg, "Couldn't open game cluster file '%s'\n\nIf you are running from CD, please ensure you have read the ScummVM documentation regarding multi-cd games.", fileName);
			guiFatalError(msg);
		}
		while (_openClus > MAX_OPEN_CLUS) {
			assert(_openCluStart);
			Clu *closeClu = _openCluStart;
			_openCluStart = _openCluStart->nextOpen;
			
			closeClu->file->close();
			delete closeClu->file;
			closeClu->file = NULL;
			closeClu->nextOpen = NULL;
			_openClus--;
		}
	}
	return cluster->file;
}

MemHandle *ResMan::resHandle(uint32 id) {
	if ((id >> 16) == 0x0405)
		id = _srIdList[id & 0xFFFF];
	uint8 cluster = (uint8)((id >> 24) - 1);
	uint8 group = (uint8)(id >> 16);

	return &(_prj.clu[cluster].grp[group].resHandle[id & 0xFFFF]);
}

uint32 ResMan::resLength(uint32 id) {
	if ((id >> 16) == 0x0405)
		id = _srIdList[id & 0xFFFF];
	uint8 cluster = (uint8)((id >> 24) - 1);
	uint8 group = (uint8)(id >> 16);

	return _prj.clu[cluster].grp[group].length[id & 0xFFFF];
}

uint32 ResMan::resOffset(uint32 id) {
	if ((id >> 16) == 0x0405)
		id = _srIdList[id & 0xFFFF];
	uint8 cluster = (uint8)((id >> 24) - 1);
	uint8 group = (uint8)(id >> 16);

	return _prj.clu[cluster].grp[group].offset[id & 0xFFFF];
}

void ResMan::openCptResourceBigEndian(uint32 id) {
	resOpen(id);
	MemHandle *handle = resHandle(id);
	uint32 totSize = handle->size;
	uint32 *data = (uint32*)((uint8*)handle->data + sizeof(Header));
	totSize -= sizeof(Header);
	if (totSize & 3)
		error("Illegal compact size for id %d: %d", id, totSize);
	totSize /= 4;
	for (uint32 cnt = 0; cnt < totSize; cnt++) {
		*data = READ_LE_UINT32(data);
		data++;
	}
}

void ResMan::openScriptResourceBigEndian(uint32 id) {
	resOpen(id);
	MemHandle *handle = resHandle(id);
	// uint32 totSize = handle->size;
	Header *head = (Header*)handle->data;
	head->comp_length = FROM_LE_32(head->comp_length);
	head->decomp_length = FROM_LE_32(head->decomp_length);
	head->version = FROM_LE_16(head->version);
	uint32 *data = (uint32*)((uint8*)handle->data + sizeof(Header));
	uint32 size = handle->size - sizeof(Header);
	if (size & 3)
		error("Odd size during script endian conversion. Resource ID =%d, size = %d", id, size);
	size >>= 2;
	for (uint32 cnt = 0; cnt < size; cnt++) {
		*data = READ_LE_UINT32(data);
		data++;
	}
}

uint32 ResMan::_srIdList[29] = { // the file numbers differ for the control panel file IDs, so we need this array
	OTHER_SR_FONT,		// SR_FONT
	0x04050000,			// SR_BUTTON
	OTHER_SR_REDFONT,	// SR_REDFONT
	0x04050001,			// SR_PALETTE
	0x04050002,			// SR_PANEL_ENGLISH
	0x04050003,			// SR_PANEL_FRENCH
	0x04050004,			// SR_PANEL_GERMAN
	0x04050005,			// SR_PANEL_ITALIAN
	0x04050006,			// SR_PANEL_SPANISH
	0x04050007,			// SR_PANEL_AMERICAN
	0x04050008,			// SR_TEXT_BUTTON
	0x04050009,			// SR_SPEED
	0x0405000A,			// SR_SCROLL1
	0x0405000B,			// SR_SCROLL2
	0x0405000C,			// SR_CONFIRM
	0x0405000D,			// SR_VOLUME
	0x0405000E,			// SR_VLIGHT
	0x0405000F,			// SR_VKNOB
	0x04050010,			// SR_WINDOW
	0x04050011,			// SR_SLAB1
	0x04050012,			// SR_SLAB2
	0x04050013,			// SR_SLAB3
	0x04050014,			// SR_SLAB4
	0x04050015,			// SR_BUTUF
	0x04050016,			// SR_BUTUS
	0x04050017,			// SR_BUTDS
	0x04050018,			// SR_BUTDF
	0x04050019,			// SR_DEATHPANEL
	0,
};

} // End of namespace Sword1