// BlinkenSisters - Hunt for the Lost Pixels
//     Bringing back the fun of the 80s
//
// (C) 2005-07 Rene Schickbauer, Wolfgang Dautermann
//
// See License.txt for licensing information
//
// This file includes also material from other authors, see bellow
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>


/*************************************************************************
// This program shows the basic usage of the LZO library.
// We will compress a block of data and decompress again.
//
// For more information, documentation, example programs and other support
// files (like Makefiles and build scripts) please download the full LZO
// package from
//    http://www.oberhumer.com/opensource/lzo/
**************************************************************************/

/* First let's include "minizo.h". */

#include "minilzo.h"
#include "bmfconvert.h"

/* #####################################################
* Size of frames is hardcoded to SCR_WIDTH / SCR_HEIGHT
* #####################################################
*/

/* We want to compress the data block at `in' with length `IN_LEN' to
* the block at `out'. Because the input block may be incompressible,
* we must provide a little more output space in case that compression
* is not possible.
*/

#define IN_LEN 10000000
#define OUT_LEN     (IN_LEN + IN_LEN / 16 + 64 + 3)

static unsigned char __LZO_MMODEL in  [ IN_LEN ];
static unsigned char __LZO_MMODEL out [ OUT_LEN ];


/* Work-memory needed for compression. Allocate memory in units
* of `lzo_align_t' (instead of `char') to make sure it is properly aligned.
*/

#define HEAP_ALLOC(var,size) \
lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ]

static HEAP_ALLOC(wrkmem,LZO1X_1_MEM_COMPRESS);


