/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-11-0/engines/parallaction/disk_ns.cpp $
 * $Id: disk_ns.cpp 30459 2008-01-12 23:07:51Z sev $
 *
 */

#include "graphics/iff.h"
#include "graphics/surface.h"

#include "parallaction/parallaction.h"


namespace Audio {
	class AudioStream;

	AudioStream *make8SVXStream(Common::ReadStream &input);
}

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

Archive::Archive() {
	resetArchivedFile();
}

void Archive::open(const char *file) {
	debugC(1, kDebugDisk, "Archive::open(%s)", file);

	if (_archive.isOpen())
		close();

	char	path[PATH_LEN];

	strcpy(path, file);
	if (!_archive.open(path))
		error("archive '%s' not found", path);

	_archiveName = file;

	bool isSmallArchive = false;
	if (_vm->getPlatform() == Common::kPlatformAmiga) {
		if (_vm->getFeatures() & GF_DEMO) {
			isSmallArchive = _archive.size() == SIZEOF_SMALL_ARCHIVE;
		} else if (_vm->getFeatures() & GF_LANG_MULT) {
			isSmallArchive = (_archive.readUint32BE() != MKID_BE('NDOS'));
		}
	}

	_numFiles = (isSmallArchive) ? SMALL_ARCHIVE_FILES_NUM : NORMAL_ARCHIVE_FILES_NUM;

	_archive.seek(ARCHIVE_FILENAMES_OFS);
	_archive.read(_archiveDir, _numFiles*32);

	_archive.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;
		_archiveLenghts[i] = _archive.readUint32BE();
		dataOffset += _archiveLenghts[i];
	}

	return;
}


void Archive::close() {
	if (!_archive.isOpen()) return;

	resetArchivedFile();

	_archive.close();
	_archiveName.clear();
}

Common::String Archive::name() const {
	return _archiveName;
}

bool Archive::openArchivedFile(const char *filename) {
	debugC(3, kDebugDisk, "Archive::openArchivedFile(%s)", filename);

	resetArchivedFile();

	debugC(3, kDebugDisk, "Archive::openArchivedFile(%s)", filename);

	if (!_archive.isOpen())
		error("Archive::openArchivedFile: the archive is not open");

	uint16 i = 0;
	for ( ; i < _numFiles; i++) {
		if (!scumm_stricmp(_archiveDir[i], filename)) break;
	}
	if (i == _numFiles) return false;

	debugC(9, kDebugDisk, "Archive::openArchivedFile: '%s' found in slot %i", filename, i);

	_file = true;

	_fileOffset = _archiveOffsets[i];
	_fileCursor = _archiveOffsets[i];
	_fileEndOffset = _archiveOffsets[i] + _archiveLenghts[i];

	_archive.seek(_fileOffset);

	return true;
}

void Archive::resetArchivedFile() {
	_file = false;
	_fileCursor = 0;
	_fileOffset = 0;
	_fileEndOffset = 0;
}

void Archive::closeArchivedFile() {
	resetArchivedFile();
}


uint32 Archive::size() const {
	return (_file == true ? _fileEndOffset - _fileOffset : 0);
}

uint32 Archive::pos() const {
	return (_file == true ? _fileCursor - _fileOffset : 0 );
}

bool Archive::eos() const {
	return (_file == true ? _fileCursor == _fileEndOffset : true );
}

void Archive::seek(int32 offs, int whence) {
	assert(_file == true && _fileCursor <= _fileEndOffset);

	switch (whence) {
	case SEEK_CUR:
		_fileCursor += offs;
		break;
	case SEEK_SET:
		_fileCursor = _fileOffset + offs;
		break;
	case SEEK_END:
		_fileCursor = _fileEndOffset - offs;
		break;
	}
	assert(_fileCursor <= _fileEndOffset && _fileCursor >= _fileOffset);

	_archive.seek(_fileCursor, SEEK_SET);
}

uint32 Archive::read(void *dataPtr, uint32 dataSize) {
//	printf("read(%i, %i)\n", file->_cursor, file->_endOffset);
	if (_file == false)
		error("Archive::read: no archived file is currently open");

	if (_fileCursor >= _fileEndOffset)
		error("can't read beyond end of archived file");

	if (_fileEndOffset - _fileCursor < dataSize)
		dataSize = _fileEndOffset - _fileCursor;

	int32 readBytes = _archive.read(dataPtr, dataSize);
	_fileCursor += readBytes;

	return readBytes;
}





/*
	This stream class is just a wrapper around Archive, so
	deallocation is not a problem. In fact, this class doesn't
	delete its input (Archive) stream.
*/
class DummyArchiveStream : public Common::SeekableReadStream {

