/* 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/config-manager.h"
#include "common/fs.h"
#include "common/iff_container.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/textconsole.h"
#include "image/iff.h"
#include "parallaction/parser.h"
#include "parallaction/parallaction.h"


namespace Parallaction {


//  HACK: Several archives ('de', 'en', 'fr' and 'disk0') in the multi-lingual
//  Amiga version of Nippon Safes, and one archive ('fr') in the Amiga Demo of
//  Nippon Safes used different internal offsets than all the other archives.
//
//  When an archive is opened in the Amiga demo, its size is checked against
//  SIZEOF_SMALL_ARCHIVE to detect when the smaller archive is used.
//
//  When an archive is opened in Amiga multi-lingual version, the header is
//  checked again NDOS to detect when a smaller archive is used.
//
#define SIZEOF_SMALL_ARCHIVE		12778

#define ARCHIVE_FILENAMES_OFS		0x16

#define NORMAL_ARCHIVE_FILES_NUM	384
#define SMALL_ARCHIVE_FILES_NUM		180

#define NORMAL_ARCHIVE_SIZES_OFS	0x3016
#define SMALL_ARCHIVE_SIZES_OFS		0x1696

#define NORMAL_ARCHIVE_DATA_OFS		0x4000
#define SMALL_ARCHIVE_DATA_OFS		0x1966

#define MAX_ARCHIVE_ENTRIES			384

class NSArchive : public Common::Archive {

	Common::SeekableReadStream	*_stream;

	char			_archiveDir[MAX_ARCHIVE_ENTRIES][32];
	uint32			_archiveLengths[MAX_ARCHIVE_ENTRIES];
	uint32			_archiveOffsets[MAX_ARCHIVE_ENTRIES];
	uint32			_numFiles;

	uint32			lookup(const char *name) const;

public:
	NSArchive(Common::SeekableReadStream *stream, Common::Platform platform, uint32 features);
	~NSArchive();

	Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
	bool hasFile(const Common::String &name) const;
	int listMembers(Common::ArchiveMemberList &list) const;
	const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
};


NSArchive::NSArchive(Common::SeekableReadStream *stream, Common::Platform platform, uint32 features) : _stream(stream) {
	if (!_stream) {
		error("NSArchive: invalid stream passed to constructor");
	}

	bool isSmallArchive = false;
	if (platform == Common::kPlatformAmiga) {
		if (features & GF_DEMO) {
			isSmallArchive = stream->size() == SIZEOF_SMALL_ARCHIVE;
		} else if (features & GF_LANG_MULT) {
			isSmallArchive = (stream->readUint32BE() != MKTAG('N','D','O','S'));
		}
	}

	_numFiles = (isSmallArchive) ? SMALL_ARCHIVE_FILES_NUM : NORMAL_ARCHIVE_FILES_NUM;

	_stream->seek(ARCHIVE_FILENAMES_OFS);
	_stream->read(_archiveDir, _numFiles*32);

	_stream->seek((isSmallArchive) ? SMALL_ARCHIVE_SIZES_OFS : NORMAL_ARCHIVE_SIZES_OFS);

	uint32 dataOffset = (isSmallArchive) ? SMALL_ARCHIVE_DATA_OFS : NORMAL_ARCHIVE_DATA_OFS;
	for (uint16 i = 0; i < _numFiles; i++) {
		_archiveOffsets[i] = dataOffset;
		_archiveLengths[i] = _stream->readUint32BE();
		dataOffset += _archiveLengths[i];
	}

}

NSArchive::~NSArchive() {
	delete _stream;
}

uint32 NSArchive::lookup(const char *name) const {
	uint32 i = 0;
	for ( ; i < _numFiles; i++) {
		if (!scumm_stricmp(_archiveDir[i], name)) break;
	}
	return i;
}

Common::SeekableReadStream *NSArchive::createReadStreamForMember(const Common::String &name) const {
	debugC(3, kDebugDisk, "NSArchive::createReadStreamForMember(%s)", name.c_str());

	if (name.empty())
		return 0;

	uint32 index = lookup(name.c_str());
	if (index == _numFiles) return 0;

	debugC(9, kDebugDisk, "NSArchive::createReadStreamForMember: '%s' found in slot %i", name.c_str(), index);

	int offset = _archiveOffsets[index];
	int endOffset = _archiveOffsets[index] + _archiveLengths[index];
	return new Common::SeekableSubReadStream(_stream, offset, endOffset, DisposeAfterUse::NO);
}

bool NSArchive::hasFile(const Common::String &name) const {
	if (name.empty())
		return false;
	return lookup(name.c_str()) != _numFiles;
}

int NSArchive::listMembers(Common::ArchiveMemberList &list) const {
	for (uint32 i = 0; i < _numFiles; i++) {
		list.push_back(Common::SharedPtr<Common::GenericArchiveMember>(new Common::GenericArchiveMember(_archiveDir[i], this)));
	}
	return _numFiles;
}

const Common::ArchiveMemberPtr NSArchive::getMember(const Common::String &name) const {
	uint32 index = lookup(name.c_str());

	const char *item = 0;
	if (index < _numFiles) {
		item = _archiveDir[index];
	}

	return Common::SharedPtr<Common::GenericArchiveMember>(new Common::GenericArchiveMember(item, this));
}


#define HIGHEST_PRIORITY		9
#define NORMAL_ARCHIVE_PRIORITY		5
#define LOW_ARCHIVE_PRIORITY		2
#define LOWEST_ARCHIVE_PRIORITY		1

Disk_ns::Disk_ns(Parallaction *vm) : _vm(vm) {
	Common::FSDirectory *baseDir = new Common::FSDirectory(ConfMan.get("path"));
	_sset.add("basedir", baseDir, HIGHEST_PRIORITY);
}

Disk_ns::~Disk_ns() {
	_sset.clear();
}

void Disk_ns::errorFileNotFound(const char *s) {
	error("File '%s' not found", s);
}

Common::SeekableReadStream *Disk_ns::openFile(const char *filename) {
	Common::SeekableReadStream *stream = tryOpenFile(filename);
	if (!stream)
		errorFileNotFound(filename);
	return stream;
}


void Disk_ns::addArchive(const Common::String& name, int priority) {
	Common::SeekableReadStream *stream = _sset.createReadStreamForMember(name);
	if (!stream)
		error("Disk_ns::addArchive() couldn't find archive '%s'", name.c_str());

	debugC(1, kDebugDisk, "Disk_ns::addArchive(name = %s, priority = %i)", name.c_str(), priority);

	NSArchive *arc = new NSArchive(stream, _vm->getPlatform(), _vm->getFeatures());
	_sset.add(name, arc, priority);
}

Common::String Disk_ns::selectArchive(const Common::String& name) {
	Common::String oldName = _resArchiveName;

	if (_sset.hasArchive(name)) {
		return oldName;
	}

	if (!_resArchiveName.empty()) {
		_sset.remove(_resArchiveName);
	}
	_resArchiveName = name;
	addArchive(name, NORMAL_ARCHIVE_PRIORITY);

	return oldName;
}

void Disk_ns::setLanguage(uint16 language) {
	debugC(1, kDebugDisk, "setLanguage(%i)", language);
	assert(language < 4);

	if (!_language.empty()) {
		_sset.remove(_language);
	}

	static const char *languages[] = { "it", "fr", "en", "ge" };
	_language = languages[language];

	if (_sset.hasArchive(_language)) {
		return;
	}

	addArchive(_language, LOWEST_ARCHIVE_PRIORITY);
}

#pragma mark -


DosDisk_ns::DosDisk_ns(Parallaction* vm) : Disk_ns(vm), _gfx(NULL) {

}

DosDisk_ns::~DosDisk_ns() {
}

void DosDisk_ns::init() {
	// setup permament archives
	addArchive("disk1", LOW_ARCHIVE_PRIORITY);
}

Common::SeekableReadStream *DosDisk_ns::tryOpenFile(const char* name) {
	debugC(3, kDebugDisk, "DosDisk_ns::tryOpenFile(%s)", name);

	Common::SeekableReadStream *stream = _sset.createReadStreamForMember(name);
	if (stream)
		return stream;

	char path[PATH_LEN];
	sprintf(path, "%s.pp", name);
	return _sset.createReadStreamForMember(path);
}

Script* Disk_ns::loadLocation(const char *name) {
	char path[PATH_LEN];
	const char *charName = _vm->_char.getBaseName();

	// WORKAROUND: Special case for the Multilingual DOS version: during the ending
	// sequence, it tries to load a non-existing file using "Dinor" as a character
	// name. In this case, the character name should be just "dino".
	if (!strcmp(charName, "Dinor"))
		charName = "dino";

	sprintf(path, "%s%s/%s.loc", charName, _language.c_str(), name);
	debugC(3, kDebugDisk, "Disk_ns::loadLocation(%s): trying '%s'", name, path);
	Common::SeekableReadStream *stream = tryOpenFile(path);

	if (!stream) {
		sprintf(path, "%s/%s.loc", _language.c_str(), name);
		debugC(3, kDebugDisk, "DosDisk_ns::loadLocation(%s): trying '%s'", name, path);
		stream = openFile(path);
	}
	return new Script(stream, true);
}

Script* Disk_ns::loadScript(const char* name) {
	debugC(1, kDebugDisk, "Disk_ns::loadScript '%s'", name);
	char path[PATH_LEN];
	sprintf(path, "%s.script", name);
	Common::SeekableReadStream *stream = openFile(path);
	return new Script(stream, true);
}

Cnv *Disk_ns::makeCnv(Common::SeekableReadStream *stream) {
	assert(stream);

	uint16 numFrames = stream->readByte();
	uint16 width = stream->readByte();
	assert((width & 7) == 0);
	uint16 height = stream->readByte();
	uint32 decsize = numFrames * width * height;
	byte *data = new byte[decsize];
	assert(data);
	memset(data, 0, decsize);

	decodeCnv(data, numFrames, width, height, stream);

	delete stream;
	return new Cnv(numFrames, width, height, data, true);
}

void DosDisk_ns::decodeCnv(byte *data, uint16 numFrames, uint16 width, uint16 height, Common::SeekableReadStream *stream) {
	int32 decsize = numFrames * width * height;
	bool packed = (stream->size() - stream->pos()) != decsize;
	if (packed) {
		Common::PackBitsReadStream decoder(*stream);
		decoder.read(data, decsize);
	} else {
		stream->read(data, decsize);
	}
}

Cnv* DosDisk_ns::loadCnv(const char *filename) {
	Common::SeekableReadStream *stream = openFile(filename);
	assert(stream);
	return makeCnv(stream);
}


GfxObj* DosDisk_ns::loadTalk(const char *name) {

	const char *ext = strstr(name, ".talk");
	if (ext) {
		// npc talk
		return new GfxObj(0, loadCnv(name), name);
	}

	char v20[30];
	if (g_engineFlags & kEngineTransformedDonna) {
		sprintf(v20, "%stta.cnv", name);
	} else {
		sprintf(v20, "%stal.cnv", name);
	}

	return new GfxObj(0, loadCnv(v20), name);
}


GfxObj* DosDisk_ns::loadHead(const char* name) {
	char path[PATH_LEN];
	sprintf(path, "%shead", name);
	path[8] = '\0';
	strcat(path, ".cnv");
	return new GfxObj(0, loadCnv(path));
}


Frames* DosDisk_ns::loadPointer(const char *name) {
	char path[PATH_LEN];
	sprintf(path, "%s.cnv", name);
	return loadCnv(path);
}


Font* DosDisk_ns::loadFont(const char* name) {
	char path[PATH_LEN];
	sprintf(path, "%scnv.cnv", name);
	return createFont(name, loadCnv(path));
}


GfxObj* DosDisk_ns::loadObjects(const char *name, uint8 part) {
	char path[PATH_LEN];
	sprintf(path, "%sobj.cnv", name);
	return new GfxObj(0, loadCnv(path), name);
}


GfxObj* DosDisk_ns::loadStatic(const char* name) {
	return new GfxObj(0, loadCnv(name), name);
}

Frames* DosDisk_ns::loadFrames(const char* name) {
	return loadCnv(name);
}

/*
	Background images are compressed using a RLE algorithm that resembles PackBits.

	The uncompressed data is then unpacked as following:
	- color data [bits 0-5]
	- mask data [bits 6-7] (z buffer)
	- path data [bit 8] (walkable areas)
*/
void DosDisk_ns::unpackBackground(Common::ReadStream *stream, byte *screen, byte *mask, byte *path) {
	byte storage[128];
	uint32 storageLen = 0, len = 0;
	uint32 j = 0;

	while (1) {
		// first extracts packbits variant data
		do {
			len = stream->readByte();
			if (stream->eos())
				return;

			if (len == 128) {
				storageLen = 0;
			} else if (len <= 127) {
				len++;
				for (uint32 i = 0; i < len; i++) {
					storage[i] = stream->readByte();
				}
				storageLen = len;
			} else {
				len = (256 - len) + 1;
				byte v = stream->readByte();
				memset(storage, v, len);
				storageLen = len;
			}
		} while (storageLen == 0);

		// then unpacks the bits to the destination buffers
		for (uint32 i = 0; i < storageLen; i++, j++) {
			byte b = storage[i];
			path[j/8] |= ((b & 0x80) >> 7) << (j & 7);
			mask[j/4] |= ((b & 0x60) >> 5) << ((j & 3) << 1);
			screen[j] = b & 0x1F;
		}
	}
}

