#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <windows.h>
#endif

#ifndef WIN32
#define stricmp strcasecmp
#endif

#include "getopt.h"
#include "opd_jp2.h"
#include "main.h"

static void displayCopyright() {
	printf("OptimiData JPEG2000 compression / decompression tool (v.%d.%d)\n",OPD_JP2_MAJOR_VERSION,OPD_JP2_MINOR_VERSION);
	printf("==============================================================\n");
}

static void displayUsage() {
	printf("\nopd_jp2.exe -i infile -o outfile [options]\n\n");
	printf("OptimiData JPEG2000 compression / decompression tool (v.%d.%d)\n",OPD_JP2_MAJOR_VERSION,OPD_JP2_MINOR_VERSION);
	printf("==============================================================\n");
	printf("-i: Input file name, the following types are allowed          \n");
	printf("    *.bmp: Windows Bitmap                                     \n");
	printf("    *.jpg: old JPEG file                                      \n");
	printf("    *.jp2: JPEG2000 file                                      \n");
	printf("    *.j2k: JPEG2000 codestream                                \n");
	printf("    *.jpc: JPEG2000 codestream                                \n");
	printf("                                                              \n");
	printf("-o: Output file name, the following types are allowed         \n");
	printf("    *.bmp: Windows Bitmap                                     \n");
	printf("    *.jpg: old JPEG file                                      \n");
	printf("    *.j2k: JPEG2000 codestream                                \n");
	printf("    *.jpc: JPEG2000 codestream                                \n");
	printf("    *.jp2: JPEG2000 file                                      \n");
	printf("                                                              \n");
	printf("--------------------------------------------------------------\n");
	printf("JPEG2000 RELATED PARAMETERS                                   \n");
	printf("--------------------------------------------------------------\n");
	printf("                                                              \n");
	printf("-s  <integer>[k/m] specify the size of the output JPEG2000    \n");
	printf("    file. The character following the integer defines         \n");
	printf("    none:  The size is given in bytes                         \n");
	printf("    'k':   The size is given in kilobytes                     \n");
	printf("    'm':   The size is given in megabytes                     \n");
	printf("                                                              \n");
	printf("-r  <float> specify the compression ratio of the output       \n");
	printf("    JPEG2000 file relative to the input image file.           \n");
	printf("                                                              \n");
	printf("-d  <1,2,3,4> downscale factor for decompression to a         \n");
	printf("    different resolution.                                     \n");
	printf("                                                              \n");
	printf("NOTE: IF NEITHER -r NOR -s ARE GIVEN, LOSSLESS ENCODING IS    \n");
	printf("      ENFORCED AND THE RESULTING JPEG2000 IMAGE MAY BECOME    \n");
	printf("      LARGER THAN THE INPUT FILE.                             \n");
	printf("                                                              \n");
	printf("--------------------------------------------------------------\n");
	printf("JPEG RELATED PARAMETERS                                       \n");
	printf("--------------------------------------------------------------\n");
	printf("-q  <integer> specify the image quality between 1...100 of    \n");
	printf("    the output JPEG file (requires *.jpg as output file name) \n");
	printf("                                                              \n");
#ifdef JPEG_LOSSLESS
	printf("-q  [lossless] lossless JPEG compression is enforced.         \n");
	printf("															  \n");
#endif
	printf("NOTE: IF -q IS NOT GIVEN, THE DEFAULT QUALITY OF 90 IS USED.  \n");
#ifdef JPEG_LOSSLESS
	printf("      IF LOSSLESS ENCODING IS ENFORCED THE RESULTING JPEG     \n");
	printf("      IMAGE MAY BECOME LARGER THAN THE INPUT FILE.            \n");
#endif
	printf("                                                              \n");
#ifdef WIN32
	printf("--------------------------------------------------------------\n");
	printf("LICENSING AND REGISTRATION                                    \n");
	printf("--------------------------------------------------------------\n");
	printf("-l  <license_string> registeres the OptimiData JPEG2000       \n");
	printf("    library for general use within the system                 \n");
#endif
}