	Archive *_input;

public:
	DummyArchiveStream(Archive &input) : _input(&input) {

	}

	~DummyArchiveStream() {
		// this class exists to provide this empty destructor
	}

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

	uint32 read(void* data, uint32 dataSize) {
		return _input->read(data, dataSize);
	}

	uint32 pos() const {
		return _input->pos();
	}

	uint32 size() const {
		return _input->size();
	}

	void seek(int32 offset, int whence) {
		_input->seek(offset, whence);
	}

};



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

Disk_ns::Disk_ns(Parallaction *vm) : _vm(vm) {

}

Disk_ns::~Disk_ns() {

}


Common::String Disk_ns::selectArchive(const Common::String& name) {
	Common::String oldName = _resArchive.name();
	_resArchive.open(name.c_str());
	return oldName;
}

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

	switch (language) {
	case 0:
		strcpy(_languageDir, "it/");
		break;

	case 1:
		strcpy(_languageDir, "fr/");
		break;

	case 2:
		strcpy(_languageDir, "en/");
		break;

	case 3:
		strcpy(_languageDir, "ge/");
		break;

	default:
		error("unknown language");

	}

	_languageDir[2] = '\0';
	_locArchive.open(_languageDir);
	_languageDir[2] = '/';

	return;
}

#pragma mark -



DosDisk_ns::DosDisk_ns(Parallaction* vm) : Disk_ns(vm) {

}

DosDisk_ns::~DosDisk_ns() {
}


//
// loads a cnv from an external file
//
Cnv* DosDisk_ns::loadExternalCnv(const char *filename) {
//	printf("Gfx::loadExternalCnv(%s)...", filename);

	char path[PATH_LEN];

	sprintf(path, "%s.cnv", filename);

	Common::File stream;

	if (!stream.open(path))
		errorFileNotFound(path);

	uint16 numFrames = stream.readByte();
	uint16 width = stream.readByte();
	uint16 height = stream.readByte();

	uint32 decsize = numFrames * width * height;
	byte *data = (byte*)malloc(decsize);
	stream.read(data, decsize);

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

Frames* DosDisk_ns::loadExternalStaticCnv(const char *filename) {

	char path[PATH_LEN];

	sprintf(path, "%s.cnv", filename);

	Common::File stream;

	if (!stream.open(path))
		errorFileNotFound(path);

	Graphics::Surface *cnv = new Graphics::Surface;

	stream.skip(1);
	byte w = stream.readByte();
	byte h = stream.readByte();

	cnv->create(w, h, 1);
	stream.read(cnv->pixels, w*h);

	return new SurfaceToFrames(cnv);
}

Cnv* DosDisk_ns::loadCnv(const char *filename) {
//	printf("Gfx::loadCnv(%s)\n", filename);

	char path[PATH_LEN];

	strcpy(path, filename);
	if (!_resArchive.openArchivedFile(path)) {
		sprintf(path, "%s.pp", filename);
		if (!_resArchive.openArchivedFile(path))
			errorFileNotFound(path);
	}

	uint16 numFrames = _resArchive.readByte();
	uint16 width = _resArchive.readByte();
	uint16 height = _resArchive.readByte();

	uint32 decsize = numFrames * width * height;
	byte *data = (byte*)malloc(decsize);

	Graphics::PackBitsReadStream decoder(_resArchive);
	decoder.read(data, decsize);

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

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

	const char *ext = strstr(name, ".talk");
	if (ext != NULL) {
		// npc talk
		return loadCnv(name);

	}

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

	return loadExternalCnv(v20);
}

Script* DosDisk_ns::loadLocation(const char *name) {

	char archivefile[PATH_LEN];
	sprintf(archivefile, "%s%s%s.loc", _vm->_char.getBaseName(), _languageDir, name);

	debugC(3, kDebugDisk, "DosDisk_ns::loadLocation(%s): trying '%s'", name, archivefile);

	if (!_locArchive.openArchivedFile(archivefile)) {
		sprintf(archivefile, "%s%s.loc", _languageDir, name);
		debugC(3, kDebugDisk, "DosDisk_ns::loadLocation(%s): trying '%s'", name, archivefile);

		if (!_locArchive.openArchivedFile(archivefile))
			errorFileNotFound(name);
	}

	return new Script(new DummyArchiveStream(_locArchive), true);
}

Script* DosDisk_ns::loadScript(const char* name) {

	char vC8[PATH_LEN];

	sprintf(vC8, "%s.script", name);

	if (!_resArchive.openArchivedFile(vC8))
		errorFileNotFound(vC8);

	return new Script(new DummyArchiveStream(_resArchive), true);
}

Frames* DosDisk_ns::loadHead(const char* name) {

	char path[PATH_LEN];

	sprintf(path, "%shead", name);
	path[8] = '\0';

	return loadExternalStaticCnv(path);
}


Frames* DosDisk_ns::loadPointer(const char *name) {
	return loadExternalStaticCnv(name);
}


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


Frames* DosDisk_ns::loadObjects(const char *name) {

	char path[PATH_LEN];
	sprintf(path, "%sobj", name);
	return loadExternalCnv(path);
}


Frames* DosDisk_ns::loadStatic(const char* name) {

	char path[PATH_LEN];

	strcpy(path, name);
	if (!_resArchive.openArchivedFile(path)) {
		sprintf(path, "%s.pp", name);
		if (!_resArchive.openArchivedFile(path))
			errorFileNotFound(path);
	}

	Graphics::Surface* cnv = new Graphics::Surface;

	_resArchive.skip(1);
	byte w = _resArchive.readByte();
	byte h = _resArchive.readByte();

	cnv->create(w, h, 1);

	Graphics::PackBitsReadStream decoder(_resArchive);
	decoder.read(cnv->pixels, w*h);

	return new SurfaceToFrames(cnv);
}

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

//
//	slides (background images) are stored compressed by scanline in a rle fashion
//
//	the uncompressed data must then be unpacked to get:
//	* 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 b;
	uint32 i = 0;

	while (!stream->eos()) {
		b = stream->readByte();

		path[i/8] |= ((b & 0x80) >> 7) << (i & 7);
		mask[i/4] |= ((b & 0x60) >> 5) << ((i & 3) << 1);
		screen[i] = b & 0x1F;
		i++;
	}

	return;
}


