/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef SCI_ENGINE_SEG_MANAGER_H
#define SCI_ENGINE_SEG_MANAGER_H

#include "common/scummsys.h"
#include "common/serializer.h"
#include "sci/engine/script.h"
#include "sci/engine/vm.h"
#include "sci/engine/vm_types.h"
#include "sci/engine/segment.h"
#ifdef ENABLE_SCI32
#include "sci/graphics/celobj32.h" // kLowResX, kLowResY
#endif

namespace Sci {

/**
 * Parameters for getScriptSegment().
 */
enum ScriptLoadType {
	SCRIPT_GET_DONT_LOAD = 0, /**< Fail if not loaded */
	SCRIPT_GET_LOAD = 1, /**< Load, if necessary */
	SCRIPT_GET_LOCK = 3 /**< Load, if necessary, and lock */
};

class Script;

class SegManager : public Common::Serializable {
	friend class Console;
public:
	/**
	 * Initialize the segment manager.
	 */
	SegManager(ResourceManager *resMan, ScriptPatcher *scriptPatcher);

	/**
	 * Deallocate all memory associated with the segment manager.
	 */
	~SegManager() override;

	void resetSegMan();

	void saveLoadWithSerializer(Common::Serializer &ser) override;

	// 1. Scripts

	/**
	 * Allocate a script into the segment manager.
	 * @param script_nr		The number of the script to load
	 * @param seg_id		The segment ID of the newly allocated segment.
	 * @return				The script
	 */
	Script *allocateScript(int script_nr, SegmentId &seg_id);

	// The script must then be initialized; see section (1b.), below.

	/**
	 * Forcefully deallocate a previously allocated script.
	 * @param script_nr		number of the script to deallocate
	 */
	void deallocateScript(int script_nr);

	/**
	 * Reconstructs the stack. Used when restoring saved games
	 */
	void reconstructStack(EngineState *s);

	/**
	 * Determines the segment occupied by a certain script, if any.
	 * @param script_nr		Number of the script to look up
	 * @return				The script's segment ID, or 0 on failure
	 */
	SegmentId getScriptSegment(int script_nr) const;

	/**
	 * Determines the segment occupied by a certain script. Optionally
	 * load it, or load & lock it.
	 * @param[in] script_nr	Number of the script to look up
	 * @param[in] load		           Flag determining whether to load/lock the script
	 * @param[in] applyScriptPatches   Apply patches for the script, if available
	 * @return				           The script's segment ID, or 0 on failure
	 */
	SegmentId getScriptSegment(int script_nr, ScriptLoadType load, bool applyScriptPatches = true);

	/**
	 * Makes sure that a script and its superclasses get loaded to the heap.
	 * If the script already has been loaded, only the number of lockers is
	 * increased. All scripts containing superclasses of this script are loaded
	 * recursively as well, unless 'recursive' is set to zero. The
	 * complementary function is "uninstantiateScript()" below.
	 * @param[in] script_nr		       The script number to load
	 * @param[in] applyScriptPatches   Apply patches for the script, if available
	 * @return					       The script's segment ID or 0 if out of heap
	 */
	int instantiateScript(int script_nr, bool applyScriptPatches = true);

	/**
	 * Decreases the numer of lockers of a script and unloads it if that number
	 * reaches zero.
	 * This function will recursively unload scripts containing its
	 * superclasses, if those aren't locked by other scripts as well.
	 * @param[in] script_nr	The script number that is requestet to be unloaded
	 */
	void uninstantiateScript(int script_nr);

private:
	void uninstantiateScriptSci0(int script_nr);

public:
	// TODO: document this
	reg_t getClassAddress(int classnr, ScriptLoadType lock, uint16 callerSegment, bool applyScriptPatches = true);

	/**
	 * Return a pointer to the specified script.
	 * If the id is invalid, does not refer to a script or the script is
	 * not loaded, this will invoke error().
	 * @param seg	ID of the script segment to check for
	 * @return		A pointer to the Script object
	 */
	Script *getScript(SegmentId seg);


