#ifdef PLATFORM_LINUX
#include <string.h>
int _stricmp(char *a, char *b)
{
	return __strcasecmp(a,b);
}
#endif
/*
	2 types of errors: bool and long
	bool : 0= Ok; 1= Impossible to proceed.
	long : 
		-Errors codes-
			0	 :	All right, ok.
			1	 :	Default error -> something was wrong
			2	 :	Can't read file
			3	 :	Can't write file
			-4	 :  file not found

    long options:
	bits, function
	0, alloc inputbuffer
	1, alloc outputbuffer

case of multiple files:
a simple file hold the packed and original sizes in the two first long of the file
so these numbers should never be negative.
In the multiple file case, the first long is negative and represent the negative
number of the number of files.
*/

#ifndef   ULONG
#define   ULONG unsigned long
#endif

#ifndef   UCHAR
#define   UCHAR unsigned char
#endif

#ifndef   NULL
#define   NULL 0
#endif

#define longer l1
#define side   l2

#define pastaga l1
#define level   l2



// the class that manipulate strings...  :) ---------------------------------
class sstring
{
	void strcpy(char *dest, char *s, long size)
	{
		long n=0;
		if( s == NULL ) return;
		if(size == 0)
		{
			dest[n++] = s[n];
			while(s[n-1] !=0) dest[n++] = s[n];
		}
		else
		{
			for(long i=0;i<size;i++)
				dest[i] = s[i];
		}
	}

public:
	char *data;		// a buffer...
	long pos;
	char *base;		// contain base directory

	void put(char *daAllocatedString)
	{
		long i;
		if(data!=NULL)
		{
			delete data;
			data = NULL;
		}
		pos= 0;
		while( daAllocatedString[pos] !=0) pos++;
		data = new char[pos+1];
		for(i=0;i<pos;i++) data[i] = daAllocatedString[i];
		data[i]= 0;
	}

	sstring()
	{
		data = NULL;
		pos = NULL;
		base = NULL;
	}

	void insert(char *hop, long offset, long lenght)
	{
		if(lenght == -1) 
		{
			lenght = 0;
			while(hop[lenght] !=0) lenght++;
		}
		char *newBuff = new char[pos+lenght+1];
		long i;
		for(i=0;i<offset;i++)		newBuff[i] = data[i];
		for(;i<(offset+lenght);i++)	newBuff[i] = hop[i-offset];
		for(;i<(offset+pos+lenght);i++) newBuff[i] = data[i-lenght-offset];
		newBuff[i]= 0;

		delete data;
		pos += lenght;
		data = newBuff;
	}

	void add(char *hop)
	{
		long n=0;
		char *t;
		while(hop[n++] != 0);
		t= new char[pos + n + 1];		// alloc always 1 more for close case
		strcpy(t, data, pos);
		strcpy(t+pos, hop, 0);
		pos += n;
		n = (long) data;
		data = t;
		t = (char*) n;
		if (t!=NULL) { delete t; t=NULL; }	// delete old buffer
	}

	void getBase(char *);
	long maxMatch(char *one, char *two, long size)
	{
		long n = 0;
		while( (one[n++] == two[n]) && (n<size));
		return n;
	}

	~sstring()
	{
		if(data!= NULL) delete data;
		if(base!= NULL) delete base;
		data = NULL;
		base = NULL;
		pos = 0;
	}

};

void sstring::getBase(char *list)
{
	long oldO, n = 0, t, minM= 0x7fffffff;
	long compareArea;
	long nPhrase = 0;
	while(1)
	{
		nPhrase++;
		oldO = n;
		while(list[n++]!=0);
		compareArea = n;
		while((list[compareArea-1]!= '\\') && (compareArea>0))compareArea--;
		if(list[n] == 0 ) break;
		t = maxMatch(list+n, list+oldO, compareArea);
		if(t<minM) minM = t;
	}
	if(nPhrase==1) 
	{
		minM = 0;
		while( list[minM++] != 0 );
		while( list[minM] != '\\') minM--;
		minM++;
	}
	if(base!=NULL) delete base;
	base = new char[minM];
	for(t = 0; t<minM-1; t++) base[t] = list[t];
	base[t] = 0;
}


// *--------------------------------------------------------- *



//			*  -----------    COHACK MAIN CLASS -------------  *
//			The class that pack, depack, sure :)
class CoHack
{
private:
	UCHAR Cbit;		// used to write bit streams