void DosDisk_ns::parseDepths(Common::SeekableReadStream &stream) {
	_vm->_gfx->_bgLayers[0] = stream.readByte();
	_vm->_gfx->_bgLayers[1] = stream.readByte();
	_vm->_gfx->_bgLayers[2] = stream.readByte();
	_vm->_gfx->_bgLayers[3] = stream.readByte();
}


void DosDisk_ns::parseBackground(BackgroundInfo& info, Common::SeekableReadStream &stream) {

	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]);
	}

	parseDepths(stream);

	for (uint32 _si = 0; _si < 6; _si++) {
		_vm->_gfx->_palettefx[_si]._timer = stream.readUint16BE();
		_vm->_gfx->_palettefx[_si]._step = stream.readUint16BE();
		_vm->_gfx->_palettefx[_si]._flags = stream.readUint16BE();
		_vm->_gfx->_palettefx[_si]._first = stream.readByte();
		_vm->_gfx->_palettefx[_si]._last = stream.readByte();
	}

}

void DosDisk_ns::loadBackground(BackgroundInfo& info, const char *filename) {

	if (!_resArchive.openArchivedFile(filename))
		errorFileNotFound(filename);

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

	parseBackground(info, _resArchive);

	info.bg.create(info.width, info.height, 1);
	info.mask.create(info.width, info.height);
	info.path.create(info.width, info.height);

	Graphics::PackBitsReadStream stream(_resArchive);
	unpackBackground(&stream, (byte*)info.bg.pixels, info.mask.data, info.path.data);

	return;
}

//
//	read background path and mask from a file
//
//	mask and path are normally combined (via OR) into the background picture itself
//	read the comment on the top of this file for more
//
void DosDisk_ns::loadMaskAndPath(BackgroundInfo& info, const char *name) {
	char path[PATH_LEN];
	sprintf(path, "%s.msk", name);

	if (!_resArchive.openArchivedFile(path))
		errorFileNotFound(name);

	parseDepths(_resArchive);

	info.path.create(info.width, info.height);
	_resArchive.read(info.path.data, info.path.size);

	info.mask.create(info.width, info.height);
	_resArchive.read(info.mask.data, info.mask.size);

	return;
}

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

	return;
}

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

	loadBackground(info, filename);

	if (mask != NULL) {
		// load external masks and paths only for certain locations
		loadMaskAndPath(info, mask);
	}

	return;
}

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

	Common::File	stream;
	if (!stream.open(path))
		errorFileNotFound(path);

	Table *t = createTableFromStream(100, stream);

	stream.close();

	return t;
}

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

	Common::File *stream = new Common::File;
	if (!stream->open(path))
		errorFileNotFound(path);

	return stream;
}


