/* LzmaLibExports.c -- LZMA library DLL Entry point
2008-10-04 : Igor Pavlov : Public domain */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "lzma/LzmaLib.h"
#include "md5/md5.h"

#define CTT_FILE 0
#define CTT_BUFFER 1

#pragma region Compression Variables

unsigned char* CurCompressBuffer = NULL;
unsigned char* CurCompressBufferPos = NULL;
unsigned char* CurCompressBufferEnd = NULL;
unsigned char* CurReadBuffer = NULL;
unsigned char* CurReadBufferPos = NULL;

FILE *CurCompressOutFile = NULL;
int CurCompressingType = CTT_BUFFER;
int CurComprBlockSize = 0;
int CurComprABlockSize = 0;

#pragma endregion


#pragma region Decompression Variables

unsigned char* CurDecompressBuffer = NULL;
unsigned char* CurDecompressBufferPos = NULL;
unsigned char* CurWriteBuffer = NULL;
unsigned char* CurWriteBufferPos = NULL;
unsigned char* CurWriteBufferEnd = NULL;

FILE* CurDecompressOutFile = NULL;
int CurDecompressingType = CTT_BUFFER;
int CurDecomprBlockSize = 0;
int CurDecomprABlockSize = 0;
int CurDecomprBlockLen = 0;

#pragma endregion


void WINAPI DecompressionStream_Free() {
	if (CurDecompressBuffer) {
		free(CurDecompressBuffer);
		CurDecompressBuffer = NULL;
		CurDecompressBufferPos = NULL;
		CurDecomprBlockSize = 0;
		CurDecomprABlockSize = 0;
	}

	if (CurWriteBuffer) {
		free(CurWriteBuffer);
		CurWriteBuffer = NULL;
		CurWriteBufferPos = NULL;
		CurWriteBufferEnd = NULL;
	}

	if (CurDecompressOutFile) {
		fclose(CurDecompressOutFile);
		CurDecompressOutFile = NULL;
	}
}

BOOL WINAPI DecompressionStream_Init(const char* fileName) {
	if (CurDecompressOutFile) {
		fclose(CurDecompressOutFile);
		CurDecompressOutFile = NULL;
	}

	CurDecomprBlockSize = 0;

	if (fileName == NULL) {
		CurDecompressingType = CTT_BUFFER;
	}
	else {
		CurDecompressingType = CTT_FILE;
		CurDecompressOutFile = fopen(fileName, "a+b");
		if (CurDecompressOutFile == NULL) {
			return FALSE;
		}
	}

	return TRUE;
}

BOOL DecompressionStream_Flush() {
	size_t DstLen = CurDecomprBlockSize;
	size_t SrcLen = CurDecompressBufferPos - CurDecompressBuffer;
	size_t cSize;
	size_t cOff;
	void* reallocBuff;
	unsigned int *pOrigSrcSize = (unsigned int*) CurDecompressBuffer;
	int Res;
	size_t SrcLen2 = SrcLen - 9;
	if (SrcLen == 0) {
		return TRUE;
	} else if (SrcLen < 9) {
		return FALSE;
	} else if (SrcLen != CurDecompressBufferPos - CurDecompressBuffer) {
		return FALSE;
	}
	if (*pOrigSrcSize > DstLen)
		return FALSE;

	DstLen = *pOrigSrcSize;

	Res = LzmaUncompress(CurWriteBufferPos, &DstLen, CurDecompressBuffer + 9, &SrcLen2, CurDecompressBuffer+4, 5);
	if (Res != SZ_OK && Res != SZ_ERROR_INPUT_EOF) {
		return FALSE;
	}
	if (DstLen != *pOrigSrcSize) {
		return FALSE;
	}
	CurWriteBufferPos += DstLen;
	if (CurDecompressingType == CTT_FILE) {
		fwrite(CurWriteBuffer, DstLen, 1, CurDecompressOutFile);
		CurWriteBufferPos = CurWriteBuffer;
	} else {
		cSize = CurWriteBufferEnd - CurWriteBuffer;
		cOff = CurWriteBufferPos - CurWriteBuffer;
		if ((CurWriteBufferEnd - CurWriteBufferPos) < CurDecomprBlockSize) {
			cSize += CurDecomprBlockSize;
			reallocBuff = realloc(CurWriteBuffer, cSize);
			if (reallocBuff == NULL)
				return FALSE;
			CurWriteBuffer = (unsigned char*)reallocBuff;
			CurWriteBufferPos = CurWriteBuffer + cOff;
			CurWriteBufferEnd = CurWriteBuffer + cSize;
		}
	}
	CurDecompressBufferPos = CurDecompressBuffer;
	return TRUE;
}