	struct node
	{
		unsigned char data;
		long l1;
		long l2;
	}*lznodes;	// lzwnodes is allocated in hackIt

	void storeBits(unsigned char *, long &, long, long);
	long getinfo(unsigned char *, long &, long, bool);
	long filesize(FILE *stream);
// codestream buffer, codestream offset, current node offset, data

	public: CoHack();

	float ratio;
	long filesize(char*);

	class buffer
	{
public:
		void *ptr;
		long size;
		buffer(){ ptr = NULL; size = NULL; }
		void release() 
		{ 
			if( ptr!=NULL) delete ptr;
			ptr= NULL; 
			size = NULL; 
		}
	};
	class buffer normalBuffer;
	class buffer LZWBuffer;
	class buffer arithBuffer;

	void releaseAll();
    long CO_MAX_NODES;
	long CO_ALLOC_OUTBUFF;
	long CO_DELETE_INBUFF;
	long CO_MODE_CRUNCH;
	long CO_MODE_DECRUNCH;
	long CO_MODE_GETSIZE;
	long CO_MODE_FORMEM;
	long CO_MODE_DECRUNCHALL;

/*
size of LZW buffer stored in the 4 first bytes.
size of original buffer stored in the 4 bytes that follow LZW size buffer(at +4).
build a LZW buffer of the input buffer at outbuf + 8.
*/
	bool LZWIt	(long);
	bool unLZWIt(long);
//	for compress or decompress a file, depends on the options parameter
	long hackIt(char *infilename, char *outfilename, long options);
};

CoHack::CoHack()
{
	  CO_MAX_NODES	   = 65536;
	  CO_ALLOC_OUTBUFF = 1;
	  CO_DELETE_INBUFF = 2;
	  CO_MODE_CRUNCH   = 4;
	  CO_MODE_DECRUNCH = 8;
	  CO_MODE_GETSIZE  = 16;
	  CO_MODE_FORMEM   = 32;
	  CO_MODE_DECRUNCHALL = 64;
	  lznodes = NULL;
}

void CoHack::releaseAll()
{
			if( normalBuffer.ptr!=NULL) delete normalBuffer.ptr;
			normalBuffer.ptr= NULL; 
			normalBuffer.size = NULL; 

			if( LZWBuffer.ptr!=NULL) delete LZWBuffer.ptr;
			LZWBuffer.ptr= NULL; 
			LZWBuffer.size = NULL; 

			if( arithBuffer.ptr!=NULL) delete arithBuffer.ptr;
			arithBuffer.ptr= NULL; 
			arithBuffer.size = NULL; 
}

void CoHack::storeBits(unsigned char *codestream, long & codeptr, long cnode, long data)
{
	unsigned long t = 0x80000000;
	long nbits=32;
	while( (t & cnode)==0) 
	{
		t >>= 1;
		nbits--;
	}
	t=1;
	for(long i=0;i<nbits;i++)
	{
		if( (t & data)!=0) codestream[codeptr] |= Cbit;
		t<<=1;
		Cbit <<=1;
		if(Cbit==0)
		{
			Cbit=1;
			codeptr++;
			codestream[codeptr] = 0;
		}
	}
}

/*
Get info from the bitStream (LZW buffer)
info extracted with decomp_ptr byte offset at the Cbit bit-offset.
bit lenght is take from the current cnode number: bits active (ex:cnode = 17, lenght=4)
*/
long CoHack::getinfo(unsigned char *codestream, long & decomp_ptr, long cnode, bool a)
{
	unsigned long t = 0x80000000;
	long data=0, bitwrite=1;
	long daptr = decomp_ptr;
	unsigned char tbit = Cbit;
	long nbits=32;
	if(cnode==CO_MAX_NODES) cnode=CO_MAX_NODES-1;
	while( (t & cnode)==0) 
	{
		t >>= 1;
		nbits--;
	}
	for(long i=0;i<nbits;i++)
	{
		if( (codestream[daptr] & tbit)!=0) data |= bitwrite;
		bitwrite <<=1;
		tbit<<=1;
		if(tbit==0)
		{
			tbit=1;
			daptr++;
		}
	}
	if(a)		// if the user request a save of the bit offset
	{
		decomp_ptr = daptr;
		Cbit = tbit;
	}
	return(data);
}

