/*
								+--------------------------------+
								|                                |
								|  ***   LamePak2 classes   ***  |
								|                                |
								|  Copyright © -tHE SWINe- 2008  |
								|                                |
								|           LamePak2.h           |
								|                                |
								+--------------------------------+
*/

#pragma once
#ifndef __LAME_PAK_2_INCLUDED
#define __LAME_PAK_2_INCLUDED

/**
 *	@file LamePak2.h
 *	@author -tHE SWINe-
 *	@date 2008
 *	@brief simple compressed file format
 *
 *	LamePak is read-only archive, it is created by compressing list of files
 *	and then it can only be read, not modified. Compressor and decompressor classes
 *	are separated, and reflect these intentions.
 *
 *	Primary use of LamePak is to contain resource files for the application.
 *
 *	LamePak file structure:
 *  <code>  TPak2Header
 *    TPak2FileInfo-0
 *    .
 *    .
 *    TPak2FileInfo-n
 *    block-0-size
 *    Block-0
 *    .
 *    .
 *    block-m-size
 *    Block-m
 *    0</code>
 *
 *	Block sizes are 32 bit integers, in case MSB is raised,
 *	corresponding block is stored uncompressed.
 *
 *	@date 2007-02-25
 *
 *	this is first beta version if the file. todo - store path separator in pak header,
 *	re write TBuffer so it can realloc itself in a way std::vector can and try to avoid
 *	any (re)allocations while unpacking files. add function to purge block cache so
 *	it doesn't keep memory when no files are unpacked for longer periods of time
 *
 *	@date 2007-02-28
 *
 *	path separator is now in pak header and there's new packer verbose object.
 *	added compression method field to pak header.
 *
 *	@date 2008-08-08
 *
 *	added \#ifdef for windows 64
 *
 *	@date 2009-05-23
 *
 *	removed all instances of std::vector::reserve and replaced them by stl_ut::Reserve_*
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 *	@date 2012-06-19
 *
 *	Moved multiple inclusion guard before file documentation comment.
 *
 *	@date 2014-09-15
 *
 *	Proposed and partially implemented adaptive packing method. The output is compatible with
 *	older binaries, unless method_BWT_C or newer is used or version is increased to 206.
 *
 */

/**
 *	@def PAK2_PACKER_USE_STDERR
 *	@brief enables CPak2PackerUtil reporting it's errors to stderr
 */
#define PAK2_PACKER_USE_STDERR

#include "Integer.h"

#pragma pack(1) // tight alignemnt

/**
 *	@brief contains pak2 file header
 *	@note The structure is tightly aligned and can be directly written or read.
 */
struct TPak2Header {
	int16_t n_version;		/**< major * 100 + minor */
	int16_t n_method;		/**< packing method */
	uint32_t n_file_num;	/**< number of files */
	char n_path_separator;	/**< backslash character, used in paths */
	uint32_t n_block_size;	/**< size of uncompressed blocks */
	uint32_t n_block_num;	/**< number of blocks */
	uint32_t n_toc_size;	/**< size of toc block (compressed or with raised MSB, as with block sizes) */
};

/**
 *	@brief contains archived file information
 *
 *	@note The structure is tightly aligned and can be directly written or read.
 */
struct TPak2FileInfo {
	bool b_same_dir;					/**< file is in the same dir as the last one */
	uint32_t n_size_lo,					/**< low 32 bits of file size */
			 n_size_hi;					/**< high 32 bits of file size */
	uint32_t n_block,					/**< zero-based index of the block containing the first byte of the file */
			 n_block_offset;			/**< offset of the first byte of the file in the block, in bytes */
	uint16_t n_filename_length;			/**< filename length in bytes; terminating zero is not saved */
};

#pragma pack() // default alignment

/**
 *	@brief packing method names
 */
enum {
	// legacy methods:
	method_BWT_A = 0,		/**< BWT packing method A (block order is BWT, MTF, RLE, Huff) */
	method_BWT_B,			/**< BWT packing method B (block order is BWT, MTF-1, RLE, Huff) */
	method_BWT_C,			/**< BWT packing method C (block order is BWT, RLE, SIF, Huff) */