void DosDisk_ns::parseDepths(BackgroundInfo &info, Common::SeekableReadStream &stream) {
	info.layers[0] = stream.readByte();
	info.layers[1] = stream.readByte();
	info.layers[2] = stream.readByte();
	info.layers[3] = stream.readByte();
}

void DosDisk_ns::createMaskAndPathBuffers(BackgroundInfo &info) {
	info._mask = new MaskBuffer;
	assert(info._mask);
	info._mask->create(info.width, info.height);
	info._mask->bigEndian = true;

	info._path = new PathBuffer;
	assert(info._path);
	info._path->create(info.width, info.height);
	info._path->bigEndian = true;
}

void DosDisk_ns::loadBackground(BackgroundInfo& info, const char *filename) {
	Common::SeekableReadStream *stream = openFile(filename);

	info.width = _vm->_screenWidth;	// 320
	info.height = _vm->_screenHeight;	// 200

	// read palette
	byte tmp[3];
	for (uint i = 0; i < 32; i++) {
		tmp[0] = stream->readByte();
		tmp[1] = stream->readByte();
		tmp[2] = stream->readByte();
		info.palette.setEntry(i, tmp[0], tmp[1], tmp[2]);
	}

	// read z coordinates
	parseDepths(info, *stream);

	// read palette rotation parameters
	PaletteFxRange range;
	for (uint32 _si = 0; _si < 6; _si++) {
		range._timer = stream->readUint16BE();
		range._step = stream->readUint16BE();
		range._flags = stream->readUint16BE();
		range._first = stream->readByte();
		range._last = stream->readByte();
		info.setPaletteRange(_si, range);
	}

	// read bitmap, mask and path data and extract them into the 3 buffers
	info.bg.create(info.width, info.height, Graphics::PixelFormat::createFormatCLUT8());
	createMaskAndPathBuffers(info);
	unpackBackground(stream, (byte *)info.bg.getPixels(), info._mask->data, info._path->data);

	delete stream;
}


