/* 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-1-1-1/engines/sci/resource.h $
 * $Id: resource.h 48085 2010-02-17 23:37:59Z fingolfin $
 *
 */

#ifndef SCI_SCICORE_RESOURCE_H
#define SCI_SCICORE_RESOURCE_H

#include "common/str.h"

#include "sci/graphics/helpers.h"		// for ViewType
#include "sci/decompressor.h"
#include "sci/sci.h"

namespace Common {
class ReadStream;
class WriteStream;
class File;
class FSNode;
}

namespace Sci {

enum {
	/** The maximum allowed size for a compressed or decompressed resource */
	SCI_MAX_RESOURCE_SIZE = 0x0400000
};

/** Resource status types */
enum ResourceStatus {
	kResStatusNoMalloc = 0,
	kResStatusAllocated,
	kResStatusEnqueued, /**< In the LRU queue */
	kResStatusLocked /**< Allocated and in use */
};

/** Initialization result types */
enum {
	SCI_ERROR_IO_ERROR = 1,
	SCI_ERROR_INVALID_RESMAP_ENTRY = 2,	/**< Invalid resource.map entry */
	SCI_ERROR_RESMAP_NOT_FOUND = 3,
	SCI_ERROR_NO_RESOURCE_FILES_FOUND = 4,	/**< No resource at all was found */
	SCI_ERROR_UNKNOWN_COMPRESSION = 5,
	SCI_ERROR_DECOMPRESSION_ERROR = 6,	/**< sanity checks failed during decompression */
	SCI_ERROR_RESOURCE_TOO_BIG = 8	/**< Resource size exceeds SCI_MAX_RESOURCE_SIZE */

	/* the first critical error number */
};

enum {
	MAX_OPENED_VOLUMES = 5 ///< Max number of simultaneously opened volumes
};

enum ResSourceType {
	kSourceDirectory = 0,
	kSourcePatch,
	kSourceVolume,
	kSourceExtMap,
	kSourceIntMap,
	kSourceAudioVolume,
	kSourceExtAudioMap,
	kSourceWave
};

enum ResourceType {
	kResourceTypeView = 0,
	kResourceTypePic,
	kResourceTypeScript,
	kResourceTypeText,
	kResourceTypeSound,
	kResourceTypeMemory,
	kResourceTypeVocab,
	kResourceTypeFont,
	kResourceTypeCursor,
	kResourceTypePatch,
	kResourceTypeBitmap,
	kResourceTypePalette,
	kResourceTypeCdAudio,
	kResourceTypeAudio,
	kResourceTypeSync,
	kResourceTypeMessage,
	kResourceTypeMap,
	kResourceTypeHeap,
	kResourceTypeAudio36,
	kResourceTypeSync36,
	kResourceTypeUnknown1, // Translation, currently unsupported
	kResourceTypeUnknown2,
	kResourceTypeRobot,
	kResourceTypeInvalid
};

const char *getResourceTypeName(ResourceType restype);


class ResourceManager;
struct ResourceSource;

class ResourceId {
public:
	ResourceType type;
	uint16 number;
	uint32 tuple; // Only used for audio36 and sync36

	ResourceId() : type(kResourceTypeInvalid), number(0), tuple(0) { };

	ResourceId(ResourceType type_, uint16 number_, uint32 tuple_ = 0)
			: type(type_), number(number_), tuple(tuple_) {
		if ((type < kResourceTypeView) || (type > kResourceTypeInvalid))
			type = kResourceTypeInvalid;
	}

	ResourceId(ResourceType type_, uint16 number_, byte noun, byte verb, byte cond, byte seq)
			: type(type_), number(number_) {
		tuple = (noun << 24) | (verb << 16) | (cond << 8) | seq;

		if ((type < kResourceTypeView) || (type > kResourceTypeInvalid))
			type = kResourceTypeInvalid;
	}

