// =============================================================================
// -----------------------------------------------------------------------------
// Program
// -----------------------------------------------------------------------------

#include <stdio.h>
#include <windows.h>

#define rangeof(ENTRY) (0x01<<(0x08*sizeof(ENTRY)))
#define DEBUG FALSE
#define DUMPDOC FALSE

#define OFFSET 0x100
#define LENGTH 0x7F

#define WINOFF 0x200
#define WINOFF_WRAP (WINOFF-1)

#define DirSize 0x1000
char Direct [DirSize], *FileName, *ExtName;

// =============================================================================
// -----------------------------------------------------------------------------
// Compression algorithm
// -----------------------------------------------------------------------------

int CompLZVRAM (char *&Memory, int &Size, int SizePLC)

{
	SizePLC /= 2;
	u_short *Input = (u_short*) Memory;
	int InSize = Size / 2;

	u_short Window [WINOFF * 2] = { 0 };
	u_short Start [rangeof (u_short)] = { 0 };
	int Chains [WINOFF] = { 0 };
	int Counts [rangeof (u_short)] = { 0 };

	u_short *Doc = (u_short*) malloc ((InSize * 2) * sizeof (u_short));
	if (Doc == NULL)
	{
		return (-1);
	}
	int DocSize = 0;

#if DUMPDOC==TRUE
	FILE *Out = fopen ("Out.txt", "wb");
	char String [0x100];
#endif

	int TotalSaved = 0;
	int WinLoc = 0, WinPos = 0;
	u_short Word;

// -----------------------------------------------------------------------------
// Compression loop
// -----------------------------------------------------------------------------

	for ( ; WinLoc < InSize; )
	{

		// --- Updating window ---

		for ( ; WinPos < (WinLoc + LENGTH) && WinPos < InSize; WinPos++)
		{
			if (WinPos > WINOFF)
			{
				Word = Window [(WinPos & WINOFF_WRAP)];
				Counts [Word]--;
			}
			Word = *Input++;
			Window [(WinPos & WINOFF_WRAP) + WINOFF] = Word;
			Window [(WinPos & WINOFF_WRAP)] = Word;
#if DEBUG==TRUE
	FILE *File = fopen ("WINOFF.bin", "wb");
	for (int Loc = 0; Loc < WINOFF * 2; Loc++)
	{
		fputc (Window [Loc] >> 0x08, File);
		fputc (Window [Loc] >> 0x00, File);
	}
	fclose (File);
#endif
		}

		// --- Finding best LZSS ---

		int TotalLength = LENGTH;
		if ((WinLoc + TotalLength) > InSize)
		{
			TotalLength = InSize - WinLoc;
		}
		int BestLength = 1;
		int BestSaved = -1;
		int BestChain = -1;
		Word = Window [(WinLoc & WINOFF_WRAP)];
		int Count = Counts [Word];
		int Chain = Start [Word];
#if DEBUG==TRUE
 printf ("%0.8X (%0.8X) - %0.4X\n", WinLoc * 2, WinPos * 2, Word);
 printf ("TotalLength = %X\n", TotalLength * 2);
 printf ("Count: %X", Count);
#endif
		while (Count-- > 0)
		{
#if DEBUG==TRUE
	fflush (stdin); getchar ( );
	printf ("	%0.8X ", Chain * 2);
#endif
			if ((WinLoc - Chain) > OFFSET)
			{
#if DEBUG==TRUE
				printf ("Too far away");
#endif
				continue;
			}
			u_short *Dest = &Window [(WinLoc+1) & WINOFF_WRAP];
			u_short *Source = &Window [(Chain+1) & WINOFF_WRAP];
			int Length = 1;
			for ( ; Length < TotalLength; Length++)
			{
				if (*Dest++ != *Source++)
				{
					break;
				}
			}
#if DEBUG==TRUE
	printf (" matches = %X", Length * 2);
#endif
			int Saved = (Length * 0x10) - (1 + 0x10);
			if (Saved > BestSaved)
			{
				BestSaved = Saved;
				BestLength = Length;
				BestChain = Chain;
				if (BestLength == TotalLength)
				{
					break;
				}
			}
			Chain = Chains [Chain & WINOFF_WRAP];
		}
#if DEBUG==TRUE
	fflush (stdin); getchar ( );
#endif

		// --- Documenting ---

		if (SizePLC != 0x00)
		{
			if (((WinLoc-1) / SizePLC) != (((WinLoc + BestLength)-1) / SizePLC))
			{
				Doc [DocSize++] = -2;
				Doc [DocSize++] = 0;
#if DEBUG==TRUE
	printf ("	End of module\n");
#endif
#if DUMPDOC==TRUE
	snprintf (String, 0x100, "%0.8X | End of module (Module size %0.4X)\r\n", WinLoc*2, SizePLC*2);
	fputs (String, Out);
#endif
			}
		}
		TotalSaved += BestSaved;
		if (BestSaved == -1)
		{
			Doc [DocSize++] = -1;
			Doc [DocSize++] = Word;
#if DEBUG==TRUE
	printf ("	Uncompressed\n");
#endif
#if DUMPDOC==TRUE
	snprintf (String, 0x100, "%0.8X | Uncompressed %0.4X\r\n", WinLoc*2, Word);
	fputs (String, Out);
#endif
		}
		else
		{
			Doc [DocSize++] = BestLength;
			Doc [DocSize++] = BestChain - WinLoc;
#if DEBUG==TRUE
	printf ("	LZSS Offset %0.8X Length %0.2X\n", BestChain*2, BestLength*2);
#endif
#if DUMPDOC==TRUE
	snprintf (String, 0x100, "%0.8X | LZSS Offset %0.8X Length %0.2X\r\n", WinLoc*2, BestChain*2, BestLength*2);
	fputs (String, Out);
#endif
		}
#if DEBUG==TRUE
	fflush (stdin); getchar ( );
#endif

		// --- Update chains/Counts ---

		do
		{
			Word = Window [(WinLoc & WINOFF_WRAP)];
			Chains [(WinLoc & WINOFF_WRAP)] = Start [Word];
			Start [Word] = WinLoc++;
			Counts [Word]++;
		}
		while (--BestLength > 0x00);
	}

#if DUMPDOC==TRUE
	fclose (Out);
#endif

// -----------------------------------------------------------------------------
// Saving/packing data
// -----------------------------------------------------------------------------

	int OutSize = (((((DocSize / 2) + 1) * 0x11) + 0x0F) / 0x10) * 2; // The +1 is for the end file marker
	char *Output = (char*) calloc (OutSize, sizeof (char));
	if (Output == NULL)
	{
		free (Doc); Doc = NULL;
		return (-1);
	}
	u_short Field;
	int Count = 0x10;
	int FieldLoc = 0;
	int OutLoc = 2;
	for (int DocLoc = 0; DocLoc < DocSize; )
	{
		if (Doc [DocLoc] == (-1 & 0xFFFF))
		{	// Uncompressed
			Output [OutLoc++] = Doc [++DocLoc];		// Word is little endian...
			Output [OutLoc++] = Doc [DocLoc++] >> 0x08;
			Field <<= 1;	if (--Count <= 0) { Output [FieldLoc++] = Field >> 8; Output [FieldLoc++] = Field; FieldLoc = OutLoc; OutLoc += 2; Count = 0x10; }
		}
		else if (Doc [DocLoc] == (-2 & 0xFFFF))
		{	// End marker
			Output [OutLoc++] = 0x00;
			Output [OutLoc++] = 0xFF;
			Field = (Field << 1) | 1;	if (--Count <= 0) { Output [FieldLoc++] = Field >> 8; Output [FieldLoc++] = Field; FieldLoc = OutLoc; OutLoc += 2;  Count = 0x10; }
			DocLoc += 2;
		}
		else
		{	// LZSS
			Output [OutLoc++] = Doc [DocLoc++];
			Output [OutLoc++] = Doc [DocLoc++];
			Field = (Field << 1) | 1;	if (--Count <= 0) { Output [FieldLoc++] = Field >> 8; Output [FieldLoc++] = Field; FieldLoc = OutLoc; OutLoc += 2;  Count = 0x10; }
		}
	}
	Output [OutLoc++] = 0x00;
	Output [OutLoc++] = 0x00;
	Field = (Field << 1) | 1;	if (--Count <= 0) { Output [FieldLoc++] = Field >> 8; Output [FieldLoc++] = Field; Count = 0x10; }
	if (Count != 0x10)
	{
		do
		{
			Field <<= 1;
		}
		while (--Count > 0);
		Output [FieldLoc++] = Field >> 8;
		Output [FieldLoc++] = Field;
	}
	OutSize = OutLoc;
	free (Doc); Doc = NULL;
	free (Memory);
	Memory = Output;
	Size = OutLoc;
}

