// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation; either version 2 of the License, or
//   (at your option) any later version.
//  
//   This program is distributed in the hope that it will be useful, but
//   WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//   General Public License for more details.
// 
//   You should have received a copy of the GNU General Public License
//   along with this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include <cassert>
#include <cstring>
#include <iostream>
using namespace std;

#include "compress.h"

// This goes at the beginning of the whole compressed block of data.
struct CompressHeader {
	unsigned int fullLength;
};

#define LENGTHBITS 15
#define MAXCHUNKLENGTH (1 << (LENGTHBITS - 1))

// This precedes every chunk of bytes, which may be a variable number
// of raw or run length bytes.
struct ChunkHeader {
	
	// 0 = raw length.
	// 1 = run length.
	unsigned short rle    : 1;
	
	// Length of the chunk. (1-128, so add one to this.)
	unsigned short length : LENGTHBITS;
};

static bool shouldDoRunLength(const char *inData, unsigned int lengthLeft, int groupSize) {

	unsigned int checkLen = sizeof(ChunkHeader) + (groupSize * 2);

	if(lengthLeft < checkLen * groupSize) {
		// Not actually enough to justify either way.
		return false;
	}
	
	for(unsigned int i = 1; i < checkLen; i++) {
		for(int j = 0; j < groupSize; j++) {
			if(inData[j] != inData[j + (i * groupSize)]) {
				return false;
			}
		}
	}
	
	// Next few bytes are the same. This means that space WILL be
	// saved by turning it into a run length, which will be the one
	// byte header and one byte data. Everything else is extra.		
	return true;
}

static bool nextFewBytesSame(const char *b1, const char *b2, int length, int lengthLeft) {
	
	if(length > lengthLeft) return false;
	
	for(int i = 0; i < length; i++) {
		
		if(b1[i] != b2[i]) {
			return false;
		}
		
	}
	
	return true;
	
}

char *compressRLE(const char *inData, unsigned int inLength, unsigned int *outLength, int groupSize) {
	
	// Worst case we end up with nothing but raw lengths. The max
	// length of that will then be the incoming length plus one chunk
	// header for every 128 bytes. (Plus one because C++ will round
	// down.) Also add the CompressHeader.
	unsigned int scratchAllocatedLength =
		(sizeof(CompressHeader) +
		inLength +
		((groupSize * inLength / (MAXCHUNKLENGTH-1)) + 1) * sizeof(ChunkHeader));
	
	//scratchAllocatedLength *= 4;
	//scratchAllocatedLength *= groupSize;
	
	// We'll add this up as we go along.
	unsigned int scratchActualLength = 0;
	
	// Build this up and reallocate it when we're done.
	char *scratchBuffer = new char[scratchAllocatedLength];
	assert(scratchBuffer);
	
	unsigned int pos = 0;
	
	CompressHeader *mainHeader = (CompressHeader*)scratchBuffer;
	scratchActualLength += sizeof(CompressHeader);
	
	while(pos < inLength) {
		
		bool doRLE = shouldDoRunLength(inData + pos, inLength - pos, groupSize);
		
		if(doRLE) {
			
			char *runGroup = new char[groupSize];
			
			assert(scratchActualLength < scratchAllocatedLength);
			
			ChunkHeader *header = (ChunkHeader*)&(scratchBuffer[scratchActualLength]);
			scratchActualLength += sizeof(ChunkHeader);
			
			header->rle = 1;
			header->length = 0; // Zero actually means 1 here. For the first byte we got at runByte.

			// Record the bytes.
			for(int i = 0; i < groupSize; i++) {
				runGroup[i] = inData[pos++];
				scratchBuffer[scratchActualLength++] = runGroup[i];
			}
			
			//while(inData[pos] == runByte) {
			while(nextFewBytesSame(runGroup, inData + pos, groupSize, inLength - pos)) {
				
				pos += groupSize;
				header->length++;
				
				if(header->length >= (MAXCHUNKLENGTH-1)) {
					
					// Reached the max length we can express.
					break;
				}
				
				if(pos >= inLength) {
					
					// Reached the end of data to compress.
					break;
				}
				
			}
			
			delete[] runGroup;
			
		} else {
			
			ChunkHeader *header = (ChunkHeader*)&scratchBuffer[scratchActualLength];
			scratchActualLength += sizeof(ChunkHeader);
			
			header->rle = 0;
			header->length = 0; // This will be the unsigned equivalent of -1.
			
			while(!shouldDoRunLength(inData + pos, inLength - pos, groupSize)) {

				for(int i = 0; i < groupSize && pos < inLength; i++) {
					scratchBuffer[scratchActualLength++] = inData[pos++];
				}

				header->length++;
				
				if(header->length >= (MAXCHUNKLENGTH-1)) {
					
					// Reached the max length we can express.
					break;
				}
				
				if(pos >= inLength) {
					
					// Reached the end of data to compress.
					break;
				}
			}
			
		}
		
	}
	
	// Final length adjustments.
	*outLength = scratchActualLength;
	
	mainHeader->fullLength = inLength;

	// Reallocate scratchbuffer as something smaller.
	char *outBuffer = new char[scratchActualLength];
	memcpy(outBuffer, scratchBuffer, scratchActualLength);
	delete[] scratchBuffer;
	return outBuffer;
	
}