static void displayError(int error) {
	switch(error) {
	case OPD_ERROR_INVALID_PARAMETER:
		printf("Invalid parameter given. Please refer the documentation   \n");
		printf("for correct settings and values of the parameters!        \n");
		break;

	case OPD_ERROR_INVALID_QUALITY_LAYERS:
	case OPD_ERROR_DESTINATION_BUFFER_TOO_SMALL:
	case OPD_ERROR_NOT_IMPLEMENTED:
		printf("Internal decoding error %d. Please report this error to   \n");
		printf("OptimiData together with the source image sending an email\n");
		printf("to \"info@optimidata.com\"                                  \n");
		break;

	case OPD_ERROR_INCOMPLETE_DATA:
	case OPD_ERROR_HEADER_CORRUPT:
		printf("The input file seems to be corrupt. If you have downloaded\n");
		printf("the file from the internet please check the download      \n");
		printf("settings.                                                 \n");
		break;

	case OPD_ERROR_INVALID_LICENSE:
		printf("Your license appears incorrect. Please check the license  \n");
		printf("string with your purchasing documents and try to register \n");
		printf("the SDK again.                                            \n");
		break;

	case OPD_ERROR_UNREGISTERED:
		printf("Your copy of this software is unregistered. You can       \n");
		printf("purchase a license key at optimidata. Visit our website   \n");
		printf("http://www.optimidata.com                                 \n");
		break;

	case OPD_ERROR_ALREADY_REGISTERED:
		printf("Your copy of this software is already registered.         \n");
		break;

	case OPD_ERROR_DEMO_LICENSE:
		printf("This is a evaluation copy of the software. Please refer   \n");
		printf("our website http://www.optimidata.com for limitations     \n");
		printf("of this software.                                         \n");
		break;

	case OPD_ERROR_DEMO_EXPIRED:
		printf("This evaluation copy of the software has been expired.    \n");
		printf("For purchasing the software please visit our website at   \n");
		printf("http://www.optimidata.com                                 \n");
		break;
	default:
		break;
	}
}

int readJPEG2000(FILE *infile, opd_image_t *image, int iDownscale, int iQuality) {
	unsigned int	len;
	unsigned char	*buffer;
	int				result;

	if (infile != NULL) {
		/* seek to the end and get the length of the file */
		if (fseek(infile,0,SEEK_END) == 0) {
			len = ftell(infile);
			fseek(infile,0,SEEK_SET);
		} else {
			/* Error occured */
			return(OPD_ERROR_FILE_CORRUPT);
		}

		/* Allocate the buffer to hold the input file */
		if ((buffer = malloc(len)) != NULL) {
			/* read the file */
			if (fread(buffer,1,len,infile) == len) {
				/* reading was sucessful, decode the data */
				result = opd_jp2_decompress_scaled(buffer, len, image,iDownscale,iQuality);
				/* buffer not longer needed */
				free(buffer);
				return(result > 0 ? OPD_ERROR_OK : OPD_ERROR_FILE_CORRUPT);
			} else { /* if (fread(buffer,1,len,infile) == len) */
				free(buffer);
				return(OPD_ERROR_FILE_CORRUPT);
			}
		} else { /* if ((buffer = malloc(len)) != NULL) */
			return(OPD_ERROR_NO_MEMORY);
		}
	}
	return(OPD_ERROR_FILE_CORRUPT);
}