/*
size of LZW buffer stored in the 4 first bytes.
build a LZW buffer of the input buffer at outbuf + 4.
longer and side defined in header (#DEFINE)
if LZW buffer > LZWBuffer is released, size member = -1 and return true
*/
bool CoHack::LZWIt(long options)
{
	long i, inptr,cnode,codeptr;
	unsigned char *codestream, *inbuffer = (UCHAR*)normalBuffer.ptr;
	if( (options & CO_ALLOC_OUTBUFF) != 0)
	{
		codestream = new unsigned char[normalBuffer.size+1];
		LZWBuffer.ptr = (void*)codestream;
	}
	else codestream = (UCHAR*)LZWBuffer.ptr;
		// alloc output buffer with inbuffer size, may reallocate to the wright size 
	    // at the end
	if(normalBuffer.ptr  == NULL) return true;
	if(LZWBuffer.ptr == NULL) return true;
//  ---- Buffers ok, now compress... ----

// ----- init char nodes... ------
	cnode = 0;
	for(i=0 ; i<256 ; i++)
	{
		lznodes[i].data = (unsigned char)i;
		lznodes[i].longer = -1;
		lznodes[i].side = (unsigned char)i+1;
		cnode++;
	}
	cnode--;
	lznodes[i-1].side = -1;
	// init other linked links...
	for(i=256;i<CO_MAX_NODES;i++)
	{
		lznodes[i].longer = -1;
		lznodes[i].side  = -1;
	}
//  ------------------------------

	// go threw the file...
	inptr = 0;
	codeptr = 8;
	*((long*)(codestream+4))  = normalBuffer.size;	// store original file size
	codestream[8] = 0;
	Cbit = 1;
	while(inptr<normalBuffer.size)
	{
		long nmatch=0, offset = inbuffer[inptr];
		long result;
		while(1)
		{
		 if(lznodes[ offset ].data == inbuffer[inptr+nmatch])
		 {
			 nmatch++;
			 result = offset;		// hash var is obsolete...
			 if(lznodes[ offset ].longer == -1)
			 {
				 if(cnode<(CO_MAX_NODES-1)) // && ((inptr&0xf)==0))	
				 {
				  cnode++;
				  lznodes[ offset ].longer = cnode;
				  lznodes[cnode].data = inbuffer[inptr+nmatch];
				  lznodes[cnode].longer = -1;
				  lznodes[cnode].side   = -1;
				 }
				 break;
			 }
			 else offset = lznodes[ offset ].longer;
		 }
		 else
		 {
			 if(lznodes[ offset ].side == -1)
			 {
				 if(cnode<(CO_MAX_NODES-1)) // && ((inptr&0xf)==0))
				 {
				  cnode++;
				  lznodes[ offset ].side = cnode;
				  lznodes[cnode].data = inbuffer[inptr+nmatch];
				  lznodes[cnode].longer = -1;
				  lznodes[cnode].side   = -1;
				 }
				 break;
			 }
			 else offset = lznodes[ offset ].side;
		 }
		}

		storeBits(codestream, codeptr, cnode, result);
		if(codeptr>=(normalBuffer.size-1)) 
		{
			LZWBuffer.size = -1;
			return(false);
		}
		inptr += nmatch;

		if(cnode>=(CO_MAX_NODES-1))
		{
			lznodes[255].side=-1;
			cnode=255;
			for(i=0;i<256;i++) lznodes[i].longer =-1;
			for( ;i<CO_MAX_NODES;i++)
			{
				lznodes[i].longer = -1;
				lznodes[i].side  = -1;
			}
		}
	}
	if(Cbit!=0) codeptr++;
	*((long*)codestream) = codeptr;		// store LZW buffer size
	LZWBuffer.size = codeptr;

	if( (options & 2) != 0)	normalBuffer.release();
	return false;		// everything were alright :)
}

