/*
 * Copyright (C) 2005 Adam Johnston <agjohnst@uiuc.edu>
 *
 * 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.
 
 0.1 - Program born by Adam Johnston <agjohnst@uiuc.edu>
 0.2 - Cleaned up slightly, added error messages and big endian support 
	(still bugs) by Jeffrey Nelson <nelsonjm@vt.edu>
 0.3 - Added BGR32 support (to fix bad mencoder on OS X), luminance
        normalization, dithering, and kludge to fix interlacing on
	iPod mini-size AVIs by Zach Brewster-Geisz
	<zach@brewstergeisz.cjb.net>.
 0.4 - Tweaked pattern dither algorithm, added a user-defined
        normalization mode (!) and an option to
        create AVIs that can be previewed with mplayer, by ZBG.
 */


#include <stdio.h>
#include <stdlib.h>
#include "avifile.h"

#define DEF_RES_X_NEW 160
#define DEF_RES_Y_NEW 128


//#define PRINTDEBUG 1

static int be;
const char *invocation;

struct imagem { 
	unsigned char *buffer;
	int x;
	int y;
	int w;
} imagem_t;

FILE * f;
FILE * f2;
static int width, height;
//BITMAPINFO binf;

/* Options vars & defaults */
static char kludge = 0;
static int floor = 0;
static int ceiling = 224;
static char forcefloor = 0;
static char forceceiling = 0;
enum dithertype_t {DITHER_PATTERN, DITHER_RANDOM, DITHER_NONE};
static char dithertype = DITHER_PATTERN;
static char invert = 0;
static char preview = 0;

static int fourccCmp(char s1[4], char s2[4])
{
	if ((s1[0] == s2[0]) &&
	    (s1[1] == s2[1]) &&
	    (s1[2] == s2[2]) &&
	    (s1[3] == s2[3]))
		return 1;
	return 0;
}

void usage()
{
	//FIXME
	fprintf(stderr, "Usage: %s [opts] infile.avi outfile.avi\n", invocation);
	fprintf(stderr, "Options:\n"
		"-k\tKludge to fix bad mencoder encoding for iPod minis\n"
		"\ton Mac OS X--if your mini movies have artifacts, try\n"
		"\tthis option.\n"
		"-f <floor>\tSets the luminance value below which a pixel\n"
		"\twill render as pure white.  Legal values are 0 - 127.\n"
		"\tLuminance is computed on a frame-by-frame basis,\n"  
		"\tunless \'!\' is appended to the value, in which case the\n"
		"\tfloor is forced to this value, regardless of the\n"
		"\tluminance range of the actual frame.  Defaults to 0.\n"
		"-c <ceiling>\tSets the luminance value above which a pixel\n"
		"\twill render as pure black.  Legal values are 128 - 255.\n"
		"\tLuminance is computed on a frame-by-frame basis,\n"  
		"\tunless \'!\' is appended to the value, in which case the\n"
		"\tceiling is forced to this value, regardless of the\n"
		"\tluminance range of the actual frame.  Defaults to 224.\n"
		"-i\tInverts colors, for use in certain first generation\n"
		"\tiPods where the backlight inverts the screen.\n"
		"-r\tBy default, a pattern-based dither is applied to the\n"
		"\tmovie.  If -r is invoked, a randomized dither is applied.\n"
		"\tThis gives greater definition than the standard dither,\n"
		"\tbut creates a snowy picture.\n"
		"-d\tDisables dithering.\n"
		"-p\tPreview mode; outputs an .avi that can be played\n"
		"\tin mplayer for testing purposes, but CANNOT be played\n"
		"\ton the iPod.\n");
	exit(-1);
}