	Common::String toString() {
		char buf[32];

		snprintf(buf, 32, "%s.%i", getResourceTypeName(type), number);
		Common::String retStr = buf;

		if (tuple != 0) {
			snprintf(buf, 32, "(%i, %i, %i, %i)", tuple >> 24, (tuple >> 16) & 0xff, (tuple >> 8) & 0xff, tuple & 0xff);
			retStr += buf;
		}

		return retStr;
	}
};

struct ResourceIdHash : public Common::UnaryFunction<ResourceId, uint> {
	uint operator()(ResourceId val) const { return ((uint)((val.type << 16) | val.number)) ^ val.tuple; }
};

struct ResourceIdEqualTo : public Common::BinaryFunction<ResourceId, ResourceId, bool> {
	bool operator()(const ResourceId &x, const ResourceId &y) const { return (x.type == y.type) && (x.number == y.number) && (x.tuple == y.tuple); }
};

struct ResourceIdLess : public Common::BinaryFunction<ResourceId, ResourceId, bool> {
	bool operator()(const ResourceId &x, const ResourceId &y) const {
		return (x.type < y.type) || ((x.type == y.type) && (x.number < y.number))
			    || ((x.type == y.type) && (x.number == y.number) && (x.tuple < y.tuple));
	}
};

/** Class for storing resources in memory */
class Resource {
	friend class ResourceManager;
public:
	Resource();
	~Resource();
	void unalloc();

// NOTE : Currently all member data has the same name and public visibility
// to let the rest of the engine compile without changes
public:
	ResourceId _id;
	byte *data;
	uint32 size;
	byte *_header;
	uint32 _headerSize;

	void writeToStream(Common::WriteStream *stream) const;

protected:
	int32 _fileOffset; /**< Offset in file */
	ResourceStatus _status;
	uint16 _lockers; /**< Number of places where this resource was locked */
	ResourceSource *_source;
};

typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash, ResourceIdEqualTo> ResourceMap;

class ResourceManager {
public:
	enum ResVersion {
		kResVersionUnknown,
		kResVersionSci0Sci1Early,
		kResVersionSci1Middle,
		kResVersionSci1Late,
		kResVersionSci11,
		kResVersionSci32
	};

	/**
	 * Creates a new SCI resource manager.
	 */
	ResourceManager();
	ResourceManager(const Common::FSList &fslist);
	~ResourceManager();

	/**
	 * Looks up a resource's data.
	 * @param id: The resource type to look for
	 * @param lock: non-zero iff the resource should be locked
	 * @return (Resource *): The resource, or NULL if it doesn't exist
	 * @note Locked resources are guaranteed not to have their contents freed until
	 *       they are unlocked explicitly (by unlockResource).
	 */
	Resource *findResource(ResourceId id, bool lock);

	/* Unlocks a previously locked resource
	**             (Resource *) res: The resource to free
	** Returns   : ()
	*/
	void unlockResource(Resource *res);

	/* Tests whether a resource exists
	**             (ResourceId) id: Id of the resource to check
	** Returns   : (Resource *) non-NULL if the resource exists, NULL otherwise
	** This function may often be much faster than finding the resource
	** and should be preferred for simple tests.
	** The resource object returned is, indeed, the resource in question, but
	** it should be used with care, as it may be unallocated.
	** Use scir_find_resource() if you want to use the data contained in the resource.
	*/
	Resource *testResource(ResourceId id);

	/**
	 * Returns a list of all resources of the specified type.
	 * @param type: The resource type to look for
	 * @param mapNumber: For audio36 and sync36, limit search to this map
	 * @return: The resource list
	 */
	Common::List<ResourceId> *listResources(ResourceType type, int mapNumber = -1);

	void setAudioLanguage(int language);
	int getAudioLanguage() const;
	bool isVGA() const { return (_viewType == kViewVga) || (_viewType == kViewVga11); }
	bool isAmiga32color() const { return _viewType == kViewAmiga; }
	ViewType getViewType() const { return _viewType; }
	const char *getMapVersionDesc() const { return versionDescription(_mapVersion); }
	const char *getVolVersionDesc() const { return versionDescription(_volVersion); }