/*
pastaga and level defined in header (#DEFINE)
*/
bool CoHack::unLZWIt(long options)
{
	if(LZWBuffer.ptr  == NULL) return true;

	long i,j, decomp_ptr;
	unsigned char *codestream = (UCHAR*)LZWBuffer.ptr, *backto;
	long inSize = *((long*)LZWBuffer.ptr);
	long outSize = *((long*)LZWBuffer.ptr+1);

	if( (options & CO_ALLOC_OUTBUFF) != 0)
	{
		backto = new UCHAR[outSize+8];
		normalBuffer.ptr = (void*)backto;
	}
	else backto = (UCHAR*)normalBuffer.ptr;

	if(normalBuffer.ptr == NULL) return true;


//  pointers initialized


//	------- Init nodes -------
	struct node *nodeux = lznodes;
	for(i=0;i<256;i++)
	{
		nodeux[i].data = (unsigned char)i;
		nodeux[i].pastaga = -1;
		nodeux[i].level = 0;
	}
	for( ; i<CO_MAX_NODES;i++)	nodeux[i].pastaga = -1;
//  --------------------------

	long ns=256, backptr;
	backptr = 0;
	decomp_ptr = 8;
	Cbit=1;

	while(decomp_ptr<inSize)
	{
		long number = getinfo(codestream, decomp_ptr, ns, true);
		if(ns<CO_MAX_NODES && (decomp_ptr<inSize-1))
		{
			long next = getinfo(codestream, decomp_ptr, ns+1, false);
			if(next==ns) next = number;
			while(nodeux[next].level != 0)
				next = nodeux[next].pastaga;

			nodeux[ns].data = nodeux[next].data;
			nodeux[ns].level = nodeux[number].level +1;
			nodeux[ns].pastaga = number;
			next = number;
			for(j=nodeux[number].level;j>=0;j--)
			{
				backto[backptr+j] = nodeux[next].data;
				next = nodeux[next].pastaga;
			}
			backptr += nodeux[number].level+1;
			ns++;
		}
		else
		{
			long next=number;
			for(j=nodeux[number].level;j>=0;j--)
			{
				backto[backptr+j] = nodeux[next].data;
				next = nodeux[next].pastaga;
			}
			backptr += nodeux[number].level+1;
		}
		if(backptr==outSize) break;	// check end of file

		if(ns>=CO_MAX_NODES)
		{
			ns=256;
			for(i=256;i<CO_MAX_NODES;i++) nodeux[i].pastaga = -1;
		}
	}
	normalBuffer.size = outSize;
	if( (options & 2) != 0) LZWBuffer.release();
	return false; // that's ok :)
}


long CoHack::filesize(FILE *stream)
{
   long curpos, length;

   curpos = ftell(stream);
   fseek(stream, 0L, SEEK_END);
   length = ftell(stream);
   fseek(stream, curpos, SEEK_SET);
   return length;
}

long CoHack::filesize(char *name)
{
   FILE *stream;
   long curpos, length;

   stream = fopen(name, "rb");
   if(stream == NULL) return(0);
   curpos = ftell(stream);
   fseek(stream, 0L, SEEK_END);
   length = ftell(stream);
   fseek(stream, curpos, SEEK_SET);
   fclose(stream);
   return length;
}