int main(int argc, char *argv[]) {
	FILE	*infile;
	FILE	*outfile;
	char	*infilename;
	char	*outfilename;
	char	*inext;
	int		c,bytes,optlen,original_filesize,uncompressed;
	int		error;
	int		license_first,license_second;
	int		jpeg_quality;
	double	ratio;

	unsigned char	*dest;
	int				len;
	int				iDownscale;
	opd_image_t		image;
    opd_jp2_param	cp;

	/* if no commandline option is given, output the Usage and exit */
	if (argc == 1) {
		displayUsage();
		exit(1);
	}

	/* set the variables to default values */
	infilename			= NULL;
	outfilename			= NULL;
	infile				= NULL;
	outfile				= NULL;
	bytes				= -1;
	ratio				= -1;
	original_filesize	= 0;
	jpeg_quality		= 65536; /* initialized default */
	iDownscale			= 0;

	/* parse the command-line */
	while ((c = getopt(argc, argv, "i:o:s:d:r:l:q:")) != -1) {
		switch(c) {
		case 'i':	/* parse the input file name */
			infilename = optarg;
			break;
		case 'o':	/* parse the output file name */
			outfilename = optarg;
			break;
		case 'r':	/* a compression ratio */
			/* is the compression ratio already defined
			 * using the -s parameter */
			if (bytes != -1) {
				displayUsage();
				exit(1);
			}
			if ((ratio = atof(optarg)) == 0.0) {
				displayUsage();
				exit(1);
			}
			break;
		case 'q':
			if (stricmp(optarg,"lossless") == 0) {
				jpeg_quality = -1;
			} else if (sscanf(optarg,"%d",&jpeg_quality) == 1) {
				/* Jpeg quality is read */
				if (jpeg_quality < 0) {
					displayUsage();
					exit(1);
				}
			} else { // cannot be read
				displayUsage();
				exit(1);
			}
			break;
		case 'd':
			/* The downscale resolution */
			if (sscanf(optarg,"%d",&iDownscale) == 1) {
				if ((iDownscale < 1) || (iDownscale > 4)) {
					displayUsage();
					exit(1);
				}
			}
			break;
		case 's':	/* parse the result filesize */
			/* is the filesize already read using the -r
			 * parameter */
			if (ratio != -1) {
				displayUsage();
				exit(1);
			}
			/* the last character is the unit */
			optlen = strlen(optarg);
			switch(optarg[optlen-1]) {
			case 'm':	/* The filesize in Megabytes */
			case 'M':
				if (sscanf(optarg,"%d",&bytes) == 1) {
					/* filesize in kilobytes read */
					bytes *= 1024 * 1024;
				} else {
					displayUsage();
					exit(1);
				}
				break;
			case 'k':	/* The filesize in kilobytes */
			case 'K':
				if (sscanf(optarg,"%d",&bytes) == 1) {
					/* filesize in kilobytes read */
					bytes *= 1024;
				} else {
					displayUsage();
					exit(1);
				}
				break;
			default:	/* The filesize in bytes */
				if (sscanf(optarg,"%d",&bytes) != 1) {
					displayUsage();
					exit(1);
				}
				break;
			}
			break;

#ifdef WIN32
		case 'l':	/* a license code is given for registration */
			if (sscanf(optarg,"%x-%x",&license_first,&license_second) != 2) {
				displayUsage();
				exit(1);
			}
			switch(opd_register(optarg)) {
			case OPD_NO_ERROR:
				fprintf(stderr,"JPEG2000 Library registration using \"%s\" successful\n",optarg);
				exit(1);
			case OPD_ERROR_INVALID_LICENSE:
				fprintf(stderr,"license code \"%s\" is invalid\n",optarg);
				exit(1);
			case OPD_ERROR_ALREADY_REGISTERED:
				fprintf(stderr,"JPEG2000 Library is already registered\n");
				exit(1);
			}
			break;
#endif

		default:
			displayUsage();
			exit(1);
		}
	}

	/* All necessary parameters available */
	if ((infilename == NULL) || (outfilename == NULL)) {
		displayUsage();
		exit(1);
	}

	/* open the input file */
	if ((infile = fopen(infilename,"rb")) != NULL) {
		/* determine the size of the file */
		if (fseek(infile,0,SEEK_END) == 0) {
			original_filesize = ftell(infile);
			rewind(infile);
		}

		/* get the extension of the infilename */
		if ((inext = strrchr(infilename,'.')) != NULL) {
			/* check whether the filetype is supported */
			if (stricmp(inext,".bmp") == 0) {
				if ((error = readBitmap(infile,&image)) != OPD_ERROR_OK) {
					printf("error %d occured", error);
					exit(1);
				}
			} else if ((stricmp(inext,".j2k") == 0) || (stricmp(inext,".jpc") == 0)) {
				if ((jpeg_quality == 65536) || (jpeg_quality == -1)) jpeg_quality = 100;
				if ((error = readJPEG2000(infile,&image,iDownscale,jpeg_quality)) != OPD_ERROR_OK) {
					printf("error %d occured", error);
					exit(1);
				}
			} else if (stricmp(inext,".jp2") == 0) {
				if ((jpeg_quality == 65536) || (jpeg_quality == -1)) jpeg_quality = 100;
				if ((error = readJPEG2000(infile,&image,iDownscale,jpeg_quality)) != OPD_ERROR_OK) {
					printf("error %d occured", error);
					exit(1);
				}
			} else if (stricmp(inext,".jpg") == 0) {
				if ((error = readJPEG(infile,&image)) != OPD_ERROR_OK) {
					printf("error %d occured", error);
					exit(1);
				}
			}
		}
		/* close the input file */
		fclose(infile);
	} else {
		fprintf(stderr,"Cannot open input file %s\n",infilename);
		exit(1);
	}

    if ((outfile = fopen(outfilename, "wb")) == NULL) {
        fprintf(stderr, "failed to open %s for writing\n", outfile);
        exit(1);
    }

	if ((inext = strrchr(outfilename,'.')) != NULL) {
		/* check whether the filetype is supported */
		if (stricmp(inext,".bmp") == 0) {
			writeBitmap(outfile,&image);
		} else if (stricmp(inext,".jpg") == 0) {
			if (jpeg_quality == 65536) jpeg_quality = 90;
			writeJPEG(outfile,&image,jpeg_quality);
		} else if ((stricmp(inext,".j2k") == 0) || (stricmp(inext,".jpc") == 0)){
			/* estimate the uncompressed filesize */
			uncompressed = image.uiWidth * image.uiHeight * image.uiChannels;

			/* fill out the compression params and encode */
			if (ratio != -1) bytes = (int)(((double)(original_filesize)) / ratio);
			cp.filesize				= bytes == -1 ? uncompressed : bytes;
			cp.lossless				= bytes == -1 ? OPD_ENCODE_LOSSLESS : OPD_ENCODE_LOSSY;
			cp.quality_layers		= 1;
			cp.resolution_levels	= 6;
			cp.bCodestream          = 1;	/* true, write codestream */
			cp.iLibraryVersion		= OPD_JP2_LIBRARY_VERSION;

			if ((dest = (unsigned char*)(malloc(cp.filesize * sizeof(unsigned char)))) != NULL) {
				if ((len = opd_jp2_compress(&image, dest, cp.filesize, &cp)) <= 0) {
					/* Error occured */
					displayError(len);
					exit(1);
				}
				fwrite(dest, 1, len, outfile);
				free(dest);
			}
		} else if (stricmp(inext,".jp2") == 0) {
			/* estimate the uncompressed filesize */
			uncompressed = image.uiWidth * image.uiHeight * image.uiChannels;

			/* fill out the compression params and encode */
			if (ratio != -1) bytes = (int)(((double)(original_filesize)) / ratio);
			cp.filesize				= bytes == -1 ? uncompressed : bytes;
			cp.lossless				= bytes == -1 ? OPD_ENCODE_LOSSLESS : OPD_ENCODE_LOSSY;
			cp.quality_layers		= 1;
			cp.resolution_levels	= 6;
			cp.bCodestream          = 0;	/* false, write file format */
			cp.iLibraryVersion		= OPD_JP2_LIBRARY_VERSION;

			if ((dest = (unsigned char*)(malloc(cp.filesize * sizeof(unsigned char)))) != NULL) {
				if ((len = opd_jp2_compress(&image, dest, cp.filesize, &cp)) <= 0) {
					/* Error occured */
					displayError(len);
					exit(1);
				}
				fwrite(dest, 1, len, outfile);
				free(dest);
			}
		}
	}
	opd_jp2_freeImage(&image);
	fclose(outfile);

	/* Everything done, exit gracefully */
	exit(0);
}