BOOL WINAPI DecompressionStream_Write(unsigned char* data, int dataLen) {
	static char TmpDBuf[16];
	static char* TmpDBufPos = TmpDBuf;
	size_t wSize;
	size_t wNeed;
	if (CurDecomprBlockSize == 0) {
		wNeed = (size_t)4 - (TmpDBufPos - TmpDBuf);
		wSize = min(wNeed, (size_t)dataLen);
		memcpy(TmpDBufPos, data, wSize);
		dataLen -= (int)wSize;
		data += wSize;
		TmpDBufPos += wSize;
		if ((TmpDBufPos - TmpDBuf) < 4) {
			return TRUE;
		}
		TmpDBufPos = TmpDBuf;
		CurDecomprBlockSize = *(unsigned int*) TmpDBufPos;

		if (CurDecomprBlockSize < 4096 || CurDecomprBlockSize > 33554432) {
			DecompressionStream_Free();
			return FALSE;
		}

		if (CurDecomprBlockSize > CurDecomprABlockSize) {
			CurDecomprABlockSize = CurDecomprBlockSize;
			CurDecompressBuffer = (unsigned char*) malloc(CurDecomprBlockSize + 65536);
		}

		if (CurWriteBufferEnd - CurWriteBuffer < CurDecomprBlockSize) {
			CurWriteBuffer = (unsigned char*) malloc(CurDecomprBlockSize);
			CurWriteBufferEnd = CurWriteBuffer + CurDecomprBlockSize;
		}


		CurDecompressBufferPos = CurDecompressBuffer;
		CurWriteBufferPos = CurWriteBuffer;

		CurDecomprBlockLen = 0;
	}

	if (dataLen == 0) {
		return TRUE;
	}

	if (CurDecomprBlockLen == 0) {
		wNeed = 4 - (CurDecompressBufferPos - CurDecompressBuffer);
		wSize = min(wNeed, (size_t)dataLen);
		memcpy(CurDecompressBufferPos, data, wSize);
		CurDecompressBufferPos += wSize;
		dataLen -= (int)wSize;
		data += wSize;
		if ((CurDecompressBufferPos - CurDecompressBuffer) < 4) {
			return TRUE;
		}
		CurDecompressBufferPos = CurDecompressBuffer;
		CurDecomprBlockLen = *(unsigned int*) CurDecompressBufferPos;

		if (CurDecomprBlockLen < 9 || CurDecomprBlockLen > CurDecomprBlockSize + 65536) {
			DecompressionStream_Free();
			return FALSE;
		}
		CurDecomprBlockLen;
	}

	while(dataLen) {
		wNeed = CurDecomprBlockLen - (CurDecompressBufferPos - CurDecompressBuffer);
		wSize = min(wNeed, (size_t)dataLen);
		memcpy(CurDecompressBufferPos, data, wSize);
		CurDecompressBufferPos += wSize;
		dataLen -= (int)wSize;
		data += wSize;
		if (CurDecomprBlockLen == (CurDecompressBufferPos - CurDecompressBuffer)) {
			if (!DecompressionStream_Flush()) {
				DecompressionStream_Free();
				return FALSE;
			}
			CurDecompressBufferPos = CurDecompressBuffer;
			CurDecomprBlockLen = 0;

			wNeed = 4 - (CurDecompressBufferPos - CurDecompressBuffer);
			wSize = min(wNeed, (size_t)dataLen);
			memcpy(CurDecompressBufferPos, data, wSize);
			CurDecompressBufferPos += wSize;
			dataLen -= (int)wSize;
			data += wSize;
			if ((CurDecompressBufferPos - CurDecompressBuffer) < 4) {
				return TRUE;
			}
			CurDecompressBufferPos = CurDecompressBuffer;
			CurDecomprBlockLen = *(unsigned int*) CurDecompressBufferPos;

			if (CurDecomprBlockLen < 9 || CurDecomprBlockLen > CurDecomprBlockSize + 65536) {
				DecompressionStream_Free();
				return FALSE;
			}
			CurDecomprBlockLen;
		}
	}

	return TRUE;
}