char *decompressRLE(const char *inData, unsigned int inLength, unsigned int *outLength, int groupSize) {
	
	if(inLength < sizeof(CompressHeader)) {
		
		// No way that's valid.
		*outLength = 0;
		return NULL;
	}
	
	unsigned int pos = 0;       // Compressed buffer position.
	unsigned int decompPos = 0; // Decompression buffer position.
	
	CompressHeader *mainHeader = (CompressHeader*)inData;
	pos += sizeof(CompressHeader);
	
	char *decompressed = new char[mainHeader->fullLength];
	*outLength = mainHeader->fullLength;
	
	while(pos < inLength && decompPos < *outLength) {
		
		// Next header.
		ChunkHeader *header = (ChunkHeader*)&inData[pos];
		pos += sizeof(ChunkHeader);

		if(pos >= inLength) break;

		// Pull out the run length and adjust it. (0 = 1, etc.)
		unsigned int realLength = header->length;
		
		if(header->rle) {
			
			// Tun length. Copy the same byte group over and over until
			// its length runs out.

			const char *runGroup = inData + pos;
			
			pos += groupSize;
			
			// I have no idea why I have to do this.
			realLength++;
			
			while(realLength) {
				
				for(int i = 0; i < groupSize && decompPos < *outLength; i++) {
					decompressed[decompPos++] = runGroup[i];
				}

				realLength--;
			}			
			
		} else {
			
			// Raw length. Copy bytes directly from the stream until
			// its length runs out.
			
			while(realLength) {

				const char *rawGroup = inData + pos;
				
				pos += groupSize;
				
				for(int i = 0; i < groupSize && decompPos < *outLength; i++) {
					
					if(rawGroup + i >= inData + inLength) {
						// Ran past the end of the buffer! Oh noes!
						break;
					}
					
					decompressed[decompPos++] = rawGroup[i];
				}

				realLength--;

				if(pos >= inLength) break;
			}
		}
	}
	
	if(pos >= inLength && decompPos >= *outLength) {
		
		// Everything went okay. Made it to the end of both buffers.
	
	} else {
		
		// Compression problems? Uncomment these...
		
		/*
		if(pos < inLength) {
			cout << "Didn't make it to the end of decompress input" << pos << " " << inLength << endl;
		}
		if(decompPos < *outLength) {
			cout << "Didn't make it to the end of decompress output " << decompPos << " " << *outLength << endl;
		}
		*/
		
		// Something went wrong.
		delete[] decompressed;
		decompressed = NULL;
		*outLength = 0;
	}
	
	return decompressed;
	
}