void parseOpts(int argc, char *argv[], char **infile, char **outfile)
{
	int i = 0;
	int getceiling = 0;
	int getfloor = 0;
	invocation = argv[0];
	if (argc < 3) {
		fprintf(stderr, "%s: Not enough arguments!\n", invocation);
		usage();
	}
	while (++i < argc) {
		//fprintf(stderr, "%s\n", argv[i]);
		if (argv[i][0] == '-') {
			char *c;
			//fprintf(stderr, "got dash\n");
			c = argv[i];
			while ((*(++c)) != '\0') {
				switch(*c) {
				case 'k':
					//fprintf(stderr, "k\n");
					kludge = 1;
					break;
				case 'd':
					dithertype = DITHER_NONE;
					break;
				case 'r':
					dithertype = DITHER_RANDOM;
					break;
				case 'f':
					getfloor = 1;
					break;
				case 'c':
					getceiling = 1;
					break;
				case 'i':
					invert = 1;
					break;
				case 'p':
					preview = 1;
					break;
				default:
					fprintf(stderr, "%s: unrecognized option %c\n", invocation, *c);
					usage();
					break;
				}
			}
		} else if (getfloor) {
			char *e;
			if (e = strchr(argv[i], '!')) {
				forcefloor = 1;
				*e = '\0';
				//fprintf(stderr, "Exclaimfloor!\n");
			}
			floor = atoi(argv[i]);
			if ((floor < 0) || (floor > 127)) {
				fprintf(stderr, "%s: Error: floor must be between 0 and 127.\n", invocation);
				usage();
			}
			getfloor = 0;
		} else if (getceiling) {
			char *e;
			if (e = strchr(argv[i], '!')) {
				forceceiling = 1;
				*e = '\0';
				//fprintf(stderr, "Exclaimceiling!\n");
			}
			ceiling = atoi(argv[i]);
			if ((ceiling < 128) || (ceiling > 255)) {
				fprintf(stderr, "%s: Error: ceiling must be between 128 and 255.\n", invocation);
				usage();
			}
			getceiling = 0;
		} else if (NULL == *infile)
			*infile = argv[i];
		else *outfile = argv[i];
	}
}


int main(int argc, char * argv[])
{
	char *infile  = NULL;
	char *outfile = NULL;

	parseOpts(argc, argv, &infile, &outfile);
	return convVideo(infile, outfile);
	fprintf(stderr, "\n");
}

void closeFiles(FILE * f, FILE * f2)
{
	if (f)close(f);
	if (f2) close(f2);
	
}

// Simple endian switching function.
unsigned switch_32(unsigned l)
{
    if (be)
		return ((l & 0xff) << 24)
			| ((l & 0xff00) << 8)
			| ((l & 0xff0000) >> 8)
			| ((l & 0xff000000) >> 24);
    return l;
}

// Simple endian switching function.
short int switch_16(short int l)
{
	if (be)
		return (
				(short int) ((short int)(l) << 0x08) | 
				(short int) ((short int)(l) >> 0x08));
	return l;
}

// Check to see if this is a big or little endian computer.
void test_endian(void)
{
    char ch[4] = { '\0', '\1', '\2', '\3' };
    unsigned i = 0x00010203;
	
    if (*((int *)ch) == i) 
		be = 1;
    else
		be = 0;
    return;
}

int doStrfInfo(aviChunkHdr *hdr, BITMAPINFO * binf, struct imagem curframe[],
	       RGBQUAD * colortable)
{
	static int gotinfo = 0;
	int bitcount = 2;
	int x = 0;
	unsigned char * chunkBuffer;
	if (gotinfo==0)
	{
		BITMAPINFO binfcopy;	
		fprintf(stderr, "gotinfo=0\n");
		//fprintf(stderr, "%d %d %d\n", sizeof(binf), sizeof(binf.bmiHeader),sizeof(BITMAPINFO));
		fread(binf, sizeof(*binf), 1, f);
				
		//fprintf(stderr, "got here\n");	
		width = (switch_32(binf->bmiHeader.biWidth) > DEF_RES_X_NEW) ?
			switch_32(DEF_RES_X_NEW) : binf->bmiHeader.biWidth;
		height = (switch_32(binf->bmiHeader.biHeight) > DEF_RES_Y_NEW) ?
			switch_32(DEF_RES_Y_NEW) : binf->bmiHeader.biHeight;
				
		fprintf(stderr, "biSize: %d\n"
			"biWidth: %d\n"
			"biHeight: %d\n"
			"biBitCount: %d\n"
			"biCompression: %d\n",
			switch_32(binf->bmiHeader.biSize),
			switch_32(binf->bmiHeader.biWidth),
			switch_32(binf->bmiHeader.biHeight),
			switch_16(binf->bmiHeader.biBitCount),
			switch_32(binf->bmiHeader.biCompression));	
		for (x = 0; x < NUM_FRAMES_BUFFER; x++)	
#ifdef FORCE16BIT
			curframe[x].buffer = (unsigned char *)malloc(DEF_RES_X_NEW*DEF_RES_Y_NEW*2);
#else 
		curframe[x].buffer = (unsigned char *)malloc(DEF_RES_X_NEW * DEF_RES_Y_NEW * 3); 
#endif	
				
		if (switch_16(binf->bmiHeader.biBitCount)==8) {
			colortable = malloc(256*sizeof(RGBQUAD));	
			fread(colortable, sizeof(RGBQUAD), 256, f);
			fseek(f, -256*sizeof(RGBQUAD), SEEK_CUR);
		}	
		gotinfo = 1;	
		fseek(f, -sizeof(*binf), SEEK_CUR);
		fseek(f, switch_32(hdr->ckSize), SEEK_CUR);	
		memcpy(&binfcopy, binf, sizeof(*binf)); //***
				
		binfcopy.bmiHeader.biWidth = width;
		binfcopy.bmiHeader.biHeight = height;	
		binfcopy.bmiHeader.biBitCount = switch_16(bitcount);
		hdr->ckSize = switch_32(sizeof(binfcopy));	
		fwrite(hdr, sizeof(*hdr), 1, f2);
		fwrite(&binfcopy, 1, sizeof(binfcopy), f2);
				
				
				
	} else {
		fprintf(stderr, "gotinfo!=0\n"); 
		chunkBuffer = (unsigned char *) malloc(switch_32(hdr->ckSize));
		fread(chunkBuffer, 1, switch_32(hdr->ckSize), f);
		fwrite(hdr, sizeof(*hdr), 1, f2);
		fwrite(chunkBuffer, 1, switch_32(hdr->ckSize), f2);
		free(chunkBuffer);	
	}
}