	/**
	 * Adds the appropriate GM patch from the Sierra MIDI utility as 4.pat, without
	 * requiring the user to rename the file to 4.pat. Thus, the original Sierra
	 * archive can be extracted in the extras directory, and the GM patches can be
	 * applied per game, if applicable
	 */
	void addNewGMPatch(const Common::String &gameId);

	bool detectHires();

protected:
	// Maximum number of bytes to allow being allocated for resources
	// Note: maxMemory will not be interpreted as a hard limit, only as a restriction
	// for resources which are not explicitly locked. However, a warning will be
	// issued whenever this limit is exceeded.
	enum {
		MAX_MEMORY = 256 * 1024	// 256KB
	};

	ViewType _viewType; // Used to determine if the game has EGA or VGA graphics
	Common::List<ResourceSource *> _sources;
	int _memoryLocked;	///< Amount of resource bytes in locked memory
	int _memoryLRU;		///< Amount of resource bytes under LRU control
	Common::List<Resource *> _LRU; ///< Last Resource Used list
	ResourceMap _resMap;
	Common::List<Common::File *> _volumeFiles; ///< list of opened volume files
	ResourceSource *_audioMapSCI1; ///< Currently loaded audio map for SCI1
	ResVersion _volVersion; ///< RESOURCE.0xx version
	ResVersion _mapVersion; ///< RESOURCE.MAP version

	/**
	 * Initializes the resource manager
	 */
	void init();

	/**
	 * Add a path to the resource manager's list of sources.
	 * @return a pointer to the added source structure, or NULL if an error occurred.
	 */
	ResourceSource *addPatchDir(const char *path);

	ResourceSource *getVolume(ResourceSource *map, int volume_nr);

	/**
	 * Adds a source to the resource manager's list of sources.
	 * @param map		The map associated with this source
	 * @param type		The source type
	 * @param filename	The name of the source to add
	 * @return A pointer to the added source structure, or NULL if an error occurred.
	 */
	ResourceSource *addSource(ResourceSource *map, ResSourceType type, const char *filename,
	                          int number);

	ResourceSource *addSource(ResourceSource *map, ResSourceType type,
								const Common::FSNode *resFile, int number);

	/**
	 * Add an external (i.e., separate file) map resource to the resource manager's list of sources.
	 * @param file_name	 The name of the volume to add
	 * @param volume_nr  The volume number the map starts at, 0 for <SCI2.1
	 * @return		A pointer to the added source structure, or NULL if an error occurred.
	 */
	ResourceSource *addExternalMap(const char *file_name, int volume_nr = 0);

	ResourceSource *addExternalMap(const Common::FSNode *mapFile);

	/**
	 * Add an internal (i.e., resource) map to the resource manager's list of sources.
	 * @param name		The name of the resource to add
	 * @param resNr		The map resource number
	 * @return A pointer to the added source structure, or NULL if an error occurred.
	 */
	ResourceSource *addInternalMap(const char *name, int resNr);

	/**
	 * Scans newly registered resource sources for resources, earliest addition first.
	 * @param detected_version: Pointer to the detected version number,
	 *					 used during startup. May be NULL.
	 * @return One of SCI_ERROR_*.
	 */
	void scanNewSources();
	int addAppropriateSources();
	int addAppropriateSources(const Common::FSList &fslist);
	int addInternalSources();
	void freeResourceSources();

	/**
	 * Returns a string describing a ResVersion
	 * @param version: The resource version
	 * @return: The description of version
	 */
	const char *versionDescription(ResVersion version) const;

