#pragma once

/* includes */
#include "XMill.h"
#include "MemStreamer.hpp"
#include "PathTree.hpp"
#include "MemMan.hpp"

/* defines */
#define XMILL_HEADER_XML		      "<?xml"
#define XMILL_HEADER_BZIP		      "BZh"
#define XMILL_HEADER_NOZIP		      "NOZIP"
#define XMILL_HEADER_PPMDI_START    0x00654321  // MSB is used to store the ppmdi compression index
#define XMILL_HEADER_PPMDI_END      0x12345678

/* Bytesize of longest header string */
#define XMILL_HEADER_PEEK_SIZE (sizeof(XMILL_HEADER_NOZIP)-1)

/* a *lot* of forward references */
class DecompLabelDict;
class CompLabelDict;
class DecompVPathExprMan;
class CompVPathExprMan;
class XMLParse;
class PathDict;
struct EnumHashEntry;
class DecompEnumerationCompressorFactory;
class CompEnumerationCompressorFactory;
class ConstantCompressorFactory;
class EnumHashTable;
class UncompressContainerMan;
class PathTree;
struct EnumCompressState;
class Compressor;
class Output;
class XMill;
class Session;
class CurPath;
class CompressContainerMan;
class XMLMemParse;
class MemInput;

// Several flags
class Settings
{
public:
	char outputtype;
	char noincrease;
	char copyxml;
	char use_bzip;
	char usestdout;
	char no_output;
	char timing;
   char globalfullwhitespacescompress;
   char globalleftwhitespacescompress;
   char globalrightwhitespacescompress;
   char globalattribwhitespacescompress;
   char verbose;
   char delete_inputfiles;
   char ignore_comment;
   char ignore_cdata;
   char ignore_doctype;
   char ignore_pi;
   unsigned char compressidx;
   char overwrite_files;  // Is 1, if the user wants to overwrite all files
   char skip_all_files;   // Is 1, if the user want to skip all remaining files
	char usedosnewline;	// (Don't) use CR/LF i.s.o just LF
	char printcompressorexception;		// (Don't) generate an exception in the PrintCompressor()

	Session *session;

	Settings(Session *s);
	~Settings();

	/* MainFileActions.cpp, used in:
		Compress.cpp, MainFileActions.cpp, Uncompress.cpp
	*/
	char AskOverwriteFile(char *file);
	/* used in MainFileActions.cpp, realmain.cpp */
	void HandleFileArg(XMill *xmill, char *filepattern, bool compress);
	int HandleAllOptions(char **argv,int argc);
	/* Options.cpp */
	void InterpretOptionString(char *option);

	void Init(int outtype, bool lossy, 
				  char usebzip,
				  bool usedos, 
				  char igntype, bool ignore, 
				  int zlibidx,
				  bool copyxml);

	void setIgnore(char type, bool ignore);
	void setWhiteSpaceComp(char t, int value);
	void setPrintCompressorException(char e);

private:
	void HandleSingleFile(XMill *xmill, char *file, bool compress);
};

class Session 
{
public:
	Settings *settings;
	char **exprs;

   PPMDIData *ppmdidata;

	MemStreamer thetmpmem, *tmpmem;
	MemStreamer themainmem, *mainmem;
	MemStreamer theblockmem, *blockmem;

   // The memory cutoff is the maximum amount of memory that should be used
   // If the current memory allocation exceed the limit, then the parser stops
   // and the current data is written to the compressed output file
   // Then, the parser resumes
   unsigned long memory_cutoff;
	unsigned long allocsize;
	char do_compression;
	char output_initialized;

	unsigned long allocatedmemory;
	char *freeblocklists[BLOCKSIZE_NUM];
		// For each possible block size, we store the list of free blocks

	UncompressContainerMan  *uncomprcont;

	// The global user (de)compressor managers
	CompressMan   *compressman;
	DecompressMan *decompressman;

	/* global (un)compressors */
	UserCompressorFactory   **compressorlist;  // The list of user compressors
	int numcompressors, curcompressorlistsize;

	/* references to special factories that need to be hardwired */
	ConstantCompressorFactory				*constantcompressor;
	CompEnumerationCompressorFactory    *enumccompressfactory;
	DecompEnumerationCompressorFactory  *enumdcompressfactory;

	CurPath             *curpath;          // The current path in the XML document
	CompVPathExprMan    *cpathexprman;   // The path manager
	DecompVPathExprMan  *dpathexprman;   // The path manager

	CompLabelDict           *globalclabeldict;  // The label dictionary
	DecompLabelDict         *globaldlabeldict;  // The label dictionary
	CompressContainerMan    *compresscontman;  // The user compressor manager