void DosDisk_ns::loadSlide(BackgroundInfo& info, const char *filename) {
	char path[PATH_LEN];
	sprintf(path, "%s.slide", filename);
	loadBackground(info, path);
}

void DosDisk_ns::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) {
	char filename[PATH_LEN];
	sprintf(filename, "%s.dyn", name);

	// load bitmap
	loadBackground(info, filename);

	if (mask == 0) {
		return;
	}

	// load external mask and path if present (overwriting the ones loaded by loadBackground)
	char maskPath[PATH_LEN];
	sprintf(maskPath, "%s.msk", mask);

	Common::SeekableReadStream *stream = openFile(maskPath);
	assert(stream);

	parseDepths(info, *stream);

	createMaskAndPathBuffers(info);
	stream->read(info._path->data, info._path->size);
	stream->read(info._mask->data, info._mask->size);

	delete stream;
}

Table* DosDisk_ns::loadTable(const char* name) {
	char path[PATH_LEN];
	sprintf(path, "%s.tab", name);
	return createTableFromStream(100, openFile(path));
}

Common::SeekableReadStream* DosDisk_ns::loadMusic(const char* name) {
	char path[PATH_LEN];
	sprintf(path, "%s.mid", name);
	return openFile(path);
}


Common::SeekableReadStream* DosDisk_ns::loadSound(const char* name) {
	return 0;
}






