/* 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 "cryo/defs.h"
#include "cryo/cryo.h"
#include "cryo/platdefs.h"
#include "cryo/cryolib.h"
#include "cryo/eden.h"
#include "cryo/sound.h"

namespace Cryo {

#define CRYO_DAT_VER 1	// 32-bit integer

// Original name: prechargephrases
void EdenGame::preloadDialogs(int16 vid) {
	perso_t *perso = &_persons[PER_MORKUS];
	if (vid == 170)
		perso = &_persons[PER_UNKN_156];
	_globals->_characterPtr = perso;
	_globals->_dialogType = DialogType::dtInspect;
	int num = (perso->_id << 3) | _globals->_dialogType;
	Dialog *dial = (Dialog *)getElem(_gameDialogs, num);
	dialoscansvmas(dial);
}

////// datfile.c
void EdenGame::verifh(byte *ptr) {
	byte sum = 0;
	byte *head = ptr;

	for (int8 i = 0; i < 6; i++)
		sum += *head++;

	if (sum != 0xAB)
		return;

	debug("* Begin unpacking resource");
	head -= 6;
	uint16 h0 = READ_LE_UINT16(head);
	// 3 = 2 bytes for the uint16 and 1 byte for an unused char
	head += 3;
	uint16 h3 = READ_LE_UINT16(head);
	head += 2;
	byte *data = h0 + head + 26;
	h3 -= 6;
	head += h3;
	for (; h3; h3--)
		*data-- = *head--;
	head = data + 1;
	data = ptr;
	expandHSQ(head, data);
}

void EdenGame::openbigfile() {
	_bigfile.open("EDEN.DAT");

	char buf[16];
	int count = _bigfile.readUint16LE();
	_bigfileHeader = new PakHeaderNode(count);
	for (int j = 0; j < count; j++) {
		for (int k = 0; k < 16; k++)
			buf[k] = _bigfile.readByte();
		_bigfileHeader->_files[j]._name = Common::String(buf);
		_bigfileHeader->_files[j]._size = _bigfile.readUint32LE();
		_bigfileHeader->_files[j]._offs = _bigfile.readUint32LE();
		_bigfileHeader->_files[j]._flag = _bigfile.readByte();
	}

	_vm->_video->resetInternals();
	_vm->_video->setFile(&_bigfile);
}

void EdenGame::closebigfile() {
	_bigfile.close();
}

int EdenGame::loadmusicfile(int16 num) {
	PakHeaderItem *file = &_bigfileHeader->_files[num + 435];
	int32 size = file->_size;
	int32 offs = file->_offs;
	_bigfile.seek(offs, SEEK_SET);
	uint32 numread = size;
	if (numread > kMaxMusicSize)
		error("Music file %s is too big", file->_name.c_str());
	_bigfile.read(_musicBuf, numread);
	return size;
}

void EdenGame::loadRawFile(uint16 num, byte *buffer) {
	if (_vm->getPlatform() == Common::kPlatformDOS) {
		if ((_vm->isDemo() && num > 2204) || num > 2472)
			error("Trying to read invalid game resource");
	}

	assert(num < _bigfileHeader->_count);
	PakHeaderItem *file = &_bigfileHeader->_files[num];
	int32 size = file->_size;
	int32 offs = file->_offs;

	_bigfile.seek(offs, SEEK_SET);
	_bigfile.read(buffer, size);
}

void EdenGame::loadIconFile(uint16 num, Icon *buffer) {
	if (_vm->getPlatform() == Common::kPlatformDOS) {
		if ((_vm->isDemo() && num > 2204) || num > 2472)
			error("Trying to read invalid game resource");
	}

	assert(num < _bigfileHeader->_count);
	PakHeaderItem *file = &_bigfileHeader->_files[num];
	int32 size = file->_size;
	int32 offs = file->_offs;
	debug("* Loading icon - Resource %d (%s) at 0x%X, %d bytes", num, file->_name.c_str(), offs, size);
	_bigfile.seek(offs, SEEK_SET);

	int count = size / 18;	// sizeof(Icon)
	for (int i = 0; i < count; i++) {
		if (_vm->getPlatform() == Common::kPlatformMacintosh) {
			buffer[i].sx = _bigfile.readSint16BE();
			buffer[i].sy = _bigfile.readSint16BE();
			buffer[i].ex = _bigfile.readSint16BE();
			buffer[i].ey = _bigfile.readSint16BE();
			buffer[i]._cursorId = _bigfile.readUint16BE();
			buffer[i]._actionId = _bigfile.readUint32BE();
			buffer[i]._objectId = _bigfile.readUint32BE();
		}
		else {
			buffer[i].sx = _bigfile.readSint16LE();
			buffer[i].sy = _bigfile.readSint16LE();
			buffer[i].ex = _bigfile.readSint16LE();
			buffer[i].ey = _bigfile.readSint16LE();
			buffer[i]._cursorId = _bigfile.readUint16LE();
			buffer[i]._actionId = _bigfile.readUint32LE();
			buffer[i]._objectId = _bigfile.readUint32LE();
		}
	}
}

void EdenGame::loadRoomFile(uint16 num, Room *buffer) {
	if (_vm->getPlatform() == Common::kPlatformDOS) {
		if ((_vm->isDemo() && num > 2204) || num > 2472)
			error("Trying to read invalid game resource");
	}

	assert(num < _bigfileHeader->_count);
	PakHeaderItem *file = &_bigfileHeader->_files[num];
	int32 size = file->_size;
	int32 offs = file->_offs;
	debug("* Loading room - Resource %d (%s) at 0x%X, %d bytes", num, file->_name.c_str(), offs, size);
	_bigfile.seek(offs, SEEK_SET);

	int count = size / 14;	// sizeof(Room)
	for (int i = 0; i < count; i++) {
		buffer[i]._id = _bigfile.readByte();
		for (int j = 0; j < 4; j++)
			buffer[i]._exits[j] = _bigfile.readByte();
		buffer[i]._flags = _bigfile.readByte();
		if (_vm->getPlatform() == Common::kPlatformMacintosh) {
			buffer[i]._bank = _bigfile.readUint16BE();
			buffer[i]._party = _bigfile.readUint16BE();
		}
		else {
			buffer[i]._bank = _bigfile.readUint16LE();
			buffer[i]._party = _bigfile.readUint16LE();
		}
		buffer[i]._level = _bigfile.readByte();
		buffer[i]._video = _bigfile.readByte();
		buffer[i]._location = _bigfile.readByte();
		buffer[i]._backgroundBankNum = _bigfile.readByte();
	}
}

// Original name: shnmfl
void EdenGame::loadHnm(uint16 num) {
	unsigned int resNum = num - 1 + 485;
	assert(resNum < _bigfileHeader->_count);
	PakHeaderItem *file = &_bigfileHeader->_files[resNum];
	int size = file->_size;
	int offs = file->_offs;
	debug("* Loading movie %d (%s) at 0x%X, %d bytes", num, file->_name.c_str(), (uint)offs, size);
	_vm->_video->_file->seek(offs, SEEK_SET);
}

// Original name: ssndfl
int EdenGame::loadSound(uint16 num) {
	unsigned int resNum = num - 1 + ((_vm->getPlatform() == Common::kPlatformDOS && _vm->isDemo()) ? 656 : 661);
	assert(resNum < _bigfileHeader->_count);
	PakHeaderItem *file = &_bigfileHeader->_files[resNum];
	int32 size = file->_size;
	int32 offs = file->_offs;
	debug("* Loading sound %d (%s) at 0x%X, %d bytes", num, file->_name.c_str(), (uint)offs, size);
	if (_soundAllocated) {
		free(_voiceSamplesBuffer);
		_voiceSamplesBuffer = nullptr;
		_soundAllocated = false; //TODO: bug??? no alloc
	}
	else {
		_voiceSamplesBuffer = (byte *)malloc(size);
		_soundAllocated = true;
	}

	_bigfile.seek(offs, SEEK_SET);
	//For PC loaded data is a VOC file, on Mac version this is a raw samples
	if (_vm->getPlatform() == Common::kPlatformMacintosh)
		_bigfile.read(_voiceSamplesBuffer, size);
	else {
		// VOC files also include extra information for lipsync
		// 1. Standard VOC header
		_bigfile.read(_voiceSamplesBuffer, 0x1A);

		// 2. Lipsync?
		unsigned char chunkType = _bigfile.readByte();

		uint32 val = 0;
		_bigfile.read(&val, 3);
		unsigned int chunkLen = LE32(val);

		if (chunkType == 5) {
			_bigfile.read(_gameLipsync + 7260, chunkLen);
			chunkType = _bigfile.readByte();
			_bigfile.read(&val, 3);
			chunkLen = LE32(val);
		}

		// 3. Normal sound data
		if (chunkType == 1) {
			_bigfile.readUint16LE();
			size = chunkLen - 2;
			_bigfile.read(_voiceSamplesBuffer, size);
		}
	}

	return size;
}

void EdenGame::convertMacToPC() {
	// Convert all mac (big-endian) resources to native format
	// Array of longs
	int *p = (int *)_gameLipsync;
	for (int i = 0; i < 7240 / 4; i++)
		p[i] = BE32(p[i]);
}

void EdenGame::loadpermfiles() {
	Common::File f;
	const int kNumIcons = 136;
	const int kNumRooms = 424;
	const int kNumFollowers = 15;
	const int kNumLabyrinthPath = 70;
	const int kNumDinoSpeedForCitaLevel = 16;
	const int kNumTabletView = 12;
	const int kNumPersoRoomBankTable = 84;
	const int kNumGotos = 130;
	const int kNumObjects = 42;
	const int kNumObjectLocations = 45;
	const int kNumPersons = 58;
	const int kNumCitadel = 7;
	const int kNumCharacterRects = 19;
	const int kNumCharacters = 20;
	const int kNumAreas = 12;
	// tab_2CEF0
	// tab_2CF70
	const int kNumActionCursors = 299;

	const int expectedDataSize =
		kNumIcons * 18 +		// sizeof(Icon)
		kNumRooms * 14 +		// sizeof(Room)
		kNumFollowers * 16 +	// sizeof(Follower)
		kNumLabyrinthPath +
		kNumDinoSpeedForCitaLevel +
		kNumTabletView +
		kNumPersoRoomBankTable +
		kNumGotos * 5 +			// sizeof(Goto)
		kNumObjects * 12 +		// sizeof(object_t)
		kNumObjectLocations * 2 +
		kNumPersons * 18 +		// sizeof(perso_t)
		kNumCitadel * 34 +		// sizeof(Citadel)
		kNumCharacterRects * 8 +
		kNumCharacters * 5 +
		kNumAreas * 10 +		// (sizeof(Area) - 4)
		64 * 2 +
		64 * 2 +
		kNumActionCursors +
		12 +
		3 * 6 * 2 * 3 * 2;

	if (f.open("cryo.dat")) {
		const int dataSize = f.size() - 8 - 4;	// CRYODATA + version
		char headerId[9];

		f.read(headerId, 8);
		headerId[8] = '\0';
		if (strcmp(headerId, "CRYODATA"))
			error("Invalid cryo.dat aux data file");

		if (f.readUint32LE() != CRYO_DAT_VER)
			error("Incorrect data version for cryo.dat");

		if (dataSize != expectedDataSize)
			error("Mismatching data in cryo.dat aux data file (got %d, expected %d)", dataSize, expectedDataSize);
	}
	else
		error("Can not load cryo.dat");

	switch (_vm->getPlatform()) {
	case Common::kPlatformDOS:
		// Since PC version stores hotspots and rooms info in the executable, load them from premade resource file
		for (int i = 0; i < kNumIcons; i++) {
			_gameIcons[i].sx = f.readSint16LE();
			_gameIcons[i].sy = f.readSint16LE();
			_gameIcons[i].ex = f.readSint16LE();
			_gameIcons[i].ey = f.readSint16LE();
			_gameIcons[i]._cursorId = f.readUint16LE();
			_gameIcons[i]._actionId = f.readUint32LE();
			_gameIcons[i]._objectId = f.readUint32LE();
		}

		for (int i = 0; i < kNumRooms; i++) {
			_gameRooms[i]._id = f.readByte();
			for (int j = 0; j < 4; j++)
				_gameRooms[i]._exits[j] = f.readByte();
			_gameRooms[i]._flags = f.readByte();
			_gameRooms[i]._bank = f.readUint16LE();
			_gameRooms[i]._party = f.readUint16LE();
			_gameRooms[i]._level = f.readByte();
			_gameRooms[i]._video = f.readByte();
			_gameRooms[i]._location = f.readByte();
			_gameRooms[i]._backgroundBankNum = f.readByte();
		}
		break;
	case Common::kPlatformMacintosh:
		loadIconFile(2498, _gameIcons);
		loadRoomFile(2497, _gameRooms);
		loadRawFile(2486, _gameLipsync);
		convertMacToPC();

		// Skip the icons and rooms of the DOS version
		f.skip(kNumIcons * 14 + kNumRooms * 11);
		break;
	default:
		error("Unsupported platform");
	}

	// Read the common static data

	for (int i = 0; i < kNumFollowers; i++) {
		_followerList[i]._id = f.readSByte();
		_followerList[i]._spriteNum = f.readSByte();
		_followerList[i].sx = f.readSint16LE();
		_followerList[i].sy = f.readSint16LE();
		_followerList[i].ex = f.readSint16LE();
		_followerList[i].ey = f.readSint16LE();
		_followerList[i]._spriteBank = f.readSint16LE();
		_followerList[i].ff_C = f.readSint16LE();
		_followerList[i].ff_E = f.readSint16LE();
	}

	f.read(_labyrinthPath, kNumLabyrinthPath);
	f.read(_dinoSpeedForCitadelLevel, kNumDinoSpeedForCitaLevel);
	f.read(_tabletView, kNumTabletView);
	f.read(_personRoomBankTable, kNumPersoRoomBankTable);
	f.read(_gotos, kNumGotos * 5);	// sizeof(Goto)

	for (int i = 0; i < kNumObjects; i++) {
		_objects[i]._id = f.readByte();
		_objects[i]._flags = f.readByte();
		_objects[i]._locations = f.readUint32LE();
		_objects[i]._itemMask = f.readUint16LE();
		_objects[i]._powerMask = f.readUint16LE();
		_objects[i]._count = f.readSint16LE();
	}

	for (int i = 0; i < kNumObjectLocations; i++) {
		_objectLocations[i] = f.readUint16LE();
	}

	for (int i = 0; i < kNumPersons; i++) {
		_persons[i]._roomNum = f.readUint16LE();
		_persons[i]._actionId = f.readUint16LE();
		_persons[i]._partyMask = f.readUint16LE();
		_persons[i]._id = f.readByte();
		_persons[i]._flags = f.readByte();
		_persons[i]._roomBankId = f.readByte();
		_persons[i]._spriteBank = f.readByte();
		_persons[i]._items = f.readUint16LE();
		_persons[i]._powers = f.readUint16LE();
		_persons[i]._targetLoc = f.readByte();
		_persons[i]._lastLoc = f.readByte();
		_persons[i]._speed = f.readByte();
		_persons[i]._steps = f.readByte();
	}

	for (int i = 0; i < kNumCitadel; i++) {
		_citadelList[i]._id = f.readSint16LE();
		for (int j = 0; j < 8; j++)
			_citadelList[i]._bank[j] = f.readSint16LE();
		for (int j = 0; j < 8; j++)
			_citadelList[i]._video[j] = f.readSint16LE();
	}

	for (int i = 0; i < kNumCharacterRects; i++) {
		_characterRects[i].left = f.readSint16LE();
		_characterRects[i].top = f.readSint16LE();
		_characterRects[i].right = f.readSint16LE();
		_characterRects[i].bottom = f.readSint16LE();
	}

	f.read(_characterArray, kNumCharacters * 5);

	for (int i = 0; i < kNumAreas; i++) {
		_areasTable[i]._num = f.readByte();
		_areasTable[i]._type = f.readByte();
		_areasTable[i]._flags = f.readUint16LE();
		_areasTable[i]._firstRoomIdx = f.readUint16LE();
		_areasTable[i]._citadelLevel = f.readByte();
		_areasTable[i]._placeNum = f.readByte();
		_areasTable[i]._citadelRoomPtr = nullptr;
		_areasTable[i]._visitCount = f.readSint16LE();
	}

	for (int i = 0; i < 64; i++) {
		tab_2CEF0[i] = f.readSint16LE();
	}

	for (int i = 0; i < 64; i++) {
		tab_2CF70[i] = f.readSint16LE();
	}

	f.read(_actionCursors, kNumActionCursors);
	f.read(_mapMode, 12);
	f.read(_cubeTextureCoords, 3 * 6 * 2 * 3 * 2);

	f.close();

	loadRawFile(0, _mainBankBuf);
	loadRawFile(402, _gameFont);
	loadRawFile(404, _gameDialogs);
	loadRawFile(403, _gameConditions);
}

bool EdenGame::ReadDataSyncVOC(unsigned int num) {
	unsigned int resNum = num - 1 + ((_vm->getPlatform() == Common::kPlatformDOS && _vm->isDemo()) ? 656 : 661);
	unsigned char vocHeader[0x1A];
	int filePos = 0;
	loadpartoffile(resNum, vocHeader, filePos, sizeof(vocHeader));
	filePos += sizeof(vocHeader);
	unsigned char chunkType = 0;
	loadpartoffile(resNum, &chunkType, sizeof(vocHeader), 1);
	filePos++;
	if (chunkType == 5) {
		uint32 chunkLen = 0;
		loadpartoffile(resNum, &chunkLen, filePos, 3);
		filePos += 3;
		chunkLen = LE32(chunkLen);
		loadpartoffile(resNum, _gameLipsync + 7260, filePos, chunkLen);
		return true;
	}
	return false;
}

bool EdenGame::ReadDataSync(uint16 num) {
	if (_vm->getPlatform() == Common::kPlatformMacintosh) {
		long pos = READ_LE_UINT32(_gameLipsync + num * 4);
		if (pos != -1) {
			long len = 1024;
			loadpartoffile(1936, _gameLipsync + 7260, pos, len);
			return true;
		}
	}
	else
		return ReadDataSyncVOC(num + 1);	//TODO: remove -1 in caller
	return false;
}

void EdenGame::loadpartoffile(uint16 num, void *buffer, int32 pos, int32 len) {
	assert(num < _bigfileHeader->_count);
	PakHeaderItem *file = &_bigfileHeader->_files[num];
	int32 offs = READ_LE_UINT32(&file->_offs);
	debug("* Loading partial resource %d (%s) at 0x%X(+0x%X), %d bytes", num, file->_name.c_str(), offs, pos, len);
	_bigfile.seek(offs + pos, SEEK_SET);
	_bigfile.read(buffer, len);
}

void EdenGame::expandHSQ(byte *input, byte *output) {
	byte *src = input;
	byte *dst = output;
	byte *ptr;
	uint16 bit;        // bit
	uint16 queue = 0;  // queue
	uint16 len = 0;
	int16  ofs;
#define GetBit										\
bit = queue & 1;								\
queue >>= 1;									\
if (!queue) {									\
	queue = (src[1] << 8) | src[0]; src += 2;	\
	bit = queue & 1;							\
	queue = (queue >> 1) | 0x8000;				\
	}

	for (;;) {
		GetBit;
		if (bit)
			*dst++ = *src++;
		else {
			len = 0;
			GetBit;
			if (!bit) {
				GetBit;
				len = (len << 1) | bit;
				GetBit;
				len = (len << 1) | bit;
				ofs = 0xFF00 | *src++;      //TODO: -256
			}
			else {
				ofs = (src[1] << 8) | src[0];
				src += 2;
				len = ofs & 7;
				ofs = (ofs >> 3) | 0xE000;
				if (!len) {
					len = *src++;
					if (!len)
						break;
				}
			}
			ptr = dst + ofs;
			len += 2;
			while (len--)
				*dst++ = *ptr++;
		}
	}
}

}   // namespace Cryo