/* 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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1994-1998 Revolution Software Ltd.
 *
 * 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-1-1-1/engines/sword2/protocol.cpp $
 * $Id: protocol.cpp 46951 2010-01-03 21:20:05Z fingolfin $
 */

#include "common/file.h"
#include "common/endian.h"

#include "sword2/sword2.h"
#include "sword2/header.h"
#include "sword2/resman.h"
#include "sword2/logic.h"

namespace Sword2 {

/**
 * Returns a pointer to the first palette entry, given the pointer to the start
 * of the screen file.
 */

byte *Sword2Engine::fetchPalette(byte *screenFile) {
	byte *palette;

	if (isPsx()) { // PSX version doesn't have a "MultiScreenHeader", instead there's a ScreenHeader and a tag
		palette = screenFile + ResHeader::size() + ScreenHeader::size() + 2;
	} else {
		MultiScreenHeader mscreenHeader;

		mscreenHeader.read(screenFile + ResHeader::size());
		palette = screenFile + ResHeader::size() + mscreenHeader.palette;
	}

	// Always set colour 0 to black, because while most background screen
	// palettes have a bright colour 0 it should come out as black in the
	// game.

	palette[0] = 0;
	palette[1] = 0;
	palette[2] = 0;
	palette[3] = 0;

	return palette;
}

/**
 * Returns a pointer to the start of the palette match table, given the pointer
 * to the start of the screen file.
 * It returns NULL when used with PSX version, as there are no palette match tables in
 * the resource files.
 */

byte *Sword2Engine::fetchPaletteMatchTable(byte *screenFile) {

	if (isPsx()) return NULL;

	MultiScreenHeader mscreenHeader;

	mscreenHeader.read(screenFile + ResHeader::size());

	return screenFile + ResHeader::size() + mscreenHeader.paletteTable;
}

/**
 * Returns a pointer to the screen header, given the pointer to the start of
 * the screen file.
 */

byte *Sword2Engine::fetchScreenHeader(byte *screenFile) {
	if (isPsx()) { // In PSX version there's no MultiScreenHeader, so just skip resource header
		return screenFile + ResHeader::size();
	} else {
		MultiScreenHeader mscreenHeader;

		mscreenHeader.read(screenFile + ResHeader::size());
		return screenFile + ResHeader::size() + mscreenHeader.screen;
	}
}

/**
 * Returns a pointer to the requested layer header, given the pointer to the
 * start of the screen file. Drops out if the requested layer number exceeds
 * the number of layers on this screen.
 */

byte *Sword2Engine::fetchLayerHeader(byte *screenFile, uint16 layerNo) {
#ifdef SWORD2_DEBUG
	ScreenHeader screenHead;

	screenHead.read(fetchScreenHeader(screenFile));
	assert(layerNo < screenHead.noLayers);
#endif

	if (isPsx()) {
		return screenFile + ResHeader::size() + ScreenHeader::size() + 2 + 0x400 + layerNo * LayerHeader::size();
	} else {
		MultiScreenHeader mscreenHeader;

		mscreenHeader.read(screenFile + ResHeader::size());
		return screenFile + ResHeader::size() + mscreenHeader.layers + layerNo * LayerHeader::size();
	}
}

/**
 * Returns a pointer to the start of the shading mask, given the pointer to the
 * start of the screen file.
 * If we are non PSX, this will return NULL, as we don't have shading masks.
 */

byte *Sword2Engine::fetchShadingMask(byte *screenFile) {
	if (isPsx()) return NULL;

	MultiScreenHeader mscreenHeader;

	mscreenHeader.read(screenFile + ResHeader::size());

	return screenFile + ResHeader::size() + mscreenHeader.maskOffset;
}

/**
 * Returns a pointer to the anim header, given the pointer to the start of the
 * anim file.
 */

byte *Sword2Engine::fetchAnimHeader(byte *animFile) {
	return animFile + ResHeader::size();
}

/**
 * Returns a pointer to the requested frame number's cdtEntry, given the
 * pointer to the start of the anim file. Drops out if the requested frame
 * number exceeds the number of frames in this anim.
 */

byte *Sword2Engine::fetchCdtEntry(byte *animFile, uint16 frameNo) {
#ifdef SWORD2_DEBUG
	AnimHeader animHead;

	animHead.read(fetchAnimHeader(animFile));

	if (frameNo > animHead->noAnimFrames - 1)
		error("fetchCdtEntry(animFile,%d) - anim only %d frames", frameNo, animHead->noAnimFrames);
#endif

	return fetchAnimHeader(animFile) + AnimHeader::size() + frameNo * CdtEntry::size();
}

/**
 * Returns a pointer to the requested frame number's header, given the pointer
 * to the start of the anim file. Drops out if the requested frame number
 * exceeds the number of frames in this anim
 */

byte *Sword2Engine::fetchFrameHeader(byte *animFile, uint16 frameNo) {
	// required address = (address of the start of the anim header) + frameOffset

	CdtEntry cdt;

	cdt.read(fetchCdtEntry(animFile, frameNo));

	return animFile + ResHeader::size() + cdt.frameOffset;
}

/**
 * Returns a pointer to the requested parallax layer data.
 */

byte *Sword2Engine::fetchBackgroundParallaxLayer(byte *screenFile, int layer) {
	if (isPsx()) {
		byte *psxParallax = _screen->getPsxScrCache(0);

		// Manage cache for background psx parallaxes
		if (!_screen->getPsxScrCacheStatus(0)) { // This parallax layer is not present
			return NULL;
		} else if (psxParallax != NULL) { // Parallax layer present, and already in cache
			return psxParallax;
		} else { // Present, but not cached
			uint32 locNo = _logic->getLocationNum();

			// At game startup, we have a wrong location number stored
			// in game vars (0, instead of 3), work around this.
			locNo = (locNo == 0) ? 3 : locNo;

			psxParallax = fetchPsxParallax(locNo, 0);
			_screen->setPsxScrCache(psxParallax, 0);
			return psxParallax;
		}
	} else {
		MultiScreenHeader mscreenHeader;

		mscreenHeader.read(screenFile + ResHeader::size());
		assert(mscreenHeader.bg_parallax[layer]);
		return screenFile + ResHeader::size() + mscreenHeader.bg_parallax[layer];
	}
}

byte *Sword2Engine::fetchBackgroundLayer(byte *screenFile) {
	if (isPsx()) {
		byte *psxBackground = _screen->getPsxScrCache(1);

		// Manage cache for psx backgrounds
		if (psxBackground) { // Background is cached
			return psxBackground;
		} else { // Background not cached
			uint32 locNo = _logic->getLocationNum();

			// We have a wrong location number at start, fix that
			locNo = (locNo == 0) ? 3 : locNo;

			psxBackground = fetchPsxBackground(locNo);
			_screen->setPsxScrCache(psxBackground, 1);
			return psxBackground;
		}
	} else {
		MultiScreenHeader mscreenHeader;

		mscreenHeader.read(screenFile + ResHeader::size());
		assert(mscreenHeader.screen);
		return screenFile + ResHeader::size() + mscreenHeader.screen + ScreenHeader::size();
	}
}

byte *Sword2Engine::fetchForegroundParallaxLayer(byte *screenFile, int layer) {
	if (isPsx()) {
		byte *psxParallax = _screen->getPsxScrCache(2);

		// Manage cache for psx parallaxes
		if (!_screen->getPsxScrCacheStatus(2)) { // This parallax layer is not present
			return NULL;
		} else if (psxParallax) { // Parallax layer present and cached
			return psxParallax;
		} else { // Present, but still not cached
			uint32 locNo = _logic->getLocationNum();

			// We have a wrong location number at start, fix that
			locNo = (locNo == 0) ? 3 : locNo;

			psxParallax = fetchPsxParallax(locNo, 1);
			_screen->setPsxScrCache(psxParallax, 2);
			return psxParallax;
		}
	} else {
		MultiScreenHeader mscreenHeader;

		mscreenHeader.read(screenFile + ResHeader::size());
		assert(mscreenHeader.fg_parallax[layer]);
		return screenFile + ResHeader::size() + mscreenHeader.fg_parallax[layer];
	}
}

byte *Sword2Engine::fetchTextLine(byte *file, uint32 text_line) {
	TextHeader text_header;
	static byte errorLine[128];

	text_header.read(file + ResHeader::size());

	if (text_line >= text_header.noOfLines) {
		sprintf((char *)errorLine, "xxMissing line %d of %s (only 0..%d)", text_line, _resman->fetchName(file), text_header.noOfLines - 1);

		// first 2 chars are NULL so that actor-number comes out as '0'
		errorLine[0] = 0;
		errorLine[1] = 0;
		return errorLine;
	}

	// The "number of lines" field is followed by a lookup table

	return file + READ_LE_UINT32(file + ResHeader::size() + 4 + 4 * text_line);
}

/**
 * Returns a pointer to psx background data for passed location number
 * At the beginning of the passed data there's an artificial header composed by
 * uint16: background X resolution
 * uint16: background Y resolution
 * uint32: offset to subtract from offset table entries
 */

byte *Sword2Engine::fetchPsxBackground(uint32 location) {
	Common::File file;
	PSXScreensEntry header;
	uint32 screenOffset, dataOffset;
	uint32 totSize; // Total size of background, counting data, offset table and additional header
	byte *buffer;

	if (!file.open("screens.clu")) {
		GUIErrorMessage("Broken Sword II: Cannot open screens.clu");
		return NULL;
	}

	file.seek(location * 4, SEEK_SET);
	screenOffset = file.readUint32LE();

	if (screenOffset == 0) { // We don't have screen data for this location number.
		file.close();
		return NULL;
	}

	// Get to the beginning of PSXScreensEntry
	file.seek(screenOffset + ResHeader::size(), SEEK_SET);

	buffer = (byte *)malloc(PSXScreensEntry::size());
	file.read(buffer, PSXScreensEntry::size());

	// Prepare the header
	header.read(buffer);
	free(buffer);

	file.seek(screenOffset + header.bgOffset + 4, SEEK_SET);
	dataOffset = file.readUint32LE();

	file.seek(screenOffset + header.bgOffset, SEEK_SET);

	totSize = header.bgSize + (dataOffset - header.bgOffset) + 8;
	buffer = (byte *)malloc(totSize);

	// Write some informations before background data
	WRITE_LE_UINT16(buffer, header.bgXres);
	WRITE_LE_UINT16(buffer + 2, header.bgYres);
	WRITE_LE_UINT32(buffer + 4, header.bgOffset);

	file.read(buffer + 8, totSize - 8); // Do not write on the header
	file.close();

	return buffer;
}

/**
 * Returns a pointer to selected psx parallax data for passed location number
 * At the beginning of the passed data there's an artificial header composed by
 * uint16: parallax X resolution
 * uint16: parallax Y resolution
 * uint16: width in 64x16 tiles of parallax
 * uint16: height in 64x16 tiles of parallax
 */

byte *Sword2Engine::fetchPsxParallax(uint32 location, uint8 level) {
	Common::File file;
	PSXScreensEntry header;
	uint32 screenOffset;
	uint16 horTiles; // Number of horizontal tiles in the parallax grid
	uint16 verTiles; // Number of vertical tiles in parallax grid
	uint32 totSize; // Total size of parallax, counting data, grid, and additional header
	byte *buffer;

	uint16 plxXres;
	uint16 plxYres;
	uint32 plxOffset;
	uint32 plxSize;

	if (level > 1)
		return NULL;

	if (!file.open("screens.clu")) {
		GUIErrorMessage("Broken Sword II: Cannot open screens.clu");
		return NULL;
	}

	file.seek(location * 4, SEEK_SET);
	screenOffset = file.readUint32LE();

	if (screenOffset == 0) // There is no screen here
		return NULL;

	// Get to the beginning of PSXScreensEntry
	file.seek(screenOffset + ResHeader::size(), SEEK_SET);

	buffer = (byte *)malloc(PSXScreensEntry::size());
	file.read(buffer, PSXScreensEntry::size());

	// Initialize the header
	header.read(buffer);
	free(buffer);

	// We are fetching...
	if (level == 0) { // a background parallax
		plxXres = header.bgPlxXres;
		plxYres = header.bgPlxYres;
		plxOffset = header.bgPlxOffset;
		plxSize = header.bgPlxSize;
	} else {  // a foreground parallax
		plxXres = header.fgPlxXres;
		plxYres = header.fgPlxYres;
		plxOffset = header.fgPlxOffset;
		plxSize = header.fgPlxSize;
	}

	if (plxXres == 0 || plxYres == 0 || plxSize == 0) // This screen has no parallax data.
		return NULL;

	debug(2, "fetchPsxParallax() -> %s parallax, xRes: %u, yRes: %u", (level == 0) ? "Background" : "Foreground", plxXres, plxYres);

	// Calculate the number of tiles which compose the parallax grid.
	horTiles = plxXres % 64 ? (plxXres / 64) + 1 : plxXres / 64;
	verTiles = plxYres % 16 ? (plxYres / 16) + 1 : plxYres / 16;

	totSize = plxSize + horTiles * verTiles * 4 + 8;

	file.seek(screenOffset + plxOffset, SEEK_SET);
	buffer = (byte *)malloc(totSize);

	// Insert parallax resolution information in the buffer,
	// preceding parallax data.
	WRITE_LE_UINT16(buffer, plxXres);
	WRITE_LE_UINT16(buffer + 2, plxYres);
	WRITE_LE_UINT16(buffer + 4, horTiles);
	WRITE_LE_UINT16(buffer + 6, verTiles);

	// Read parallax data from file and store it inside the buffer,
	// skipping the generated header.
	file.read(buffer + 8, totSize - 8);
	file.close();

	return buffer;
}

// Used for testing text & speech (see fnISpeak in speech.cpp)

bool Sword2Engine::checkTextLine(byte *file, uint32 text_line) {
	TextHeader text_header;

	text_header.read(file + ResHeader::size());

	return text_line < text_header.noOfLines;
}

} // End of namespace Sword2