#pragma mark -


/* the decoder presented here is taken from pplib by Stuart Caie. The
 * following statement comes from the original source.
 *
 * pplib 1.0: a simple PowerPacker decompression and decryption library
 * placed in the Public Domain on 2003-09-18 by Stuart Caie.
 */

#define PP_READ_BITS(nbits, var) do {				\
	bit_cnt = (nbits); (var) = 0;				\
	while (bits_left < bit_cnt) {				\
		if (buf < src) return 0;			\
		bit_buffer |= *--buf << bits_left;		\
		bits_left += 8;					\
	}							\
	bits_left -= bit_cnt;					\
	while (bit_cnt--) {					\
		(var) = ((var) << 1) | (bit_buffer & 1);	\
		bit_buffer >>= 1;				\
	}							\
} while (0)

#define PP_BYTE_OUT(byte) do {					\
	if (out <= dest) return 0;				\
	*--out = (byte); written++;				\
} while (0)


class PowerPackerStream : public Common::SeekableReadStream {

	SeekableReadStream *_stream;
	bool				_dispose;

private:
	int ppDecrunchBuffer(byte *src, byte *dest, uint32 src_len, uint32 dest_len) {

		byte *buf, *out, *dest_end, *off_lens, bits_left = 0, bit_cnt;
		uint32 bit_buffer = 0, x, todo, offbits, offset, written = 0;

		if (!src || !dest) return 0;

		/* set up input and output pointers */
		off_lens = src; src = &src[4];
		buf = &src[src_len];

		out = dest_end = &dest[dest_len];

		/* skip the first few bits */
		PP_READ_BITS(src[src_len + 3], x);

		/* while there are input bits left */
		while (written < dest_len) {
			PP_READ_BITS(1, x);
			if (x == 0) {
				  /* bit==0: literal, then match. bit==1: just match */
				  todo = 1; do { PP_READ_BITS(2, x); todo += x; } while (x == 3);
				  while (todo--) { PP_READ_BITS(8, x); PP_BYTE_OUT(x); }

				  /* should we end decoding on a literal, break out of the main loop */
				  if (written == dest_len) break;
			}

			/* match: read 2 bits for initial offset bitlength / match length */
			PP_READ_BITS(2, x);
			offbits = off_lens[x];
			todo = x+2;
			if (x == 3) {
				PP_READ_BITS(1, x);
				if (x == 0) offbits = 7;
				PP_READ_BITS(offbits, offset);
				do { PP_READ_BITS(3, x); todo += x; } while (x == 7);
			}
			else {
				PP_READ_BITS(offbits, offset);
			}
			if (&out[offset] >= dest_end) return 0; /* match_overflow */
			while (todo--) { x = out[offset]; PP_BYTE_OUT(x); }
		}

		/* all output bytes written without error */
		return 1;
	}