	Common::File *getVolumeFile(const char *filename);
	void loadResource(Resource *res);
	bool loadPatch(Resource *res, Common::File &file);
	bool loadFromPatchFile(Resource *res);
	bool loadFromWaveFile(Resource *res, Common::File &file);
	bool loadFromAudioVolumeSCI1(Resource *res, Common::File &file);
	bool loadFromAudioVolumeSCI11(Resource *res, Common::File &file);
	void freeOldResources();
	int decompress(Resource *res, Common::File *file);
	int readResourceInfo(Resource *res, Common::File *file, uint32&szPacked, ResourceCompression &compression);
	void addResource(ResourceId resId, ResourceSource *src, uint32 offset, uint32 size = 0);
	void removeAudioResource(ResourceId resId);

	/**--- Resource map decoding functions ---*/
	ResVersion detectMapVersion();
	ResVersion detectVolVersion();

	/**
	 * Reads the SCI0 resource.map file from a local directory.
	 * @param map The map
	 * @return 0 on success, an SCI_ERROR_* code otherwise
	 */
	int readResourceMapSCI0(ResourceSource *map);

	/**
	 * Reads the SCI1 resource.map file from a local directory.
	 * @param map The map
	 * @return 0 on success, an SCI_ERROR_* code otherwise
	 */
	int readResourceMapSCI1(ResourceSource *map);

	/**
	 * Reads SCI1.1 audio map resources
	 * @param map The map
	 * @return 0 on success, an SCI_ERROR_* code otherwise
	 */
	int readAudioMapSCI11(ResourceSource *map);

	/**
	 * Reads SCI1 audio map files
	 * @param map The map
	 * @param unload Unload the map instead of loading it
	 * @return 0 on success, an SCI_ERROR_* code otherwise
	 */
	int readAudioMapSCI1(ResourceSource *map, bool unload = false);

	/**--- Patch management functions ---*/

	/**
	 * Reads patch files from a local directory.
	 */
	void readResourcePatches(ResourceSource *source);
	void processPatch(ResourceSource *source, ResourceType restype, int resnumber);

	/**
	 * Process wave files as patches for Audio resources
	 */
	void readWaveAudioPatches();

 	/**
	 * Applies to all versions before 0.000.395 (i.e. KQ4 old, XMAS 1988 and LSL2).
	 * Old SCI versions used two word header for script blocks (first word equal
	 * to 0x82, meaning of the second one unknown). New SCI versions used one
	 * word header.
	 * Also, old SCI versions assign 120 degrees to left & right, and 60 to up
	 * and down. Later versions use an even 90 degree distribution.
	 */
	bool hasOldScriptHeader();

	void printLRU();
	void addToLRU(Resource *res);
	void removeFromLRU(Resource *res);

	ResourceCompression getViewCompression();
	ViewType detectViewType();
	bool hasSci0Voc999();
	bool hasSci1Voc900();
	void detectSciVersion();
};

class SoundResource {
public:
	struct Channel {
		byte number;
		byte poly;
		uint16 prio;
		uint16 size;
		byte *data;
		long time;
		byte prev;
	};

	struct Track {
		byte type;
		byte channelCount;
		Channel *channels;
		int16 digitalChannelNr;
		uint16 digitalSampleRate;
		uint16 digitalSampleSize;
		uint16 digitalSampleStart;
		uint16 digitalSampleEnd;
	};
public:
	SoundResource(uint32 resNumber, ResourceManager *resMan, SciVersion soundVersion);
	~SoundResource();
#if 0
	Track *getTrackByNumber(uint16 number);
#endif
	Track *getTrackByType(byte type);
	Track *getDigitalTrack();
	int getChannelFilterMask(int hardwareMask, bool wantsRhythm);
	byte getInitialVoiceCount(byte channel);

private:
	SciVersion _soundVersion;
	int _trackCount;
	Track *_tracks;
	Resource *_innerResource;
	ResourceManager *_resMan;
};

} // End of namespace Sci

#endif // SCI_SCICORE_RESOURCE_H