/*
you want docs, hu?
piss off, I m too tired, I can't give you anything...
The end is near for me  :)
*/
long CoHack::hackIt(char *infilename, char *outfilename, long options)
{
	FILE *infile, *outfile;
	char *infilebuffer;

	lznodes = new struct node[CO_MAX_NODES];	// allocate nodes

	long h=0, headerSize = 4, nfile = 0;
	long totalNormalSize = 0, totalLZWSize = 0;
	if( (options & CO_MODE_CRUNCH) != 0)
	{
		outfile = fopen(outfilename, "wb");
		if(outfile == NULL) return 3;

		sstring sS;
//		dont delete base Dir in name
//		char b=0;
//		sS.getBase(infilename);
//		while(sS.base[b++]!=0);

		while(infilename[h] != 0)		// first lets see the header size
		{
			nfile++;
			headerSize +=4;
			while(infilename[h++] != 0) headerSize++;
			headerSize++;
//			headerSize -= b;		// base directory is cutted
		}

		long infilesize;
		long currentPos=headerSize, prevPos;
		long headerWrite=4;
		h = 0;
		fseek(outfile, 0, SEEK_SET);
		nfile = -nfile;
		fwrite(&nfile, 4, 1, outfile);	// write 
		while(infilename[h] != 0)		// first lets see the header size
		{
			infile = fopen(infilename+h, "rb");
			if(infile==NULL) return 2;	// can't read file error code
			infilesize = filesize(infile);
			infilebuffer = new char[infilesize];
			fread((void*)infilebuffer, 1, infilesize, infile);
			fclose(infile);

			normalBuffer.ptr = infilebuffer;
			normalBuffer.size = infilesize;
			if( LZWIt(CO_ALLOC_OUTBUFF | CO_DELETE_INBUFF)) return 1;	// default error code

			totalNormalSize += infilesize;
			// can't pack file(infilebuffer has not been deleted)
			if(LZWBuffer.size == -1)
			{
				LZWBuffer.release();
				LZWBuffer.size = infilesize;
				LZWBuffer.ptr = infilebuffer;		// store original buffer
				fseek(outfile, currentPos,SEEK_SET);
				infilesize = -infilesize;		// indicates the buffer is not packed
				fwrite(&infilesize, 4, 1, outfile);
				normalBuffer.ptr = NULL;	// normalBuffer pointer was equal to LZW
				normalBuffer.size = 0;// kill the pointer, now buffer is on LZWBuffer
			}
			else fseek(outfile, currentPos,SEEK_SET);

			totalLZWSize    += LZWBuffer.size;

			// let's write LZW buffer
			fwrite(LZWBuffer.ptr, 1, LZWBuffer.size, outfile);
			prevPos = currentPos;		// save current LZWBuffer offset for Header
			currentPos = ftell(outfile);

			// let's write header
			fseek(outfile, headerWrite, SEEK_SET);
			fwrite(&prevPos, 4, 1, outfile);
//			h += b;						// skip base directory
			while(1)
			{
				fwrite((void*)(infilename + h), 1, 1, outfile);
				if(infilename[h++] == 0) break;
			}
			headerWrite = ftell(outfile);
			releaseAll();				// release all buffers
		}
		ratio = (float)totalLZWSize / (float)totalNormalSize;
		fclose(outfile);
	}
	else if( (options & CO_MODE_DECRUNCH) != 0)
	{
		long nfile, headRead = 4, offsetFile;
		char a, daName[256], b;
		bool dontPack;

		infile = fopen(infilename, "rb");
		if (infile == NULL) return 2;
		fseek(infile, 0, SEEK_SET);
		fread(&nfile, 4, 1, infile);
		nfile = -nfile;
		long i;
		for(i = 0;i< nfile; i++)
		{
			fseek(infile, headRead, SEEK_SET);
			fread(&offsetFile, 4, 1, infile);
			a = 1;
/*
			while(outfilename[a]!=0) a++;
			
			for(b = 0; b<a;b++)	daName[b] = outfilename[b];
			if( outfilename[a-2] != '\\' ) 
			{
				a++;
				daName[b++] = '\\';
			}
*/
			b = 0;
			while(a!=0)
			{
				fread(&a, 1, 1, infile);
				daName[b++] = a;
			}
			headRead = ftell(infile);
			// check if
			if( (0== _stricmp(daName, outfilename) || ( (options & CO_MODE_DECRUNCHALL) != 0)) )
			{
				fseek(infile, offsetFile, SEEK_SET);
				fread(&LZWBuffer.size, 4, 1, infile);
				if(LZWBuffer.size < 0)
				{
					dontPack = true;
					LZWBuffer.size = -LZWBuffer.size;
				}else
				{
					dontPack = false;
					fseek(infile, -4, SEEK_CUR);
				}

				LZWBuffer.ptr = (void*) new char[LZWBuffer.size];
				fread(LZWBuffer.ptr, 1, LZWBuffer.size, infile);

				long decrunchSize = *((long*)LZWBuffer.ptr+1);
				if( (options & CO_MODE_GETSIZE) != 0) 
				{
					long returnVal = decrunchSize;
					if( dontPack ) returnVal = LZWBuffer.size;
					releaseAll();
					return returnVal;
				}

				totalLZWSize += LZWBuffer.size;

				if(!dontPack)
				{
					if( unLZWIt(CO_ALLOC_OUTBUFF | CO_DELETE_INBUFF)) return 1;
				}
				else
				{
					normalBuffer.ptr = LZWBuffer.ptr;
					normalBuffer.size = LZWBuffer.size;
					LZWBuffer.ptr = NULL;	// swap pointers
					LZWBuffer.size = 0;
				}
	
				totalNormalSize += normalBuffer.size;
	
				// return values in NormalBuffer.ptr and NormalBuffer.size :)
				if( (options & CO_MODE_FORMEM) == 0)
				{
					outfile = fopen(daName, "wb");
					if(outfile == NULL) return 3;
					fwrite(normalBuffer.ptr, 1, normalBuffer.size, outfile);
					fclose(outfile);
					releaseAll();
				}
				if( (options & CO_MODE_DECRUNCHALL) == 0 ) break;
			}
		}
		// file not found
		if( ((options & CO_MODE_DECRUNCHALL) == 0) &&  (i==nfile)) return -4;

		ratio = (float)totalLZWSize / (float)totalNormalSize;
		fclose(infile);
	}

	delete lznodes;							// free nodes
	lznodes = NULL;
	return 0;
}