int main(int argc, char *argv[])
{
    int r;
    lzo_uint in_len;
    lzo_uint out_len;
	bool isMeta = false;
	
	if(argc == 4 && strcmp(argv[1], "META") == 0) {
		printf("META!!!!\n");
		isMeta = true;
	} else if (argc != 6 && argc != 5){
		printf("Usage:\n"
               "bmfcompress fps framecount outputfile.bmf inputdir [soundfile]\n" 
			   "   OR\n"
			   "bmfcompress META configfile outputfile.bmf\n");
		exit(3);
	}
	
	int framecount = atoi(argv[2]);
	
	unsigned long fulllength = 0;
	unsigned long origfulllength = 0;
	
    if (argc < 0 && argv == NULL)   /* avoid warning about unused args */
        return 0;
	
    printf("\nLZO real-time data compression library (v%s, %s).\n",
		   lzo_version_string(), lzo_version_date());
    printf("Copyright (C) 1996-2005 Markus Franz Xaver Johannes Oberhumer\nAll Rights Reserved.\n\n");
	
	
	/*
	 * Step 1: initialize the LZO library
	 */
    if (lzo_init() != LZO_E_OK)
    {
        printf("internal error - lzo_init() failed !!!\n");
        printf("(this usually indicates a compiler bug - try recompiling\nwithout optimizations, and enable -DLZO_DEBUG for diagnostics)\n");
        return 3;
    }
	
	/*
	 * Step 2: prepare the input block that will get compressed.
	 *         We just fill it with zeros in this example program,
	 *         but you would use your real-world data here.
	 */
    in_len = IN_LEN;
	struct stat filestats;
	
	FILE* ofh = fopen(argv[3], "wb");
	if(!ofh) {
		printf("Can't write file %s\n", argv[3]);
		exit(1);
	}
	bmfWriteInt(ofh, BMFTYPE_MAGIC);
	bmfWriteInt(ofh, BMFTYPE_VERSION);
	bmfWriteInt(ofh, BMF_VERSION);
	fulllength += 3 * 4;
	
	if(!isMeta) {
		// --- normal Video BMF File ---
		printf("Compiling normal file...\n");
		bmfWriteInt(ofh, BMFTYPE_FPS);
		bmfWriteInt(ofh, atoi(argv[1]));
		bmfWriteInt(ofh, BMFTYPE_WIDTH);
		bmfWriteInt(ofh, SCR_WIDTH);
		bmfWriteInt(ofh, BMFTYPE_HEIGHT);
		bmfWriteInt(ofh, SCR_HEIGHT);
		bmfWriteInt(ofh, BMFTYPE_FRAMECOUNT);
		bmfWriteInt(ofh, framecount);
		fulllength += 4 * 8;
		
		// ++++++++++++++++++++++ SND ++++++++++++++++++++
		if(argc == 6) {
			bmfWriteInt(ofh, BMFTYPE_FILENAME);
			bmfWriteInt(ofh, strlen(argv[5]));
			fwrite(argv[5], strlen(argv[5]), 1, ofh);
			fulllength += 2 * 4 + strlen(argv[5]);
			
			char tmp1[500];
			sprintf(tmp1, "%s/%s", argv[4], argv[5]);
			
			if(stat(tmp1, &filestats)) {
				printf("Could not stat file %s\n", tmp1);
				exit(2);
			} else {
				in_len = filestats.st_size;
				//printf("File %s has a length of %d\n", tmp1, in_len);
			}
			
			FILE* fh = fopen(tmp1, "rb");
			if(!fh) {
				printf("Could not open %s\n", tmp1);
				exit(1);
			}
			
			if(fread(in, in_len, 1, fh) != 1 ) {
				printf("Could not load %s\n", tmp1);
				exit(1);
			}
			fclose(fh);
			
			r = lzo1x_1_compress(in,in_len,out,&out_len,wrkmem);
			if (r == LZO_E_OK) {
				/*
				 printf("compressed %lu bytes into %lu bytes\n",
						(unsigned long) in_len, (unsigned long) out_len);
				 */
				if (out_len >= in_len) {
					printf("Warning: File %s contains incompressible data.\n", tmp1);
					fulllength += in_len;
					fulllength += 8;
					origfulllength += in_len;
					bmfWriteInt(ofh, BMFTYPE_SND_UNCOMPRESSED);
					bmfWriteInt(ofh, in_len);
					fwrite(in, in_len, 1, ofh);
				} else {
					fulllength += out_len;
					fulllength += 8;
					origfulllength += in_len;
					
					bmfWriteInt(ofh, BMFTYPE_SND_COMPRESSED);
					bmfWriteInt(ofh, out_len);
					fwrite(out, out_len, 1, ofh);
				}
			} else {
				/* this should NEVER happen */
				printf("internal error - compression failed: %d\n", r);
				return 2;
			}
			
		}
		
		// +++++++++++++++++ FRAMES +++++++++++++++++++
		for(int i = 1; i <= framecount; i++) {
			char tmp1[500];
			char tmp2[500];
			tmp1[0] = 0;
			tmp2[0] = 0;
			sprintf(tmp1, "%d", i);
			while(strlen(tmp1) < 4) {
				sprintf(tmp2, "0%s", tmp1);
				sprintf(tmp1, "%s", tmp2);
			}
			strcat(tmp1, ".jpg");
			
			// Write filename
			bmfWriteInt(ofh, BMFTYPE_FILENAME);
			bmfWriteInt(ofh, strlen(tmp1));
			fwrite(tmp1, strlen(tmp1), 1, ofh);
			fulllength += 2 * 4 + strlen(tmp1);
			
			
			sprintf(tmp2, "%s/%s", argv[4], tmp1);
			sprintf(tmp1, "%s", tmp2);
			//printf("Compressing %s...\n", tmp1);
			
			if(stat(tmp1, &filestats)) {
				printf("Could not stat file %s\n", tmp1);
				exit(2);
			} else {
				in_len = filestats.st_size;
				//printf("File %s has a length of %d\n", tmp1, in_len);
			}
			
			FILE* fh = fopen(tmp1, "rb");
			if(!fh) {
				printf("Could not open %s\n", tmp1);
				exit(1);
			}
			
			if(fread(in, in_len, 1, fh) != 1 ) {
				printf("Could not load %s\n", tmp1);
				exit(1);
			}
			fclose(fh);
			
			/*
			 * Step 3: compress from `in' to `out' with LZO1X-1
			 */
			r = lzo1x_1_compress(in,in_len,out,&out_len,wrkmem);
			if (r == LZO_E_OK) {
				/*
				 printf("compressed %lu bytes into %lu bytes\n",
						(unsigned long) in_len, (unsigned long) out_len);
				 */
				if (out_len >= in_len) {
					printf("Warning: File %s contains incompressible data.\n", tmp1);
					fulllength += in_len;
					fulllength += 8;
					origfulllength += in_len;
					bmfWriteInt(ofh, BMFTYPE_FRAME_UNCOMPRESSED);
					bmfWriteInt(ofh, in_len);
					fwrite(in, in_len, 1, ofh);
				} else {
					fulllength += out_len;
					fulllength += 8;
					origfulllength += in_len;
					
					bmfWriteInt(ofh, BMFTYPE_FRAME_COMPRESSED);
					bmfWriteInt(ofh, out_len);
					fwrite(out, out_len, 1, ofh);
				}
			} else {
				/* this should NEVER happen */
				printf("internal error - compression failed: %d\n", r);
				return 2;
			}
		}
		
	} else {
		// META File that holds a BMF AddOn
		printf("Compiling AddOn (META) File...\n");
	    FILE* cfh = fopen(argv[2], "r");
		if(!cfh) {
			printf("Can't read config file!\n");
			exit(1);
		}
		char line[1001];
		char *tmp;
		char *cmd;
		char *filesrc;
		char *filedest;
		while(!feof(cfh)) {
			if(!fgets(line, 1000, cfh)) {
				break;
			}
			
			// Remove Comments and newline characters
			while((tmp = strstr(line, "\n"))) {
				*tmp = 0;
			}
			while((tmp = strstr(line, "\r"))) {
				*tmp = 0;
			}
			while((tmp = strstr(line, "#"))) {
				*tmp = 0;
			}
			
			if(line[0] == '\0') {
				// Ignore remark or empty line
				continue;
			}
			
			// Ok, let's try to parse command
			tmp = strstr(line, "=");
			if(!tmp) {
				printf("Unknown line '%s': Compress failed\n", line);
				return 3;
			}
			*tmp = '\0';
			cmd = line;
			filesrc = tmp;
			filesrc++;
			
			// ok, parse the command
			if(strcmp(cmd, "DIR") == 0) {
				//printf("Create DIR '%s'\n", filesrc);
				// Write filename
				bmfWriteInt(ofh, BMFTYPE_DIR);
				bmfWriteInt(ofh, strlen(filesrc));
				fwrite(filesrc, strlen(filesrc), 1, ofh);
				fulllength += 2 * 4 + strlen(filesrc);
				
			} else if(strcmp(cmd, "REGISTERADDON") == 0) {
					//printf("Create REGISTER ADDON entry '%s'\n", filesrc);
					// Write register string
					bmfWriteInt(ofh, BMFTYPE_REGISTER_ADDON);
					bmfWriteInt(ofh, strlen(filesrc));
					fwrite(filesrc, strlen(filesrc), 1, ofh);
					fulllength += 2 * 4 + strlen(filesrc);
			} else if(strcmp(cmd, "REGISTERMUSIC") == 0) {
					//printf("Create REGISTER MUSIC entry '%s'\n", filesrc);
					// Write register string
					bmfWriteInt(ofh, BMFTYPE_REGISTER_MUSIC);
					bmfWriteInt(ofh, strlen(filesrc));
					fwrite(filesrc, strlen(filesrc), 1, ofh);
					fulllength += 2 * 4 + strlen(filesrc);
			} else if(strcmp(cmd, "REGISTERPOSCAP") == 0) {
				//printf("Create REGISTER POSCAP entry '%s'\n", filesrc);
				// Write register string
				bmfWriteInt(ofh, BMFTYPE_REGISTER_POSCAP);
				bmfWriteInt(ofh, strlen(filesrc));
				fwrite(filesrc, strlen(filesrc), 1, ofh);
				fulllength += 2 * 4 + strlen(filesrc);
			} else if(strcmp(cmd, "FILE") == 0) {
				tmp = strstr(filesrc, "|");
				if(!tmp) {
					printf("File command has no pipe delimeter for srcfile/destfile disambiguation\n");
					return 4;
				}
				filedest = tmp;
				*tmp = '\0';
				filedest++;
				//printf("Trying to add local file '%s' as AddOn file '%s'..\n", filesrc, filedest);

				bmfWriteInt(ofh, BMFTYPE_FILENAME);
				bmfWriteInt(ofh, strlen(filedest));
				fwrite(filedest, strlen(filedest), 1, ofh);
				fulllength += 2 * 4 + strlen(filedest);
				

				if(stat(filesrc, &filestats)) {
					printf("Could not stat file %s\n", filesrc);
					exit(2);
				} else {
					in_len = filestats.st_size;
					//printf("File %s has a length of %d\n", tmp1, in_len);
				}
				
				FILE* fh = fopen(filesrc, "rb");
				if(!fh) {
					printf("Could not open %s\n", filesrc);
					exit(1);
				}
				
				if(fread(in, in_len, 1, fh) != 1 ) {
					printf("Could not load %s\n", filesrc);
					exit(1);
				}
				fclose(fh);
				
				/*
				 * Step 3: compress from `in' to `out' with LZO1X-1
				 */
				r = lzo1x_1_compress(in,in_len,out,&out_len,wrkmem);
				if (r == LZO_E_OK) {
					/*
					 printf("compressed %lu bytes into %lu bytes\n",
							(unsigned long) in_len, (unsigned long) out_len);
					 */
					if (out_len >= in_len) {
						//printf("Warning: File %s contains incompressible data.\n", filesrc);
						fulllength += in_len;
						fulllength += 8;
						origfulllength += in_len;
						bmfWriteInt(ofh, BMFTYPE_METAFILE_UNCOMPRESSED);
						bmfWriteInt(ofh, in_len);
						fwrite(in, in_len, 1, ofh);
					} else {
						fulllength += out_len;
						fulllength += 8;
						origfulllength += in_len;
						
						bmfWriteInt(ofh, BMFTYPE_METAFILE_COMPRESSED);
						bmfWriteInt(ofh, out_len);
						fwrite(out, out_len, 1, ofh);
					}
				} else {
					/* this should NEVER happen */
					printf("internal error - compression failed: %d\n", r);
					return 2;
				}	
				
				
				
				
			
			} else {
				printf("Unknown command '%s'\n", cmd);
				return 5;
			}
		}
		
	}
	
	bmfWriteInt(ofh, BMFTYPE_END_OF_FILE);
	fulllength += 4;
	fclose(ofh);
	
	printf("\n\nCompressed file should be %lu bytes long \n", fulllength);
	printf("Original size: %lu bytes\n", origfulllength);
	printf("Reduced size by %lu bytes\n", origfulllength - fulllength);
	printf("Reduction by %f %%\n", (((float)origfulllength - (float)fulllength) / (float)origfulllength) * (float)100);
	
	
	printf("  DONE  \n");
	
	
	return 0;
}

