/*
 * -----------------------------------------------------------------------------
 * This source file is part of OGRE
 * (Object-oriented Graphics Rendering Engine)
 * For the latest info, see http://www.ogre3d.org/
 *
 * Copyright (c) 2000-2014 Torus Knot Software Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * -----------------------------------------------------------------------------
 */
#ifndef __OgrePackageArchive_H__
#define __OgrePackageArchive_H__

#include "OgrePrerequisites.h"
#include "OgreArchive.h"
#include "OgreArchiveFactory.h"
#include "OgreHeaderPrefix.h"
#include "Threading/OgreThreadHeaders.h"

#include <fstream>
#include <map>
#include <vector>
#include <cstdint>

namespace Ogre
{

/** \addtogroup Core
 *  @{
 */
/** \addtogroup Resources
 *  @{
 */

/**
 * Package archive format - an uncompressed indexed file container.
 *
 * The .package format is a simple archive format similar to Unity's asset
 * bundles. It stores files sequentially with a file index at the end,
 * allowing fast random access without compression overhead.
 *
 * Format layout:
 *   [File 1 Data]
 *   [File 2 Data]
 *   ...
 *   [File N Data]
 *   [Index Block]
 *     - Number of files (uint32_t)
 *     - For each file:
 *       - Filename length (uint32_t)
 *       - Filename data
 *       - Offset (uint64_t)
 *       - Size (uint64_t)
 *       - Timestamp (int64_t)
 *   [Magic Footer]
 *     - Index offset (uint64_t)
 *     - Magic number "PACK" (uint32_t)
 */
class _OgreExport PackageArchive : public Archive {
public:
	struct FileEntry {
		uint64_t offset;
		uint64_t size;
		int64_t timestamp;
	};

protected:
	/// File path to the .package file on disk
	String mFilePath;

	/// File stream for reading/writing
	mutable std::fstream mFileStream;

	/// File index: filename -> FileEntry
	typedef std::map<String, FileEntry> FileIndex;
	FileIndex mFileIndex;

	/// Whether the archive is loaded
	bool mLoaded;

	/// Magic number and format constants
	static const uint32_t MAGIC;
	static const uint32_t FORMAT_VERSION;

	OGRE_AUTO_MUTEX;

	/** Read the index from the package file. */
	void readIndex();

	/** Write the index to the package file.
	 *  This rewrites the entire file: data blocks followed by index and footer.
	 */
	void writeIndex();

	/** Rebuild the package file from the current in-memory index.
	 *  This is used after add/remove operations to produce a clean file.
	 */
	void rebuildPackage();

public:
	PackageArchive(const String &name, const String &archType);
	~PackageArchive();

	/// @copydoc Archive::isCaseSensitive
	bool isCaseSensitive(void) const override
	{
		return false;
	}

	/// @copydoc Archive::load
	void load() override;

	/// @copydoc Archive::unload
	void unload() override;

	/// @copydoc Archive::isReadOnly
	bool isReadOnly() const override
	{
		return mReadOnly;
	}

	/// @copydoc Archive::open
	DataStreamPtr open(const String &filename,
			   bool readOnly = true) const override;

	/// @copydoc Archive::create
	DataStreamPtr create(const String &filename) override;

	/// @copydoc Archive::remove
	void remove(const String &filename) override;

	/// @copydoc Archive::list
	StringVectorPtr list(bool recursive = true,
			     bool dirs = false) const override;

	/// @copydoc Archive::listFileInfo
	FileInfoListPtr listFileInfo(bool recursive = true,
				     bool dirs = false) const override;

	/// @copydoc Archive::find
	StringVectorPtr find(const String &pattern, bool recursive = true,
			     bool dirs = false) const override;

	/// @copydoc Archive::findFileInfo
	FileInfoListPtr findFileInfo(const String &pattern,
				     bool recursive = true,
				     bool dirs = false) const override;

	/// @copydoc Archive::exists
	bool exists(const String &filename) const override;

	/// @copydoc Archive::getModifiedTime
	time_t getModifiedTime(const String &filename) const override;

	/** Add a file from an external source into the package.
	 *  @param filename The name to store the file as in the archive
	 *  @param data Pointer to the file data
	 *  @param size Size of the data in bytes
	 *  @param timestamp Modification time (0 = use current time)
	 */
	void addFile(const String &filename, const void *data, size_t size,
		     int64_t timestamp = 0);

	/** Add a file from the filesystem into the package.
	 *  @param destFilename The name to store the file as in the archive
	 *  @param sourcePath Path to the source file on disk
	 */
	void addFileFromDisk(const String &destFilename,
			     const String &sourcePath);

	/** Extract a file from the package to the filesystem.
	 *  @param filename The name of the file in the archive
	 *  @param destPath Path to write the extracted file to
	 *  @return true if successful
	 */
	bool extractFile(const String &filename, const String &destPath) const;

	/** List all files in the package index. */
	StringVector listFiles() const;
};

/** Factory for creating PackageArchive instances. */
class _OgreExport PackageArchiveFactory : public ArchiveFactory {
public:
	virtual ~PackageArchiveFactory()
	{
	}

	/// @copydoc FactoryObj::getType
	const String &getType(void) const override;

	//! @cond Doxygen_Suppress
	using ArchiveFactory::createInstance;
	//! @endcond

	Archive *createInstance(const String &name, bool readOnly) override;
};

/** @} */
/** @} */

} // namespace Ogre

#include "OgreHeaderSuffix.h"

#endif // __OgrePackageArchive_H__