	uint16 getCrunchType(uint32 signature) {

		byte eff = 0;

		switch (signature) {
		case 0x50503230: /* PP20 */
			eff = 4;
			break;
		case 0x50504C53: /* PPLS */
			error("PPLS crunched files are not supported");
#if 0
			eff = 8;
			break;
#endif
		case 0x50583230: /* PX20 */
			error("PX20 crunched files are not supported");
#if 0
			eff = 6;
			break;
#endif
		default:
			eff = 0;

		}

		return eff;
	}

public:
	PowerPackerStream(Common::SeekableReadStream &stream) {

		_dispose = false;

		uint32 signature = stream.readUint32BE();
		if (getCrunchType(signature) == 0) {
			stream.seek(0, SEEK_SET);
			_stream = &stream;
			return;
		}

		stream.seek(-4, SEEK_END);
		uint32 decrlen = stream.readUint32BE() >> 8;
		byte *dest = (byte *)malloc(decrlen);

		uint32 crlen = stream.size() - 4;
		byte *src = (byte *)malloc(crlen);
		stream.seek(4, SEEK_SET);
		stream.read(src, crlen);

		ppDecrunchBuffer(src, dest, crlen-8, decrlen);

		free(src);
		_stream = new Common::MemoryReadStream(dest, decrlen, DisposeAfterUse::YES);
		_dispose = true;
	}

	~PowerPackerStream() {
		if (_dispose) delete _stream;
	}

	int32 size() const {
		return _stream->size();
	}

	int32 pos() const {
		return _stream->pos();
	}

	bool eos() const {
		return _stream->eos();
	}

	bool seek(int32 offs, int whence = SEEK_SET) {
		return _stream->seek(offs, whence);
	}

	uint32 read(void *dataPtr, uint32 dataSize) {
		return _stream->read(dataPtr, dataSize);
	}
};




AmigaDisk_ns::AmigaDisk_ns(Parallaction *vm) : Disk_ns(vm) {
}


AmigaDisk_ns::~AmigaDisk_ns() {

}