int dither(int grayValue, int horiz, int vert)
{
	int ditherfactor = grayValue % 85;
	switch (dithertype) {
	case DITHER_NONE:
		return 0;
	case DITHER_RANDOM:
		return (ditherfactor > (rand() % 85));
	case DITHER_PATTERN:
		ditherfactor *= 8;
		ditherfactor /= 85;
		switch (ditherfactor) {
		case 0: // pure color
			return 0;
		case 1: //12% gray
			return (((horiz % 4) == 0) && ((vert % 4) == 0));
		case 2: // 25% gray
			return ((((horiz % 4) == 0) && ((vert % 4) == 0)) ||
				(((horiz % 4) == 2) && ((vert % 4) == 2)));
		case 3: // 37% gray
			return (((horiz % 2) == 0) && ((vert % 2) == 0));
		case 4: // 50% gray
			return !((horiz % 2) ^ (vert % 2));
		case 5: // 62% gray
			return !(((horiz % 2) == 0) && ((vert % 2) == 1));
		case 6: // 75% gray
			return !((((horiz % 4) == 3) && ((vert % 4) == 0)) ||
				(((horiz % 4) == 1) && ((vert % 4) == 2)));
		case 7: // 87% gray
			return !(((horiz % 4) == 3) && ((vert % 4) == 0));
		}
	}
}

int normalize(int inValue, char turningAround)
{
	static int old_lo_luma = 0;
	static int old_hi_luma = 224;
	static int cur_lo_luma = 127;
	static int cur_hi_luma = 128;
	if (!turningAround) {
		if (forcefloor)
			old_lo_luma = floor;
		if (forceceiling)
			old_hi_luma = ceiling;

		if ((inValue - old_lo_luma) >= ceiling)
			return 255;
		if (inValue <= floor)
			return 0;

		if ((inValue > cur_hi_luma) && (inValue <= ceiling))
			cur_hi_luma = inValue;
		if ((inValue < cur_lo_luma) && (inValue >= floor))
			cur_lo_luma = inValue;

		inValue -= old_lo_luma;
		if (inValue < 0)
			return 0;
		inValue *= (255.0 / (old_hi_luma - old_lo_luma));
		if (inValue > 255)
			return 255;
		return inValue;
	} else { // when finishing a frame, we need to reset these vars.
		old_lo_luma = cur_lo_luma;
		old_hi_luma = cur_hi_luma;
		cur_lo_luma = 127;
		cur_hi_luma = 128;
		//fprintf(stderr, "lo: %d hi:%d\n", old_lo_luma, old_hi_luma);
		return 0;
	}
}
int processFrame(aviChunkHdr *hdr, BITMAPINFO * binf, 
		 struct imagem curframe[])
{
	static int curbufferframe = 0;
	int countpix;
	static long frames = 0;
	long x, y, z, w;
	unsigned char * buf_tmp;
	int kludger = 0;