	// methods added in version 206:
	method_Adaptive,		/**< packing method selected per-block, written as int16_t before block data, if the given block is compressed (the int16_t is included in block size to be compatible with older versions) */
	method_BWT_B0,			/**< BWT packing method B0 (block order is BWT, MTF-1, RLE-0 (CRLE0_HuffmanCodec), CRC32) */
	method_BWT_A0_1,		/**< BWT packing method A0_1 (similar to BW94, block order is BWT, MTF, RLE-0 (CRLE0_HuffmanCodec_1), CRC32) */
	method_BWT_B0_1,		/**< BWT packing method B0_1 (as BW94, block order is BWT, MTF-1, RLE-0 (CRLE0_HuffmanCodec_1), CRC32) */
	method_BWT_A0_3,		/**< BWT packing method A0_3 (block order is BWT, MTF, RLE-0 (CRLE0_HuffmanCodec_3), CRC32) */
	method_BWT_B0_3,		/**< BWT packing method B0_3 (block order is BWT, MTF-1, RLE-0 (CRLE0_HuffmanCodec_3), CRC32) */
	method_BWT_E,			/**< BWT packing method E (block order is BWT, RLE-Exp, SIF-Mod, Huff, CRC32) */

	method_Max				/**< @brief number of methods, must always be the last */
};

/**
 *	@brief simple class containing function for creating pak from list of files
 */
class CPak2PackerUtil {
public:
	/**
	 *	@brief temporary buffer bank for use by the (de)compressors
	 */
	class CTempBufferPack {
	protected:
		TBuffer p_temp[5]; // increase if using algorithm requiring greater number of buffers

	public:
		inline TBuffer &operator [](size_t n_index)
		{
			_ASSERTE(n_index < n_Buffer_Num());
			return p_temp[n_index];
		}

		inline size_t n_Buffer_Num() const
		{
			return sizeof(p_temp) / sizeof(p_temp[0]);
		}

		inline void Free_Memory()
		{
			for(size_t i = 0, n = n_Buffer_Num(); i < n; ++ i) {
				{
					TBuffer t_empty;
					t_empty.Swap(p_temp[i]);
				}
				// swap with empty
			}
		}
	};

	/**
	 *	@brief unified (de)compressor interface
	 */
	class CCompressorIface {
	public:
		virtual bool Compress(const TBuffer &r_t_in_buffer,
			TBuffer &r_t_out_buffer, bool b_skip_BWT = false) = 0;
		virtual bool Decompress(const TBuffer &r_t_in_buffer, TBuffer &r_t_out_buffer) = 0;
	};

	/**
	 *	@brief codec implementation bank
	 */
	class CCodecBank : public CCompressorIface {
	protected:
		CTempBufferPack &m_r_temp_buffers;
		int m_n_codec; /**< @brief the chosen codec */

	public:
		inline CCodecBank(CTempBufferPack &r_temp_buffers, int n_codec)
			:m_r_temp_buffers(r_temp_buffers), m_n_codec(n_codec)
		{}

		virtual bool Compress(const TBuffer &r_t_in_buffer,
			TBuffer &r_t_out_buffer, bool b_skip_BWT = false);
		virtual bool Decompress(const TBuffer &r_t_in_buffer, TBuffer &r_t_out_buffer);

	protected:
		static bool CCodecBank::Run(CTempBufferPack &temp_buffers,
			const TBuffer &r_t_in_buffer, TBuffer &r_t_out_buffer, bool b_skip_BWT,
			int n_codec, bool b_compress);
	};

	/**
	 *	@brief virtual class for progress indicator
	 *
	 *	Gets (differential) amounts of packer input and output data.
	 *
	 *	@note It can be used to interrupt packing.
	 */
	class CVerboseObject {
	public:
		/**
		 *	@brief called when table of contents is compressed
		 *
		 *	@param[in] n_in is amount of data to be packed
		 *	@param[in] n_out is resulting packed size
		 */
		virtual void On_TOCCompress(size_t n_in, size_t n_out, int n_method) = 0;

		/**
		 *	@brief called when data block is compressed
		 *
		 *	@param[in] n_in is amount of data to be packed
		 *	@param[in] n_out is resulting packed size
		 *
		 *	@return Returns false if compression should be aborted, otherwie returns false.
		 */
		virtual bool On_BlockCompress(size_t n_in, size_t n_out, int n_method) = 0;
	};

	enum {
		pak_Version_Major = 2,
		pak_Version_Minor = 6,
		pak_Version = pak_Version_Major * 100 + pak_Version_Minor % 100,