void AmigaDisk_ns::init() {
	// setup permament archives
	if (_vm->getFeatures() & GF_DEMO) {
		addArchive("disk0", LOW_ARCHIVE_PRIORITY);
	} else {
		addArchive("disk0", LOW_ARCHIVE_PRIORITY);
		addArchive("disk1", LOW_ARCHIVE_PRIORITY);
	}
}

#define NUM_PLANES		5

/*
	unpackFrame transforms images from 5-bitplanes format to
	8-bit color-index mode
*/
void AmigaDisk_ns::unpackFrame(byte *dst, byte *src, uint16 planeSize) {

	byte s0, s1, s2, s3, s4, mask, t0, t1, t2, t3, t4;

	for (uint32 j = 0; j < planeSize; j++) {
		s0 = src[j];
		s1 = src[j+planeSize];
		s2 = src[j+planeSize*2];
		s3 = src[j+planeSize*3];
		s4 = src[j+planeSize*4];

		for (uint32 k = 0; k < 8; k++) {
			mask = 1 << (7 - k);
			t0 = (s0 & mask ? 1 << 0 : 0);
			t1 = (s1 & mask ? 1 << 1 : 0);
			t2 = (s2 & mask ? 1 << 2 : 0);
			t3 = (s3 & mask ? 1 << 3 : 0);
			t4 = (s4 & mask ? 1 << 4 : 0);
			*dst++ = t0 | t1 | t2 | t3 | t4;
		}

	}

}

/*
	patchFrame applies DLTA data (dlta) to specified buffer (dst)
*/
void AmigaDisk_ns::patchFrame(byte *dst, byte *dlta, uint16 bytesPerPlane, uint16 height) {

	uint32 *dataIndex = (uint32 *)dlta;
	uint32 *ofslenIndex = (uint32 *)dlta + 8;

	uint16 *base = (uint16 *)dlta;
	uint16 wordsPerLine = bytesPerPlane >> 1;

	for (uint j = 0; j < NUM_PLANES; j++) {
		uint16 *dst16 = (uint16 *)(dst + j * bytesPerPlane * height);

		uint16 *data = base + READ_BE_UINT32(dataIndex);
		dataIndex++;
		uint16 *ofslen = base + READ_BE_UINT32(ofslenIndex);
		ofslenIndex++;

		while (*ofslen != 0xFFFF) {

			uint16 ofs = READ_BE_UINT16(ofslen);
			ofslen++;
			uint16 size = READ_BE_UINT16(ofslen);
			ofslen++;

			while (size > 0) {
				dst16[ofs] ^= *data++;
				ofs += wordsPerLine;
				size--;
			}

		}

	}

}

// FIXME: no mask is loaded
void AmigaDisk_ns::unpackBitmap(byte *dst, byte *src, uint16 numFrames, uint16 bytesPerPlane, uint16 height) {

	byte *baseFrame = src;
	byte *tempBuffer = 0;

	uint16 planeSize = bytesPerPlane * height;

	for (uint32 i = 0; i < numFrames; i++) {
		if (READ_BE_UINT32(src) == MKTAG('D','L','T','A')) {

			uint size = READ_BE_UINT32(src + 4);

			if (tempBuffer == 0)
				tempBuffer = (byte *)malloc(planeSize * NUM_PLANES);

			memcpy(tempBuffer, baseFrame, planeSize * NUM_PLANES);

			patchFrame(tempBuffer, src + 8, bytesPerPlane, height);
			unpackFrame(dst, tempBuffer, planeSize);

			src += (size + 8);
			dst += planeSize * 8;
		} else {
			unpackFrame(dst, src, planeSize);
			src += planeSize * NUM_PLANES;
			dst += planeSize * 8;
		}
	}

	free(tempBuffer);

}


void AmigaDisk_ns::decodeCnv(byte *data, uint16 numFrames, uint16 width, uint16 height, Common::SeekableReadStream *stream) {
	byte bytesPerPlane = width / 8;
	uint32 rawsize = numFrames * bytesPerPlane * NUM_PLANES * height;
	byte *buf = new byte[rawsize];
	assert(buf);
	stream->read(buf, rawsize);
	unpackBitmap(data, buf, numFrames, bytesPerPlane, height);
	delete[] buf;
}

#undef NUM_PLANES

Frames* AmigaDisk_ns::loadPointer(const char* name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadPointer");
	Common::SeekableReadStream *stream = openFile(name);
	return makeCnv(stream);
}

