/*
	WinSTon

	Compression functions

	These routines uses a very simple algorithm to compress/uncompress data. The compression
	ratio is not any where near something like PKZip but does attempt to reduce/protect the data
	we are concerned with.
*/

#include "..\includes\winston.h"
#include "..\includes\file.h"

#define FLAG_COPIED			0x80
#define FLAG_COMPRESS		0x40
#define CHUNK_SIZE			(16*1024)

unsigned char SrcCompBuffer[CHUNK_SIZE*4],DestCompBuffer[CHUNK_SIZE*4];

//-----------------------------------------------------------------------
/*
	Find string match in compression data
*/
BOOL Compress_GetMatch(unsigned char *pSource,unsigned short int i,unsigned short int SourceSize,short int *pHash,unsigned short int *pSize,short int *pPos)
{
	unsigned short int HashValue = (40543L*((((pSource[i] << 4) ^ pSource[i+1]) << 4) ^ pSource[i+2]) >> 4) & 0xfff;

	*pPos = pHash[HashValue];
	pHash[HashValue] = i;
    if ((*pPos!=-1) && ((i-*pPos)<4096)) {
		for (*pSize=0; ((*pSize<18) && (pSource[i+*pSize] == pSource[*pPos+*pSize]) && ((i+*pSize)<SourceSize)); (*pSize)++);
		return(*pSize>=3);
	}

	return(FALSE);
}

//-----------------------------------------------------------------------
/*
	Compress data chunk
*/
unsigned short int Compress_Pack_Chunk(unsigned char *pSource,unsigned char *pDest,unsigned short int SourceSize)
{
	short int Hash[4096];
	short int Pos;
	unsigned short int Key,Size;
	unsigned short int Command;
	unsigned char Bit=0;
	unsigned short int i=0,j=3,k=1;

	for (Key=0; Key<4096; Key++)
		Hash[Key] = -1;
	pDest[0] = FLAG_COMPRESS;
	for (; (i<SourceSize) && (j<=SourceSize);) {
		if (Bit>15) {
			pDest[k++] = (Command>>8)&0x00ff;
			pDest[k] = Command&0x00ff;
			k = j;
			Bit = 0;
			j += 2;
		}
		for (Size=1; (pSource[i]==pSource[i+Size]) && (Size<0x0fff) && (i+Size<SourceSize); Size++);
		if (Size>=16) {
			pDest[j++] = 0;
			pDest[j++] = ((Size-16)>>8)&0x00ff;
			pDest[j++] = (Size-16)&0x00ff;
			pDest[j++] = pSource[i];
			i += Size;
			Command = (Command<<1) + 1;
		}
		else if (Compress_GetMatch(pSource,i,SourceSize,Hash,&Size,&Pos)) {
			Key = ((i-Pos)<<4) + (Size-3);
			pDest[j++] = (Key>>8)&0x00ff;
			pDest[j++] = Key&0x00ff;
			i += Size;
			Command = (Command<<1) + 1;
		}
		else {
			pDest[j++] = pSource[i++];
			Command = (Command<<1);
		}
        Bit++;
	}

	Command <<= (16-Bit);
	pDest[k++] = (Command>>8)&0x00ff;
	pDest[k] = Command&0x00ff;
	if (j>SourceSize) {
		for(j=0; j<SourceSize; pDest[j+1] = pSource[j++]);
		pDest[0] = FLAG_COPIED;
		return(SourceSize+1);
	}

	return(j);
}

//-----------------------------------------------------------------------
/*
	UnCompress data chunk
*/
unsigned short int Compress_UnPack_Chunk(unsigned char *pSource,unsigned char *pDest,unsigned short int SourceSize)
{
	unsigned short int Pos,Size;
	unsigned short int Command=(pSource[1]<<8) + pSource[2];
	unsigned char Bit=16;
	unsigned short int i=3,j=0,k;

	if (pSource[0]==FLAG_COPIED) {
		for (j=1; j<SourceSize; pDest[j-1] = pSource[j++]);
		return(SourceSize-1);
	}
	for (; i<SourceSize;) {
		if (Bit==0) {
			Command = (pSource[i++]<<8);
			Command += pSource[i++];
			Bit = 16;
		}
		if (Command&0x8000) {
			Pos = (pSource[i++]<<4);
			Pos += (pSource[i]>>4);
			if (Pos) {
				Size = (pSource[i++]&0x0f)+3;
				for (k=0; k<Size; k++)
					pDest[j+k] = pDest[j-Pos+k];
				j += Size;
			}
			else {
				Size = (pSource[i++]<<8);
				Size += pSource[i++] + 16;
				for (k=0; k<Size; pDest[j+k++] = pSource[i]);
				i++;
				j += Size;
			}
		}
		else
			pDest[j++] = pSource[i++];

		Command <<= 1;
		Bit--;
	}

	return(j);
}