	XMLParse *xmlparser;	// The XML Parser
	#ifdef USE_FORWARD_DATAGUIDE
		#include "PathTree.hpp"

		PathTreeNode   *curpathtreenode;
		MemStreamer    mypathtreemem(5);
	#endif
	MemStreamer *pathtreemem;
	MemStreamer *pathdictmem;

	PathDict *pathdict;
	PathTree *pathtree;      // The path tree

	// We reserve two labels for '#' and '@#'
	TLabelID elementpoundlabelid;
	TLabelID attribpoundlabelid;
	char fileheader_iswritten;
	char fileheader_isread;
	MemStreamer *fsmtmpmem,*fsmmem;
	unsigned long structcontsizeorig;
	unsigned long structcontsizecompressed;
	unsigned long whitespacecontsizeorig;
	unsigned long whitespacecontsizecompressed;
	unsigned long specialcontsizeorig;
	unsigned long specialcontsizecompressed;
	unsigned long fileheadersize_orig;
	unsigned long fileheadersize_compressed;
	unsigned long compressorcontsizeorig;
	unsigned long compressorcontsizecompressed;
	unsigned long datacontsizeorig;
	unsigned long datacontsizecompressed;
	CompressContainerBlock  *globalcontblock;
		// The first container block in the system contains
		// those three containers
	CompressContainer       *globalwhitespacecont;  // The special container for white spaces
	CompressContainer       *globalspecialcont;     // The special container for special sequences (DTDs, PIs...)
	CompressContainer       *globaltreecont;        // The structure container
	EnumHashTable				*enumhashtable;

	XMLMemParse *parse;
	MemOutput *memoutput;
	MemOutput *memoutput2;
	MemInput *meminput;
	XMLOutput *xmloutput;

	// To read the structural information at the beginning
	// of each file and each run in the file, a input buffer
	// is kept that can change (increase) in size.
	// The input buffer here can be larger than the input buffer
	// in 'Input'. The buffer here can contain an entire block
	// that is decompressed
	unsigned char *memoryalloc_buf;
	unsigned char *memoryalloc_curptr;
	unsigned long memoryalloc_bufsize;

	Session();
	~Session();
	void SetSettings (Settings *se);

	void setExprs(char **pathexprs);
	void dropExprs();

	void Init(int type, int indenttype = -1, int indentcount = 1, char **pathexpr = NULL);
	void InitMore();
	void DeInit();
	void InitPathTree();

	/* adapt RegisterCompressorFactories when you define your own user compressor */
	void RegisterCompressorFactories();
	void AddCompressFactory(UserCompressorFactory *compressor);
	void FreeCompressFactories();

	/* PrintCompressor 'callback' */
	void PrintCompressorOutput(char *s);

	/* compress or uncompress switch */
	void SetCompress(bool compress = true);

	// Initializes the FSM machinery: creates a '#' and '@#' label
	void FSMInit();
	void AddEnumCompressState(EnumCompressState *state);

	/* The memory management for blocks */
	unsigned char *AllocateMemBlock(unsigned long size);
	char *AllocateBlockRecurs(unsigned char blocksizeidx);
		// Allocates a block of size blocksizes[blocksizeidx]
	char *AllocateBlock(unsigned char blocksizeidx);
		// Allocates a new memory block
		// and increases the allocated memory count
	void FreeBlock(char *ptr,unsigned char blocksizeidx);
		// Frees a memory block
	unsigned long GetBlockSize(unsigned char blocksizeidx);
		// Returns the block size for a specific index
	void SetMemoryAllocationSize(unsigned long allocsize);
		// Sets the amount of memory needed. If the current block is too small,
		// the current block is reallocated.
	void WordAlignMemBlock();
		// We align the current pointer to an address divisible by 4
	void FreeMemBlock(void *ptr,unsigned long size);
	void FreeBlocks();

	void StoreFileHeader(Compressor *compressor);
	void CompressBlockHeader(Compressor *compressor,unsigned long totaldatasize);
	void CompressCurrentBlock(Output *output,unsigned long totaldatasize);

	void StoreEndLabel();
	void StoreEmptyEndLabel();
	void StoreStartLabel(TLabelID labelid);
	void StoreTextToken(unsigned blockid);
	void CompressTextItem(char *str,int len,int leftwslen,int rightwslen);

	void DecodeTreeBlock(UncompressContainer *treecont,UncompressContainer *whitespacecont,UncompressContainer *specialcont,XMLOutput *xoutput);

	void InitSpecialContainerSizeSum(); // Resets the accumulate size for special containers
	void PrintSpecialContainerSizeSum();// Prints the accumulate size for special containers

	void UncompressFileHeader(SmallBlockUncompressor *uncompressor);
	char UncompressBlockHeader(Input *input);

	void PrintUsage(char showmoreoptions);
};