GfxObj* AmigaDisk_ns::loadStatic(const char* name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadStatic '%s'", name);
	Common::SeekableReadStream *s = openFile(name);
	return new GfxObj(0, makeCnv(s), name);
}

Common::SeekableReadStream *AmigaDisk_ns::tryOpenFile(const char* name) {
	debugC(3, kDebugDisk, "AmigaDisk_ns::tryOpenFile(%s)", name);

	PowerPackerStream *ret;
	Common::SeekableReadStream *stream = _sset.createReadStreamForMember(name);
	if (stream)
		return stream;

	char path[PATH_LEN];
	sprintf(path, "%s.pp", name);
	stream = _sset.createReadStreamForMember(path);
	if (stream) {
		ret = new PowerPackerStream(*stream);
		delete stream;
		return ret;
	}

	sprintf(path, "%s.dd", name);
	stream = _sset.createReadStreamForMember(path);
	if (stream) {
		ret = new PowerPackerStream(*stream);
		delete stream;
		return ret;
	}

	return 0;
}


/*
	FIXME: mask values are not computed correctly for level 1 and 2

	NOTE: this routine is only able to build masks for Nippon Safes, since mask widths are hardcoded
	into the main loop.
*/
void AmigaDisk_ns::buildMask(byte* buf) {

	byte mask1[16] = { 0, 0x80, 0x20, 0xA0, 8, 0x88, 0x28, 0xA8, 2, 0x82, 0x22, 0xA2, 0xA, 0x8A, 0x2A, 0xAA };
	byte mask0[16] = { 0, 0x40, 0x10, 0x50, 4, 0x44, 0x14, 0x54, 1, 0x41, 0x11, 0x51, 0x5, 0x45, 0x15, 0x55 };

	byte plane0[40];
	byte plane1[40];

	for (int32 i = 0; i < _vm->_screenHeight; i++) {

		memcpy(plane0, buf, 40);
		memcpy(plane1, buf+40, 40);

		for (uint32 j = 0; j < 40; j++) {
			*buf++ = mask0[(plane0[j] & 0xF0) >> 4] | mask1[(plane1[j] & 0xF0) >> 4];
			*buf++ = mask0[plane0[j] & 0xF] | mask1[plane1[j] & 0xF];
		}

	}
}


void AmigaDisk_ns::loadBackground(BackgroundInfo& info, const char *name) {
	Common::SeekableReadStream *s = openFile(name);
	Image::IFFDecoder decoder;
	decoder.loadStream(*s);

	info.bg.copyFrom(*decoder.getSurface());
	info.width = info.bg.w;
	info.height = info.bg.h;

	const byte *p = decoder.getPalette();
	for (uint i = 0; i < 32; i++) {
		byte r = *p >> 2;
		p++;
		byte g = *p >> 2;
		p++;
		byte b = *p >> 2;
		p++;
		info.palette.setEntry(i, r, g, b);
	}

	const Common::Array<Image::IFFDecoder::PaletteRange> &paletteRanges = decoder.getPaletteRanges();
	for (uint j = 0; j < 6 && j < paletteRanges.size(); j++) {
		PaletteFxRange range;
		range._timer = paletteRanges[j].timer;
		range._step = paletteRanges[j].step;
		range._flags = paletteRanges[j].flags;
		range._first = paletteRanges[j].first;
		range._last = paletteRanges[j].last;
		info.setPaletteRange(j, range);
	}
}

void AmigaDisk_ns::loadMask_internal(BackgroundInfo& info, const char *name) {
	debugC(5, kDebugDisk, "AmigaDisk_ns::loadMask_internal(%s)", name);

	char path[PATH_LEN];
	sprintf(path, "%s.mask", name);

	Common::SeekableReadStream *s = tryOpenFile(path);
	if (!s) {
		debugC(5, kDebugDisk, "Mask file not found");
		return;	// no errors if missing mask files: not every location has one
	}

	Image::IFFDecoder decoder;
	decoder.setNumRelevantPlanes(2); // use only 2 first bits from each pixel
	decoder.setPixelPacking(true); // pack 4 2bit pixels into 1 byte
	decoder.loadStream(*s);

	const byte *p = decoder.getPalette();
	byte r, g, b;
	for (uint i = 0; i < 4; i++) {
		r = p[i*3];
		g = p[i*3+1];
		b = p[i*3+2];
		info.layers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF;
	}

	info._mask = new MaskBuffer;
	// surface width was shrunk to 1/4th of the bitmap width due to the pixel packing
	info._mask->create(decoder.getSurface()->w * 4, decoder.getSurface()->h);
	memcpy(info._mask->data, decoder.getSurface()->getPixels(), info._mask->size);
	info._mask->bigEndian = true;
}