	/**
	 * Return a pointer to the specified script.
	 * If the id is invalid, does not refer to a script, or
	 * the script is not loaded, this will return NULL
	 * @param seg	ID of the script segment to check for
	 * @return		A pointer to the Script object, or NULL
	 */
	Script *getScriptIfLoaded(SegmentId seg) const;

	// 2. Clones

	/**
	 * Allocate a fresh clone
	 * @param addr The offset of the freshly allocated clone
	 * @return	Reference to the memory allocated for the clone
	 */
	Clone *allocateClone(reg_t *addr);

	/**
	 * Reconstructs clones. Used when restoring saved games
	 */
	void reconstructClones();

	// 4. Stack

	/**
	 * Allocates a data stack
	 * @param size	Number of stack entries to reserve
	 * @return		The data stack
	 */
	DataStack *allocateStack(int size);


	// 5. System Strings

	/**
	 * Initializes the system string table.
	 */
	void initSysStrings();


	// 6, 7. Lists and Nodes

	/**
	 * Allocate a fresh list
	 * @param[in] addr	The offset of the freshly allocated list
	 * @return			Reference to the memory allocated for the list
	 */
	List *allocateList(reg_t *addr);

	/**
	 * Allocate a fresh node
	 * @param[in] addr	The offset of the freshly allocated node
	 * @return			Reference to the memory allocated for the node
	 */
	Node *allocateNode(reg_t *addr);

	/**
	 * Allocate and initialize a new list node.
	 * @param[in] value		The value to set the node to
	 * @param[in] key		The key to set
	 * @return				Pointer to the newly initialized list node
	 */
	reg_t newNode(reg_t value, reg_t key);

	/**
	 * Resolves a list pointer to a list.
	 * @param addr The address to resolve
	 * @return The list referenced, or NULL on error
	 */
	List *lookupList(reg_t addr);

	/**
	 * Resolves an address into a list node.
	 * @param addr The address to resolve
	 * @return The list node referenced, or NULL on error
	 */
	Node *lookupNode(reg_t addr, bool stopOnDiscarded = true);


	// 8. Hunk Memory

	/**
	 * Allocate a fresh chunk of the hunk
	 * @param[in] size		Number of bytes to allocate for the hunk entry
	 * @param[in] hunk_type	A descriptive string for the hunk entry, for
	 *						debugging purposes
	 * @return				The offset of the freshly allocated hunk entry
	 */
	reg_t allocateHunkEntry(const char *hunk_type, int size);

	/**
	 * Deallocates a hunk entry
	 * @param[in] addr	Offset of the hunk entry to delete
	 */
	void freeHunkEntry(reg_t addr);

	/**
	 * Gets a pointer to the hunk memory referenced by a specified handle
	 * @param[in] addr	Offset of the hunk entry
	 */
	byte *getHunkPointer(reg_t addr);

	// 9. Dynamic Memory

	/**
	 * Allocate some dynamic memory
	 * @param[in]  size			Number of bytes to allocate
	 * @param[in]  description	A descriptive string for debugging purposes
	 * @param[out] addr			The offset of the freshly allocated X
	 * @return					Raw pointer into the allocated dynamic
	 * 							memory
	 */
	byte *allocDynmem(int size, const char *description, reg_t *addr);

	/**
	 * Deallocates a piece of dynamic memory
	 * @param[in] addr	Offset of the dynmem chunk to free
	 */
	bool freeDynmem(reg_t addr);


	// Generic Operations on Segments and Addresses

	/**
	 * Dereferences a raw memory pointer
	 * @param[in]  reg	The reference to dereference
	 * @return			The data block referenced
	 */
	SegmentRef dereference(reg_t pointer);

	/**
	 * Dereferences a heap pointer pointing to raw memory.
	 * @param pointer The pointer to dereference
	 * @parm entries The number of values expected (for checkingO
	 * @return A physical reference to the address pointed to, or NULL on error or
	 * if not enough entries were available.
	 */
	byte *derefBulkPtr(reg_t pointer, int entries);