		method_Default = method_Adaptive,
						//method_BWT_B0_1 // good
						//method_BWT_B // legacy, backward compatible

		level_Store = 0,
		level_Compress = 1,
		level_ForceCompress = 2
	};

private:
	class CBlockCounter;
	typedef std::pair<TPak2FileInfo, const char*> TNamedPak2FileInfo;

	static const uint32_t n_raw_data_flag; // MSB bit
	//static const char n_path_separator; // backslash
	//static const short n_pak_version; // pak version

	size_t m_n_block_size;
	int m_n_compression_level;
	CVerboseObject *m_p_verbose_obj;
	int m_n_thread_num;

	struct TCompressorState {
		CTempBufferPack temp_buffers; // buffers, required per encoding thread
		TBuffer t_compressed_block, t_bwt, t_best; // some other buffers
	};
	// temporary buffers for compression routine (to minimize allocation count)

	TCompressorState m_t_comp_state; // need one for every thread

	bool m_b_test_decompress;
	int m_n_compression_method;

public:
	/**
	 *	@brief default constructor, sets default packer options
	 */
	CPak2PackerUtil();

	/**
	 *	@brief gets test decompress flag
	 *	@return Returns test decompress flag.
	 */
	bool b_TestDecompress() const;

	/**
	 *	@brief sets test decompress flag
	 *	@param[in] b_enable is value of the new decompress flag
	 */
	void Enable_TestDecompress(bool b_enable);

	/**
	 *	@brief gets block compression method
	 *	@return Returns block compression method (one of method_*).
	 */
	int n_CompressionMethod() const;

	/**
	 *	@brief sets new block compression method
	 *	@param[in] n_method is block compression method (one of method_*)
	 *	@return Returns true for valid values, false for invalid values
	 *		(invalid values aren't set).
	 */
	bool Set_CompressionMethod(int n_method);

	/**
	 *	@brief gets block size
	 *	@return Returns block size.
	 */
	size_t n_BlockSize() const;

	/**
	 *	@brief sets new block size
	 *	@param[in] n_block_size is block size in bytes, valid values are 1k - 100M.
	 *	@return Returns true for valid values, false for invalid values
	 *		(invalid values aren't set).
	 */
	bool Set_BlockSize(size_t n_block_size);

	/**
	 *	@brief gets compression level
	 *	@return Returns compression level option (one of level_*).
	 */
	int n_CompressionLevel() const;

	/**
	 *	@brief sets compression level
	 *	@param[in] n_compression_level is compression level to be set (one of level_*)
	 *	@return Returns true upon accepting valid value,
	 *		  false when rejecting given n_compression_level.
	 */
	bool Set_CompressionLevel(int n_compression_level);

	/**
	 *	@brief gets current verbose object
	 *	@return Returns current verbose object (can be null).
	 */
	CVerboseObject *p_VerboseObject() const;

	/**
	 *	@brief sets new verbose object
	 *	@param[in] p_verbose is verbose object which gets amounts
	 *		of packer input and output data; can be null
	 */
	void Set_VerboseFunc(CVerboseObject *p_verbose);

	/**
	 *	@brief certain parts of compressor can run in parallel, gets number of threads
	 *	@return Returns number of threads.
	 */
	int n_Thread_Num() const;

	/**
	 *	@brief certain parts of compressor can run in parallel, gets number of threads
	 *	@param[in] n_thread_num is number of threads, valid values are 1 to 64
	 *	@return Returns true upon accepting valid value, false when rejecting given n_thread_num.
	 */
	bool Set_Thread_Num(int n_thread_num);

	/**
	 *	@brief gets packer version
	 *	@return Returns packer version, which is stored as major * 100 + minor.
	 */
	static int n_Version();