//-----------------------------------------------------------------------
/*
	Compress data, return size of compressed data
*/
int Compress_Pack(unsigned char *pSource,unsigned char *pDest,int SourceSize)
{
	unsigned int *pChunkSize;
	int ChunkCompressedSize,TotalCompressedSize=0;

	// Pass 16k segments to compression routine, store size of each at beginning
	while(SourceSize>0) {
		pChunkSize = (unsigned int *)pDest;
		pDest += sizeof(int);							// Skip size of this chunk
		ChunkCompressedSize = Compress_Pack_Chunk(pSource,pDest,SourceSize>CHUNK_SIZE ? CHUNK_SIZE:SourceSize);
		*pChunkSize = ChunkCompressedSize;
		pSource += CHUNK_SIZE;
		pDest += ChunkCompressedSize;
		SourceSize -= CHUNK_SIZE;
		TotalCompressedSize += sizeof(int)+ChunkCompressedSize;
	}
	// And term
	pChunkSize = (unsigned int *)pDest;
	*pChunkSize = 0;
	TotalCompressedSize += sizeof(int);

	// Return number of bytes in compressed stream
	return(TotalCompressedSize);
}

//-----------------------------------------------------------------------
/*
	UnCompress data, return total size
*/
int Compress_UnPack(unsigned char *pSource,unsigned char *pDest)
{
	unsigned int *pChunkSize;
	int ChunkSize,ChunkUnCompressedSize,TotalUnCompressedSize=0;

	// Pass 16k segments to decompression routine
	pChunkSize = (unsigned int *)pSource;
	pSource += sizeof(int);							// Skip size of this chunk
	ChunkSize = *pChunkSize;
	while(ChunkSize>0) {
		ChunkUnCompressedSize = Compress_UnPack_Chunk(pSource,pDest,ChunkSize);
		pSource += ChunkSize;
		pDest += ChunkUnCompressedSize;
		TotalUnCompressedSize += ChunkUnCompressedSize;

		pChunkSize = (unsigned int *)pSource;
		pSource += sizeof(int);						// Skip size of this chunk
		ChunkSize = *pChunkSize;
	}

	// Return number of bytes in uncompressed stream
	return(TotalUnCompressedSize);
}

//-----------------------------------------------------------------------
/*
	Compress data file
*/
int Compress_Pack_File(char *pszSrcFileName,char *pszDestFileName)
{
	HFILE SrcFile,DestFile;
	OFSTRUCT SrcFileInfo,DestFileInfo;
	int ChunkCompressedSize,TotalCompressedSize=0;
	int SourceSize;

	// Find length of source file
	SourceSize = File_Length(pszSrcFileName);
	if (SourceSize>0) {
		// Open our two files, ready for compression
		SrcFile = OpenFile(pszSrcFileName,&SrcFileInfo,OF_READ);
		if (SrcFile!=HFILE_ERROR) {
			DestFile = OpenFile(pszDestFileName,&DestFileInfo,OF_CREATE | OF_WRITE);
			if (DestFile!=HFILE_ERROR) {
				// Now run through file, in chunks and compress

				// Pass 16k segments to compression routine, store size of each at beginning
				while(SourceSize>0) {
					// Read in source buffer(limit to CHUNK_SIZE)
					_hread(SrcFile,(char *)SrcCompBuffer,SourceSize>CHUNK_SIZE ? CHUNK_SIZE:SourceSize);
					// And compress
					ChunkCompressedSize = Compress_Pack_Chunk(SrcCompBuffer,DestCompBuffer,SourceSize>CHUNK_SIZE ? CHUNK_SIZE:SourceSize);
					// Save size/data and loop
					_hwrite(DestFile,(char *)&ChunkCompressedSize,sizeof(unsigned int));
					_hwrite(DestFile,(char *)DestCompBuffer,ChunkCompressedSize);
					TotalCompressedSize += sizeof(unsigned int)+ChunkCompressedSize;
					SourceSize -= CHUNK_SIZE;
				}
				// And term
				ChunkCompressedSize = 0;
				_hwrite(DestFile,(char *)&ChunkCompressedSize,sizeof(unsigned int));
				TotalCompressedSize += sizeof(unsigned int);

				_lclose(DestFile);
			}

			_lclose(SrcFile);
		}
	}

	return(TotalCompressedSize);
}

//-----------------------------------------------------------------------
/*
	UnCompress data file
*/
int Compress_UnPack_File(char *pszSrcFileName,char *pszDestFileName)
{
	HFILE SrcFile,DestFile;
	OFSTRUCT SrcFileInfo,DestFileInfo;
	int ChunkSize,ChunkUnCompressedSize,TotalUnCompressedSize=0;

	// Open our two files, ready for compression
	SrcFile = OpenFile(pszSrcFileName,&SrcFileInfo,OF_READ);
	if (SrcFile!=HFILE_ERROR) {
		DestFile = OpenFile(pszDestFileName,&DestFileInfo,OF_CREATE | OF_WRITE);
		if (DestFile!=HFILE_ERROR) {
			// Now run through file, in chunks and uncompress

			// Read size of chunk
			_hread(SrcFile,(char *)&ChunkSize,sizeof(unsigned int));
			while(ChunkSize!=0) {
				// Read in source data
				_hread(SrcFile,(char *)SrcCompBuffer,ChunkSize);
				// Uncompress to buffer, maximum of 'CHUNK_SIZE'
				ChunkUnCompressedSize = Compress_UnPack_Chunk(SrcCompBuffer,DestCompBuffer,ChunkSize);
				// And save
				_hwrite(DestFile,(char *)DestCompBuffer,ChunkUnCompressedSize);
				TotalUnCompressedSize += ChunkUnCompressedSize;

				// Read size of next chunk
				_hread(SrcFile,(char *)&ChunkSize,sizeof(unsigned int));
			}

			_lclose(DestFile);
		}

		_lclose(SrcFile);
	}

	return(TotalUnCompressedSize);
}