	/**
	 * Dereferences a heap pointer pointing to a (list of) register(s).
	 * Ensures alignedness of data.
	 * @param pointer The pointer to dereference
	 * @parm entries The number of values expected (for checking)
	 * @return A physical reference to the address pointed to, or NULL on error or
	 * if not enough entries were available.
	 */
	reg_t *derefRegPtr(reg_t pointer, int entries);

	/**
	 * Dereferences a heap pointer pointing to raw memory.
	 * @param pointer The pointer to dereference
	 * @parm entries The number of values expected (for checking)
	 * @return A physical reference to the address pointed to, or NULL on error or
	 * if not enough entries were available.
	 */
	char *derefString(reg_t pointer, int entries = 0);

	/**
	 * Return the string referenced by pointer.
	 * pointer can point to either a raw or non-raw segment.
	 * @param pointer The pointer to dereference
	 * @return The string referenced, or an empty string if not enough
	 * entries were available.
	 */
	Common::String getString(reg_t pointer);


	/**
	 * Copies a string from src to dest.
	 * src and dest can point to raw and non-raw segments.
	 * Conversion is performed as required.
	 */
	void strcpy_(reg_t dest, reg_t src);

	/**
	 * Copies a string from src to dest.
	 * dest can point to a raw or non-raw segment.
	 * Conversion is performed as required.
	 */
	void strcpy_(reg_t dest, const char *src);

	/**
	 * Copies a string from src to dest.
	 * src and dest can point to raw and non-raw segments.
	 * Conversion is performed as required. At most n characters are copied.
	 * TODO: determine if dest should always be null-terminated.
	 */
	void strncpy(reg_t dest, reg_t src, size_t n);

	/**
	 * Copies a string from src to dest.
	 * dest can point to a raw or non-raw segment.
	 * Conversion is performed as required. At most n characters are copied.
	 * TODO: determine if dest should always be null-terminated.
	 */
	void strncpy(reg_t dest, const char *src, size_t n);

	/**
	 * Copies n bytes of data from src to dest.
	 * src and dest can point to raw and non-raw segments.
	 * Conversion is performed as required.
	 */
	void memcpy(reg_t dest, reg_t src, size_t n);

	/**
	 * Copies n bytes of data from src to dest.
	 * dest can point to a raw or non-raw segment.
	 * Conversion is performed as required.
	 */
	void memcpy(reg_t dest, const byte* src, size_t n);

	/**
	 * Copies n bytes of data from src to dest.
	 * src can point to raw and non-raw segments.
	 * Conversion is performed as required.
	 */
	void memcpy(byte *dest, reg_t src, size_t n);

	/**
	 * Determine length of string at str.
	 * str can point to a raw or non-raw segment.
	 */
	size_t strlen(reg_t str);

	/**
	 * Finds a unique segment by type
	 * @param type	The type of the segment to find
	 * @return		The segment number, or -1 if the segment wasn't found
	 */
	SegmentId findSegmentByType(int type) const;

	// TODO: document this
	SegmentObj *getSegmentObj(SegmentId seg) const;

	// TODO: document this
	SegmentType getSegmentType(SegmentId seg) const;

	// TODO: document this
	SegmentObj *getSegment(SegmentId seg, SegmentType type) const;

	/**
	 * Retrieves an object from the specified location
	 * @param[in] offset	Location (segment, offset) of the object
	 * @return				The object in question, or NULL if there is none
	 */
	Object *getObject(reg_t pos) const;

	/**
	 * Checks whether a heap address contains an object
	 * @parm obj The address to check
	 * @return True if it is an object, false otherwise
	 */
	bool isObject(reg_t obj) const { return getObject(obj) != NULL; }

	// TODO: document this
	bool isHeapObject(reg_t pos) const;

	/**
	 * Determines the name of an object
	 * @param[in] pos	Location (segment, offset) of the object
	 * @return			A name for that object, or a string describing an error
	 * 					that occurred while looking it up. The string is stored
	 * 					in a static buffer and need not be freed (neither may
	 * 					it be modified).
	 */
	const char *getObjectName(reg_t pos);