	if (curframe[curbufferframe].buffer!=NULL)
	{	
		int factor = 4;
		int curspot = 3;
				
		countpix = 0;
		//buf_tmp = curframe[curbufferframe].buffer;	
		curframe[curbufferframe].x = width;
		curframe[curbufferframe].y = height;
		curframe[curbufferframe].w = 2;
				
		w = ftell(f);
				
		buf_tmp = curframe[curbufferframe].buffer;
		//word = (unsigned short *)(curframe[curbufferframe].buffer);
				
		z = 0;
		curspot = 3;
		for (y = switch_32(binf->bmiHeader.biHeight)-1; (y>=0) && (z < DEF_RES_Y_NEW); y--)
		{
			fseek(f, w+z*switch_32(binf->bmiHeader.biWidth)*switch_16(binf->bmiHeader.biBitCount)/8, SEEK_SET);
					
			for (x = 0; (x < switch_32(width)/factor); x++)
			{
				//*****
				if ((factor==1) && (switch_16(binf->bmiHeader.biBitCount)==24)) { // never accessed
/*							fread(pchar, sizeof(char), 3, f);
							r = pchar[2] << 8;
							g = pchar[1] << 8;
							b = pchar[0] << 8;
							r &= 0xf800;
							g &= 0xfc00;
							b &= 0xf800;
							*(word) = r|g>>5|b>>11;
							word++;
*/						} else if ((factor==4) && ((switch_16(binf->bmiHeader.biBitCount)==24) || (switch_16(binf->bmiHeader.biBitCount)==32)))
 {	
	 char col;	
	 unsigned char curcol;	
	 int grayColor;
	 
	 long blueValue, greenValue, redValue;
	 col = 0;
	 curcol = 0;
	 for (curspot = 0; curspot < 4; curspot++)
	 {
		 unsigned char pc;
		 // if (kludge) {
		 if (kludge && (kludger == 1)) {
			 curspot++;
			 kludger = 0;
		 } else if (kludge && (kludger == (-1))) {
			 fseek(f, 4, SEEK_CUR);
			 kludger = 0;
		 } else
			 kludger = 0;
			 //}
		 fread(&pc, sizeof(char), 1, f);
		 blueValue = (255-pc);
								
		 fread(&pc, sizeof(char), 1, f);
		 greenValue = (255-pc);
		 fread(&pc, sizeof(char), 1, f);
		 redValue = (255-pc);
		 if (switch_16(binf->bmiHeader.biBitCount)==32)
			 fread(&pc, sizeof(char), 1, f); // discard alpha value
		 grayColor = (30*redValue+59*greenValue+11*blueValue) / 100;
		 
		 grayColor = normalize(grayColor, 0);
		 curcol = grayColor / 85;
		 if (curcol < 3)
			 curcol += dither(grayColor, curspot, z);
		 if (invert)
			 curcol = 3 - curcol;
		 col ^= (curcol << (curspot << 1));
	 }
	 countpix += 4;
	 *buf_tmp = col;	
	 buf_tmp++;	
							
 }
						
						
			}
					
			z++;
			if (kludge)
				kludger = (z % 2)?(1):(-1);
		}
				
		fseek(f, w+switch_32(hdr->ckSize), SEEK_SET);
		hdr->ckSize = switch_32(switch_32(curframe[curbufferframe].x) * switch_32(curframe[curbufferframe].y) * 
				       curframe[curbufferframe].w / 8);
		fwrite(hdr, sizeof(*hdr), 1, f2);
		fwrite(curframe[curbufferframe].buffer, 1, switch_32(hdr->ckSize), f2); 	
				
		fprintf(stderr, "Writing frame \t %d \t at position \t %d\r", frames++, ftell(f));	
		curbufferframe++;
		//curonframe = 0;
		z = normalize (z, 1);  // reset normalization values!
		                       // z is just a junk var in this case
		if (curbufferframe>=NUM_FRAMES_BUFFER)
		{
			curbufferframe = 0;
			//curonframe = 0;
		}	
				
	}
}