Common::ReadStream* DosDisk_ns::loadSound(const char* name) {
	return NULL;
}






#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 == NULL || dest == NULL) 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;

		switch (signature) {
		case 0x50503230: /* PP20 */
			eff = 4;
			break;
		case 0x50504C53: /* PPLS */
			error("PPLS crunched files are not supported");
			eff = 8;
			break;
		case 0x50583230: /* PX20 */
			error("PX20 crunched files are not supported");
			eff = 6;
			break;
		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, true);
		_dispose = true;
	}

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

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

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

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

	void seek(int32 offs, int whence = SEEK_SET) {
		_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() {

}

#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) == MKID_BE('DLTA')) {

			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);

}

Frames* AmigaDisk_ns::makeStaticCnv(Common::SeekableReadStream &stream) {

	stream.skip(1);
	uint16 width = stream.readByte();
	uint16 height = stream.readByte();

	assert((width & 7) == 0);

	byte bytesPerPlane = width / 8;

	uint32 rawsize = bytesPerPlane * NUM_PLANES * height;
	byte *buf = (byte*)malloc(rawsize);
	stream.read(buf, rawsize);

	Graphics::Surface *cnv = new Graphics::Surface;
	cnv->create(width, height, 1);

	unpackBitmap((byte*)cnv->pixels, buf, 1, bytesPerPlane, height);

	free(buf);

	return new SurfaceToFrames(cnv);
}

Cnv* AmigaDisk_ns::makeCnv(Common::SeekableReadStream &stream) {

	uint16 numFrames = stream.readByte();
	uint16 width = stream.readByte();
	uint16 height = stream.readByte();

	assert((width & 7) == 0);

	byte bytesPerPlane = width / 8;

	uint32 rawsize = numFrames * bytesPerPlane * NUM_PLANES * height;
	byte *buf = (byte*)malloc(rawsize);
	stream.read(buf, rawsize);

	uint32 decsize = numFrames * width * height;
	byte *data = (byte*)calloc(decsize, 1);

	unpackBitmap(data, buf, numFrames, bytesPerPlane, height);

	free(buf);

	return new Cnv(numFrames, width, height, data);
}
#undef NUM_PLANES

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

	char path[PATH_LEN];
	sprintf(path, "%s%s%s.loc.pp", _vm->_char.getBaseName(), _languageDir, name);

	if (!_locArchive.openArchivedFile(path)) {
		sprintf(path, "%s%s.loc.pp", _languageDir, name);
		if (!_locArchive.openArchivedFile(path)) {
			errorFileNotFound(name);
		}
	}

	debugC(3, kDebugDisk, "location file found: %s", path);

	return new Script(new PowerPackerStream(_locArchive), true);
}

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

	char vC8[PATH_LEN];

	sprintf(vC8, "%s.script", name);

	if (!_resArchive.openArchivedFile(vC8))
		errorFileNotFound(vC8);

	return new Script(new DummyArchiveStream(_resArchive), true);
}

Frames* AmigaDisk_ns::loadPointer(const char* name) {
	debugC(1, kDebugDisk, "AmigaDisk_ns::loadPointer");

	Common::File stream;
	if (!stream.open(name))
		errorFileNotFound(name);

	return makeStaticCnv(stream);
}

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

	Common::SeekableReadStream *s = openArchivedFile(name, true);
	Frames *cnv = makeStaticCnv(*s);

	delete s;

	return cnv;
}

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

	if (_resArchive.openArchivedFile(name)) {
		return new DummyArchiveStream(_resArchive);
	}

	char path[PATH_LEN];

	sprintf(path, "%s.pp", name);
	if (_resArchive.openArchivedFile(path)) {
		return new PowerPackerStream(_resArchive);
	}

	sprintf(path, "%s.dd", name);
	if (_resArchive.openArchivedFile(path)) {
		return new PowerPackerStream(_resArchive);
	}

	if (errorOnFileNotFound)
		errorFileNotFound(name);

	return NULL;
}