	/**
	 * Finds the addresses of all objects with the given name.
	 */
	Common::Array<reg_t> findObjectsByName(const Common::String &name);

	/**
	 * Find the address of an object by its name. In case multiple objects
	 * with the same name occur, the optional index parameter can be used
	 * to distinguish between them. If index is -1, then if there is a
	 * unique object with the specified name, its address is returned;
	 * if there are multiple matches, or none, then NULL_REG is returned.
	 *
	 * @param name		the name of the object we are looking for
	 * @param index		the index of the object in case there are multiple
	 * @return the address of the object, or NULL_REG
	 */
	reg_t findObjectByName(const Common::String &name, int index = -1);

	/**
	 * Finds the addresses of all objects with the superclass of the given name.
	 */
	Common::Array<reg_t> findObjectsBySuperClass(const Common::String &superClassName);

	uint32 classTableSize() const { return _classTable.size(); }
	Class getClass(int index) const { return _classTable[index]; }
	void setClassOffset(int index, reg_t offset) { _classTable[index].reg = offset;	}
	void resizeClassTable(uint32 size) { _classTable.resize(size); }

	reg_t getSaveDirPtr() const { return _saveDirPtr; }
	reg_t getParserPtr() const { return _parserPtr; }

#ifdef ENABLE_SCI32
	bool isValidAddr(reg_t reg, SegmentType expected) const {
		SegmentObj *mobj = getSegmentObj(reg.getSegment());
		return (mobj &&
				mobj->getType() == expected &&
				mobj->isValidOffset(reg.getOffset()));
	}

	SciArray *allocateArray(SciArrayType type, uint16 size, reg_t *addr);
	SciArray *lookupArray(reg_t addr);
	void freeArray(reg_t addr);
	bool isArray(reg_t addr) const;

	SciBitmap *allocateBitmap(reg_t *addr, const int16 width, const int16 height, const uint8 skipColor = kDefaultSkipColor, const int16 originX = 0, const int16 originY = 0, const int16 xResolution = kLowResX, const int16 yResolution = kLowResY, const uint32 paletteSize = 0, const bool remap = false, const bool gc = true);
	SciBitmap *lookupBitmap(reg_t addr);
	void freeBitmap(reg_t addr);
#endif

	const Common::Array<SegmentObj *> &getSegments() const { return _heap; }

private:
	Common::Array<SegmentObj *> _heap;
	Common::Array<Class> _classTable; /**< Table of all classes */
	/** Map script ids to segment ids. */
	Common::HashMap<int, SegmentId> _scriptSegMap;

	ResourceManager *_resMan;
	ScriptPatcher *_scriptPatcher;

	SegmentId _clonesSegId; ///< ID of the (a) clones segment
	SegmentId _listsSegId; ///< ID of the (a) list segment
	SegmentId _nodesSegId; ///< ID of the (a) node segment
	SegmentId _hunksSegId; ///< ID of the (a) hunk segment

	// Statically allocated memory for system strings
	reg_t _saveDirPtr;
	reg_t _parserPtr;

#ifdef ENABLE_SCI32
	SegmentId _arraysSegId;
	SegmentId _bitmapSegId;
#endif

public:
	SegmentId allocSegment(SegmentObj *mobj);

private:
	void deallocate(SegmentId seg);
	void createClassTable();

	SegmentId findFreeSegment() const;

	/**
	 * This implements our handling of scripts greater than 64K in size.
	 * They occur sporadically in SCI3 games (and in The Realm (SCI2.1),
	 * if we ever decide to support it). It works by "stealing" the upper
	 * two bits of the segment value to use as extra offset bits, making
	 * the maximum offset 0x3FFFF (262143). This is enough.
	 *
	 * The "actual" segment, then, is the segment value with the upper two
	 * bits masked out.
	 */
	SegmentId getActualSegment(SegmentId seg) const;
};

} // End of namespace Sci

#endif // SCI_ENGINE_SEG_MANAGER_H