int convVideo(char * filename, char * outfilename)
{
	//FILE * f;
	//FILE * f2;
	unsigned int filesize;	
	AVIFILE af;
	struct imagem curframe[NUM_FRAMES_BUFFER];		
	
	char fourcc[4];
	long frames;
	RGBQUAD * colortable = NULL;
	AVISTREAMHEADER avsh;
	BITMAPINFO binf;
	//int gotinfo;
	//unsigned char * pchar;
	//unsigned char somechar[3];
	long x, y, z, w;
	unsigned char redValue, greenValue, blueValue;	
	int curbufferframe;
	//int curonframe;
	//unsigned short *word;
	unsigned char * chunkBuffer;
	//unsigned r, g, b;
	int countpix;	
	long ccoff;	
	int res;	
	unsigned char * randombuff;	
	riffHdr fileHdr;
	aviChunkHdr hdr;
	//pchar = (unsigned char *)&somechar;
	//gotinfo = 0;
	frames = 0;
	curbufferframe = 0;	
	
	int counter = 0;
	// See if we are big or little endian and set a flag if we are.
	test_endian();
	
	// open files
	f = fopen(filename, "r");
	f2 = fopen(outfilename, "w");
	if (!f || !f2)
	{
		fprintf(stderr, "%s: Error: can't open one or more of the files.\n", invocation);
		closeFiles(f, f2);
		return 1; 
	}
	
	// Get size of file.
	fseek(f, 0, SEEK_END);
	filesize = ftell(f);
	rewind(f);
	fprintf(stderr, "Filesize is %d\n", filesize);	
	
	// Make sure file is big enough to read first few bits.
	if (filesize < sizeof(fileHdr))
	{
		fprintf(stderr, "Error: file too small.\n");
		closeFiles(f, f2);
		return 1;
	}
	
	// Read format to make sure it is (RIFF)
	fread(&fileHdr, sizeof(fileHdr), 1, f);	
	if (!fourccCmp(fileHdr.ckID, "RIFF"))
	{
		fprintf(stderr, "Error: bad file format.\n");
		closeFiles(f, f2);
		return 1;			
	}
	// Write format (RIFF) to output.
	fwrite(&fileHdr, sizeof(fileHdr), 1, f2);	
	
	// Nullify buffer stuff.
	for (x=0; x<NUM_FRAMES_BUFFER; x++)	
		curframe[x].buffer = NULL;
	
	
	// Keep going until end of stream. 
	while (!feof(f))
	{
		ccoff = ftell(f);	
		res = fread(&hdr.ckID, sizeof(hdr), 1, f);
		if (res <= 0)
			goto done;	
		while (hdr.ckID[0]=='\0')
		{
			fseek(f, ccoff+1, SEEK_SET);
			ccoff++;
			fread(&hdr, sizeof(hdr), 1, f);
		}
		
#ifdef PRINTDEBUG
		fprintf(stderr, "Got: %.4s at off %d\n", hdr.ckID, ftell(f));
		//getchar();	
#endif
		
		if (fourccCmp(hdr.ckID, "LIST")) { // Got LIST code
			fread(&fourcc, sizeof(fourcc), 1, f);
			fwrite(&hdr, sizeof(hdr), 1, f2);
			fwrite(&fourcc, sizeof(fourcc), 1, f2);
		} else if (fourccCmp(hdr.ckID, "avih")) { // Got avih code
			chunkBuffer = (unsigned char *)malloc(switch_32(hdr.ckSize));	
			fread(&(af.hdr), sizeof(af.hdr), 1, f);
			fseek(f, -sizeof(af.hdr), SEEK_CUR);
			fread(chunkBuffer, 1, switch_32(hdr.ckSize), f);	
			fwrite(&hdr, sizeof(hdr), 1, f2);
			fwrite(chunkBuffer, 1, switch_32(hdr.ckSize), f2);		
			free(chunkBuffer);
			fprintf(stderr, "AVI File has %d frames.\n", switch_32(af.hdr.dwTotalFrames));
		} else if (fourccCmp(hdr.ckID, "strh")) { // Got strh code
			fread(&avsh, sizeof(avsh), 1, f);
			fseek(f, -sizeof(avsh), SEEK_CUR);
			chunkBuffer = (unsigned char *) malloc(switch_32(hdr.ckSize));	
			fread(chunkBuffer, 1, switch_32(hdr.ckSize), f);	
			fwrite(&hdr, sizeof(hdr), 1, f2);
			fwrite(chunkBuffer, 1, switch_32(hdr.ckSize), f2);
			free(chunkBuffer);
		} else if (fourccCmp(hdr.ckID, "strf")) { // Got strf code.
			doStrfInfo(&hdr, &binf, curframe, colortable);
		} else if ((fourccCmp(hdr.ckID, "00dc")) || (fourccCmp(hdr.ckID, "00db"))) {
			processFrame(&hdr, &binf, curframe);
		} else if ((fourccCmp(hdr.ckID, "idx1")) || (fourccCmp(hdr.ckID, "indx"))){
			fseek(f, hdr.ckSize, SEEK_CUR); 	
		} else {
			randombuff = malloc(switch_32(hdr.ckSize));
			fread(randombuff, 1, switch_32(hdr.ckSize), f);
			fwrite(&hdr, sizeof(hdr), 1, f2);
			fwrite(randombuff, 1, switch_32(hdr.ckSize), f2);
			free(randombuff);			
		}
		
	}
done:	
	// Close the files
	closeFiles(f, f2);
	
	// Deallocation
	for (x = 0; x < NUM_FRAMES_BUFFER; x++)
		if (curframe[x].buffer!=NULL)
			free(curframe[x].buffer);
	if (colortable!=NULL)
		free(colortable);
}