BOOL WINAPI DecompressionStream_WriteFile(char* fileName) {
	static unsigned char TmpBuf[32768];
	FILE* fl = fopen(fileName, "rb");
	size_t rd;
	if (fl == NULL) {
		return FALSE;
	}

	rd = fread(TmpBuf, 1, sizeof(TmpBuf), fl);
	while (rd) {
		if (!DecompressionStream_Write(TmpBuf, (int)rd)) {
			fclose(fl);
			return FALSE;
		}
		rd = fread(TmpBuf, 1, sizeof(TmpBuf), fl);
	}
	fclose(fl);
	return TRUE;
}

BOOL WINAPI DecompressionStream_Finish() {

	if (!DecompressionStream_Flush()) {
		return FALSE;
	}
	if (CurDecompressBufferPos != CurDecompressBuffer) {
		DecompressionStream_Free();
	}
	if (CurDecompressingType == CTT_FILE) {
		fclose(CurDecompressOutFile);
		CurDecompressOutFile = NULL;
		return TRUE;
	} else {
		return TRUE;
	}
}

int WINAPI DecompressionStream_GetOutBufferLen() {
	return (int)(CurWriteBufferPos - CurWriteBuffer);
}

void WINAPI DecompressionStream_GetOutBuffer(unsigned char* buffer, int start, int size) {
	memcpy(buffer, CurWriteBuffer + start, size);
}


void WINAPI CompressionStream_Free() {
	if (CurCompressBuffer) {
		free(CurCompressBuffer);
		CurCompressBuffer = NULL;
		CurCompressBufferPos = NULL;
		CurCompressBufferEnd = NULL;
		CurComprBlockSize = 0;
		CurComprABlockSize = 0;
	}

	if (CurReadBuffer) {
		free(CurReadBuffer);
		CurReadBuffer = NULL;
		CurReadBufferPos = NULL;
	}

	if (CurCompressOutFile) {
		fclose(CurCompressOutFile);
		CurCompressOutFile = NULL;
	}
}

BOOL WINAPI CompressionStream_Init(int blockSize, const char* fileName) {
	int bufsAllocated = 0;

	CompressionStream_Free();
	if (CurCompressOutFile) {
		fclose(CurCompressOutFile);
		CurCompressOutFile = NULL;
	}

	if (blockSize < 4096)
		blockSize = 4096;
	if (blockSize > 33554432)
		blockSize = 33554432;
	
	if (blockSize > CurComprABlockSize) {
		CompressionStream_Free();
	} else {
		bufsAllocated = 1;
	}

	if (fileName == NULL) {
		CurCompressingType = CTT_BUFFER;
	}
	else {
		CurCompressingType = CTT_FILE;
		CurCompressOutFile = fopen(fileName, "a+b");
		if (CurCompressOutFile == NULL) {
			return FALSE;
		}
	}

	if (!bufsAllocated) {
		CurComprABlockSize = blockSize;
		CurCompressBuffer = (unsigned char*) malloc(blockSize + 65536);
		if (CurCompressBuffer == NULL) 
			return FALSE;
		CurReadBuffer = (unsigned char*) malloc(blockSize);
		if (CurReadBuffer == NULL) {
			free(CurCompressBuffer);
			CurCompressBuffer = FALSE;
			return FALSE;
		}
	}

	CurCompressBufferPos = CurCompressBuffer;
	CurReadBufferPos = CurReadBuffer;

	memcpy(CurCompressBufferPos, &blockSize, 4);
	CurCompressBufferPos += 4;
	CurCompressBufferEnd = CurCompressBuffer + blockSize + 65536;

	CurComprBlockSize = blockSize;

	return TRUE;
}