/*
	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 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];
		}

	}
}

class BackgroundDecoder : public Graphics::ILBMDecoder {

	PaletteFxRange *_range;
	uint32			_i;

protected:
	void readCRNG(Common::IFFChunk &chunk) {
		_range[_i]._timer = chunk.readUint16BE();
		_range[_i]._step = chunk.readUint16BE();
		_range[_i]._flags = chunk.readUint16BE();
		_range[_i]._first = chunk.readByte();
		_range[_i]._last = chunk.readByte();

		_i++;
	}

public:
	BackgroundDecoder(Common::ReadStream &input, Graphics::Surface &surface, byte *&colors, PaletteFxRange *range) :
		Graphics::ILBMDecoder(input, surface, colors), _range(range), _i(0) {
	}

	void decode() {
		Common::IFFChunk *chunk;
		while ((chunk = nextChunk()) != 0) {
			switch (chunk->id) {
			case ID_BMHD:
				readBMHD(*chunk);
				break;

			case ID_CMAP:
				readCMAP(*chunk);
				break;

			case ID_BODY:
				readBODY(*chunk);
				break;

			case ID_CRNG:
				readCRNG(*chunk);
				break;
			}
		}
	}

	uint32	getNumRanges() {
		return _i;
	}
};


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

	Common::SeekableReadStream *s = openArchivedFile(name, true);

	byte *pal;

	BackgroundDecoder decoder(*s, info.bg, pal, _vm->_gfx->_palettefx);
	decoder.decode();

	info.width = info.bg.w;
	info.height = info.bg.h;

	byte *p = pal;
	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);
	}

	free(pal);

	delete s;

	return;

}

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

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

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

	s->seek(0x30, SEEK_SET);

	byte r, g, b;
	for (uint i = 0; i < 4; i++) {
		r = s->readByte();
		g = s->readByte();
		b = s->readByte();

		_vm->_gfx->_bgLayers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF;

//		printf("rgb = (%x, %x, %x) -> %x\n", r, g, b, _vm->_gfx->_bgLayers[i]);
	}


	s->seek(0x126, SEEK_SET);	// HACK: skipping IFF/ILBM header should be done by analysis, not magic
	Graphics::PackBitsReadStream stream(*s);

	info.mask.create(info.width, info.height);
	stream.read(info.mask.data, info.mask.size);
	buildMask(info.mask.data);

	delete s;

	return;
}

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

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

	Common::SeekableReadStream *s = openArchivedFile(path, false);
	if (s == NULL)
		return;	// no errors if missing path files: not every location has one


	s->seek(0x120, SEEK_SET);	// HACK: skipping IFF/ILBM header should be done by analysis, not magic

	Graphics::PackBitsReadStream stream(*s);

	info.path.create(info.width, info.height);
	stream.read(info.path.data, info.path.size);

	delete s;

	return;
}

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 == NULL) {
		loadMask(info, background);
		loadPath(info, background);
	} else {
		loadMask(info, mask);
		loadPath(info, mask);
	}

	return;
}

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

	char path[PATH_LEN];
	sprintf(path, "slides/%s", name);
	Common::SeekableReadStream *s = openArchivedFile(path, false);

	if (s)
		loadBackground(info, path);
	else
		loadBackground(info, name);

	delete s;

	return;
}

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

	Common::SeekableReadStream *s;

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

	s = openArchivedFile(path, false);
	if (!s)
		s = openArchivedFile(name, true);

	Cnv *cnv = makeCnv(*s);
	delete s;

	return cnv;
}

Frames* 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 = openArchivedFile(path, true);
	Frames *cnv = makeStaticCnv(*s);

	delete s;

	return cnv;
}


Frames* AmigaDisk_ns::loadObjects(const char *name) {
	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 = openArchivedFile(path, true);

	Cnv *cnv = makeCnv(*s);
	delete s;

	return cnv;
}


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

	Common::SeekableReadStream *s;

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

	s = openArchivedFile(path, false);
	if (s == NULL) {
		s = openArchivedFile(name, true);
	}

	Cnv *cnv = makeCnv(*s);
	delete s;

	return cnv;
}

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

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

	bool dispose = false;

	Common::SeekableReadStream *stream;

	if (!scumm_stricmp(name, "global")) {
		Common::File *s = new Common::File;
		if (!s->open(path))
			errorFileNotFound(path);

		dispose = true;
		stream = s;
	} else {
		if (!(_vm->getFeatures() & GF_DEMO))
			sprintf(path, "objs/%s.table", name);
		if (!_resArchive.openArchivedFile(path))
			errorFileNotFound(path);

		stream = &_resArchive;
	}

	Table *t = createTableFromStream(100, *stream);

	if (dispose)
		delete stream;

	return t;
}

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

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

	if (_vm->getFeatures() & GF_LANG_IT) {
		// Italian version has separate font files
		Common::File stream;
		if (!stream.open(path))
			errorFileNotFound(path);

		return createFont(name, stream);
	} else {
		if (!_resArchive.openArchivedFile(path))
			errorFileNotFound(path);

		return createFont(name, _resArchive);
	}
}


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

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

	openArchivedFile(path);

	return new DummyArchiveStream(_resArchive);
}

} // namespace Parallaction