	/**
	 *	@brief creates a new LamePak archive
	 *
	 *	@param[in] p_s_filename is output filename
	 *	@param[in] r_file_list is list of input files
	 *	@param[in] n_block_size is compression block size (couple of megabytes
	 *		is ideal for good compression, but smaller blocks compress faster)
	 *	@param[in] p_s_base_dir is base directory, path to it will not be included
	 *		in pak file names
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note If PAK2_PACKER_USE_STDERR is defined, error messages are printed to stderr.
	 */
	bool CreatePak(const char *p_s_filename,
		const std::vector<TFileInfo*> &r_file_list, const char *p_s_base_dir = 0);

private:
	bool PackBuffer(const TBuffer &r_t_in_buffer, TBuffer &r_t_out_buffer);
	bool PackFiles(const std::vector<TFileInfo*> &r_file_list, FILE *p_fw);
	bool PackWriteBlock(const TBuffer &r_t_block, FILE *p_fw);
	static int n_TOC_Size(const std::vector<TNamedPak2FileInfo> &r_file_info);
	static bool Assemble_TOC(const std::vector<TNamedPak2FileInfo> &r_file_info,
		TBuffer &r_t_toc);
	static const char *p_s_SameDirName(const char *p_s_path, const char *p_s_prev_path);
	static TNamedPak2FileInfo t_FileInfo(const char *p_s_path, const char *p_s_prev_path,
		const CBlockCounter &r_block_counter, const TFileInfo &r_t_file_info);
	bool CreateFileHeaders(const std::vector<TFileInfo*> &r_file_list,
		const char *p_s_base_dir, std::vector<TNamedPak2FileInfo> &r_file_info,
		unsigned int &r_n_block_num);
};

/**
 *	@brief class encapsulating LamePak filesystem and decompressor
 */
class CLamePak2 {
private:
	typedef std::pair<TPak2FileInfo, char*> TNamedPak2FileInfo;

	FILE *m_p_fr;
	TPak2Header m_t_header;
	std::vector<TNamedPak2FileInfo> m_toc;

	static const uint32_t n_raw_data_flag; // MSB bit
	static const short n_pak_version; // supported pak version

	TBuffer m_t_cur_block;
	unsigned int m_n_cur_block;

	CPak2PackerUtil::CTempBufferPack m_temp_buffers;

public:
	/**
	 *	@brief opens LamePak and reads table of contents
	 *
	 *	@param[in] p_s_filename is input filename
	 *
	 *	@note Check b_Status() to see if it was successful.
	 */
	CLamePak2(const char *p_s_filename);

	/**
	 *	@brief destructor
	 */
	~CLamePak2();

	/**
	 *	@brief gets constructor result
	 *
	 *	@return Returns true if pak file was opened successfully, otherwise returns false.
	 */
	bool b_Status() const;

	/**
	 *	@brief gets file version
	 *
	 *	@return Returns opened file version or 0 in case pak failed to open.
	 */
	int n_Pak_Version() const;

	/**
	 *	@brief gets path separator
	 *
	 *	@return Returns used path separator character (ie. backslash),
	 *		or 0 in case pak failed to open.
	 */
	char n_Path_Separator() const;

	/**
	 *	@brief gets number of files in pak
	 *
	 *	@return Returns number of files or 0 in case pak file was not opened successfully.
	 */
	int n_File_Num() const;

	/**
	 *	@brief gets name of selected file
	 *
	 *	@param[in] n_index is zero-based index of the file (0 to n_File_Num() - 1)
	 *
	 *	@return Returns filename of file with zero-based index n_index.
	 */
	const char *p_s_FileName(int n_index) const;

	/**
	 *	@brief gets size of selected file
	 *
	 *	@param[in] n_index is zero-based index of the file (0 to n_File_Num() - 1)
	 *
	 *	@return Returns size of file with zero-based index n_index.
	 */
	uint64_t n_FileSize(int n_index) const;

	/**
	 *	@brief decompresses selected file
	 *
	 *	Unpacks file with zero-based index n_index, outputs to r_t_file_data
	 *		(doesn't have to be allocated).
	 *
	 *	@param[in] n_index is zero-based index of the file (0 to n_File_Num() - 1)
	 *	@param[out] r_t_file_data is output data buffer, which is filled with file contents
	 *
	 *	@return Returns true on success, false on failure.
	 *
	 *	@note This is limited to unpacking files with size up to 4GB on x86.
	 */
	bool UnpackFile(int n_index, TBuffer &r_t_file_data);

private:
	bool Read_TOC();
	bool Seek_Block(unsigned int n_block);
	bool Fetch_Block(unsigned int n_block);
	bool UnpackBuffer(const TBuffer &r_t_in_buffer, TBuffer &r_t_out_buffer, int n_method);
	static inline void DeleteName(TNamedPak2FileInfo &r_t_file_info);
};

#endif // !__LAME_PAK_2_INCLUDED