// =============================================================================
// -----------------------------------------------------------------------------
// Main routine
// -----------------------------------------------------------------------------

int main (int ArgNumber, char **ArgList, char **EnvList)

{
	printf ("LZVRAM Compressor - by MarkeyJester\n\n");
	if (ArgNumber <= 1)
	{
		printf (" -> Pass one or more files to this program to compress them in LZVRAM format\n"
			"\nPress enter key to exit...\n");
		fflush (stdin); getchar ( ); return (0);
	}
	int SizePLC = 0;
	printf ("    Please type the PLC size: ");
	scanf ("%X", &SizePLC);
	printf ("\n");
	int TotalBefore = 0, TotalAfter = 0;
	for (int ArgCount = 1; ArgCount < ArgNumber; ArgCount++)
	{
		FileName = Direct;
		ExtName = NULL;
		for (int Loc = 0; ; Loc++)
		{
			char Byte = ArgList [ArgCount] [Loc];
			Direct [Loc] = Byte;
			if (Byte == '\\' || Byte == '/')
			{
				FileName = &Direct [Loc+1];
			}
			else if (Byte == '.')
			{
				ExtName = &Direct [Loc];
			}
			if (ArgList [ArgCount] [Loc] == 0)
			{
				if (ExtName == NULL)
				{
					ExtName = &Direct [Loc];
				}
				break;
			}
		}
		printf (" -> %s\n", FileName);
		FILE *File = fopen (Direct, "rb");
		if (File == NULL)
		{
			printf ("    Error; could not open the file...\n");
			continue;
		}
		fseek (File, 0x00, SEEK_END);
		int Size = ftell (File);
		char *Memory = (char*) calloc (Size + 1, sizeof (char));
		Memory [Size] = 0;
		if (Memory == NULL)
		{
			fclose (File);
			printf ("    Error; could not allocate memory...\n");
			continue;
		}
		rewind (File);
		for (int Loc = 0; Loc < Size; Loc++)
		{
		//	Memory [(Loc & -2) + ((Loc + 1) & 1)] = fgetc (File);
			Memory [Loc] = fgetc (File);
		}
		fclose (File);
		int OldSize = Size;
		Size += (Size & 1);

		if (CompLZVRAM (Memory, Size, SizePLC) == -1)
		{
			free (Memory); Memory = NULL;
			printf ("    Error; compression allocation...\n");
			continue;
		}
		printf ("    Size %X | New size %X\n", OldSize, Size);
		TotalBefore += OldSize;
		TotalAfter += Size;

		strcpy (ExtName, ".lzv");
		if ((File = fopen (Direct, "wb")) == NULL)
		{
			free (Memory); Memory = NULL;
			printf ("    Error; could not create the file...\n");
			continue;
		}
		for (int Loc = 0; Loc < Size; Loc++)
		{
			fputc (Memory [Loc], File);
		}
		fclose (File);
		free (Memory); Memory = NULL;
	}
	printf ("\n"
		"  Total size before: %8X\n"
		"  Total size after:  %8X\n", TotalBefore, TotalAfter);
	printf ("\nPress enter key to exit...\n");
	fflush (stdin); getchar ( ); return (0);
}

// =============================================================================