BOOL CompressionStream_Flush(int finalize) {
	size_t dLen = CurCompressBufferEnd - CurCompressBufferPos - 13;
	size_t dProps = 5;
	size_t cOff;
	size_t cSize;
	size_t srcSize = CurReadBufferPos - CurReadBuffer;
	void* reallocBuff;

	//char* tmpBuf;
	//size_t SrcSize2;
	//size_t DstSize2;
	//int Res;

	if (srcSize == 0 && !finalize) {
		return TRUE;
	}
	if (LzmaCompress(CurCompressBufferPos + 13, &dLen, CurReadBuffer, srcSize, CurCompressBufferPos+8, &dProps, 9, 8192 * 1024, 3, 0, 2, 32, 2) != SZ_OK) {
		return FALSE;
	}

	/*
	tmpBuf = (char*) malloc(srcSize+64);
	DstSize2 = srcSize +64 ;
	SrcSize2 = dLen;
	Res = LzmaUncompress(tmpBuf, &DstSize2, CurCompressBufferPos + 13, &SrcSize2, CurCompressBufferPos+8, 5);
	*/

	CurReadBufferPos = CurReadBuffer;
	*(unsigned int*) (CurCompressBufferPos + 4) = (unsigned int)srcSize;
	*(unsigned int*) CurCompressBufferPos = (unsigned int)(dLen + 9);

	dLen += 13;
	CurCompressBufferPos += dLen;
	dLen = CurCompressBufferPos - CurCompressBuffer;
	if (CurCompressingType == CTT_FILE) {
		fwrite(CurCompressBuffer, dLen, 1, CurCompressOutFile);
		CurCompressBufferPos = CurCompressBuffer;
	} else {
		if (!finalize) {
			cOff = CurCompressBufferPos - CurCompressBuffer;
			cSize = CurCompressBufferEnd - CurCompressBuffer;
			cSize += CurComprBlockSize + 65536;
			reallocBuff = realloc(CurCompressBuffer, cSize);
			if (reallocBuff == NULL)
				return FALSE;
			CurCompressBuffer = (unsigned char*)reallocBuff;
			CurCompressBufferEnd = CurCompressBuffer + cSize;
			CurCompressBufferPos = CurCompressBuffer + cOff;
		}
	}

	return TRUE;
}

BOOL WINAPI CompressionStream_Write(const unsigned char* data, int dataLen) {
	const unsigned char* cdata = data;
	size_t wSize;
	size_t AvailSize;
	int Broken;
	if (dataLen == 0) {
		return TRUE;
	}

	do {
		AvailSize = CurComprBlockSize - (CurReadBufferPos - CurReadBuffer);
		Broken = (AvailSize < (size_t)dataLen);
		wSize = min(AvailSize, (size_t)dataLen);
		memcpy(CurReadBufferPos, data, wSize);
		CurReadBufferPos += wSize;
		dataLen -= (int)wSize;
		if (CurReadBufferPos >= CurReadBuffer + CurComprBlockSize) {
			if (!CompressionStream_Flush(FALSE)) {
				return FALSE;
			}
		}
	} while (Broken);

	return TRUE;
}

BOOL WINAPI CompressionStream_WriteFile(const char* fileName, char* md5Buffer) {
	size_t fSize;
	size_t AvailSize;
	size_t wSize;
	int Broken;
	MD5_CTX md5;
	FILE *fl = fopen(fileName, "rb");
	if (fl == NULL) {
		return FALSE;
	}

	fseek(fl, 0, SEEK_END);
	fSize = ftell(fl);
	fseek(fl, 0, SEEK_SET);

	MD5Init(&md5);

	do {
		AvailSize = CurComprBlockSize - (CurReadBufferPos - CurReadBuffer);
		Broken = (AvailSize < fSize);
		wSize = min(AvailSize, fSize);
		fread(CurReadBufferPos, wSize, 1, fl);
		MD5Update(&md5, CurReadBufferPos, (int)wSize);
		CurReadBufferPos += wSize;
		fSize -= wSize;
		if (CurReadBufferPos >= CurReadBuffer + CurComprBlockSize) {
			if (!CompressionStream_Flush(FALSE)) {
				fclose(fl);
				return FALSE;
			}
		}
	} while (Broken);

	MD5Final(&md5);
	fclose(fl);

	if (md5Buffer) {
		memcpy(md5Buffer, md5.digest, 16);
	}

	return TRUE;
}

BOOL WINAPI CompressionStream_Finish() {
	CompressionStream_Flush(TRUE);
	if (CurCompressingType == CTT_FILE) {
		if (CurCompressOutFile) {
			fclose(CurCompressOutFile);
			CurCompressOutFile = NULL;
		}
		return TRUE;

	} else {
		return TRUE;
	}
}

int WINAPI CompressionStream_GetOutBufferLen() {
	return (int)(CurCompressBufferPos - CurCompressBuffer);
}

void WINAPI CompressionStream_GetOutBuffer(unsigned char* buffer, int start, int size) {
	memcpy(buffer, CurCompressBuffer + start, size);
}


BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	hInstance = hInstance;
	dwReason = dwReason;
	lpReserved = lpReserved;
	return TRUE;
}