void AmigaDisk_ns::loadPath_internal(BackgroundInfo& info, const char *name) {

	char path[PATH_LEN];
	sprintf(path, "%s.path", name);

	Common::SeekableReadStream *s = tryOpenFile(path);
	if (!s) {
		return;	// no errors if missing path files: not every location has one
	}

	Image::IFFDecoder decoder;
	decoder.setNumRelevantPlanes(1); // use only first bit from each pixel
	decoder.setPixelPacking(true); // pack 8 1bit pixels into 1 byte
	decoder.loadStream(*s);

	info._path = new PathBuffer;
	// surface width was shrunk to 1/8th of the bitmap width due to the pixel packing
	info._path->create(decoder.getSurface()->w * 8, decoder.getSurface()->h);
	memcpy(info._path->data, decoder.getSurface()->getPixels(), info._path->size);
	info._path->bigEndian = true;
}

void AmigaDisk_ns::loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadScenery '%s', '%s'", background, mask);

	char filename[PATH_LEN];
	sprintf(filename, "%s.bkgnd", background);

	loadBackground(info, filename);

	if (mask == 0) {
		loadMask_internal(info, background);
		loadPath_internal(info, background);
	} else {
		loadMask_internal(info, mask);
		loadPath_internal(info, mask);
	}

	return;
}

void AmigaDisk_ns::loadSlide(BackgroundInfo& info, const char *name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadSlide '%s'", name);
	loadBackground(info, name);
	return;
}

Frames* AmigaDisk_ns::loadFrames(const char* name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadFrames '%s'", name);

	char path[PATH_LEN];
	sprintf(path, "anims/%s", name);

	Common::SeekableReadStream *s = tryOpenFile(path);
	if (!s)
		s = openFile(name);

	return makeCnv(s);
}

GfxObj* AmigaDisk_ns::loadHead(const char* name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadHead '%s'", name);
	char path[PATH_LEN];
	sprintf(path, "%s.head", name);
	Common::SeekableReadStream *s = openFile(path);
	return new GfxObj(0, makeCnv(s), name);
}


GfxObj* AmigaDisk_ns::loadObjects(const char *name, uint8 part) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadObjects");

	char path[PATH_LEN];
	if (_vm->getFeatures() & GF_DEMO)
		sprintf(path, "%s.objs", name);
	else
		sprintf(path, "objs/%s.objs", name);

	Common::SeekableReadStream *s = openFile(path);
	return new GfxObj(0, makeCnv(s), name);
}


GfxObj* AmigaDisk_ns::loadTalk(const char *name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadTalk '%s'", name);

	char path[PATH_LEN];
	if (_vm->getFeatures() & GF_DEMO)
		sprintf(path, "%s.talk", name);
	else
		sprintf(path, "talk/%s.talk", name);

	Common::SeekableReadStream *s = tryOpenFile(path);
	if (!s) {
		s = openFile(name);
	}
	return new GfxObj(0, makeCnv(s), name);
}

Table* AmigaDisk_ns::loadTable(const char* name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadTable '%s'", name);

	char path[PATH_LEN];
	if (!scumm_stricmp(name, "global")) {
		sprintf(path, "%s.table", name);
	} else {
		if (!(_vm->getFeatures() & GF_DEMO))
			sprintf(path, "objs/%s.table", name);
		else
			sprintf(path, "%s.table", name);
	}

	return createTableFromStream(100, openFile(path));
}

Font* AmigaDisk_ns::loadFont(const char* name) {
	debugC(1, kDebugDisk, "AmigaFullDisk::loadFont '%s'", name);

	char path[PATH_LEN];
	sprintf(path, "%sfont", name);

	Common::SeekableReadStream *stream = openFile(path);
	Font *font = createFont(name, *stream);
	delete stream;

	return font;
}


Common::SeekableReadStream* AmigaDisk_ns::loadMusic(const char* name) {
	return tryOpenFile(name);
}

Common::SeekableReadStream* AmigaDisk_ns::loadSound(const char* name) {
	char path[PATH_LEN];
	sprintf(path, "%s.snd", name);

	return tryOpenFile(path);
}

} // End of namespace Parallaction
