/*
This product contains certain software code or other information
("AT&T Software") proprietary to AT&T Corp. ("AT&T").  The AT&T
Software is provided to you "AS IS".  YOU ASSUME TOTAL RESPONSIBILITY
AND RISK FOR USE OF THE AT&T SOFTWARE.  AT&T DOES NOT MAKE, AND
EXPRESSLY DISCLAIMS, ANY EXPRESS OR IMPLIED WARRANTIES OF ANY KIND
WHATSOEVER, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, WARRANTIES OF
TITLE OR NON-INFRINGEMENT OF ANY INTELLECTUAL PROPERTY RIGHTS, ANY
WARRANTIES ARISING BY USAGE OF TRADE, COURSE OF DEALING OR COURSE OF
PERFORMANCE, OR ANY WARRANTY THAT THE AT&T SOFTWARE IS "ERROR FREE" OR
WILL MEET YOUR REQUIREMENTS.

Unless you accept a license to use the AT&T Software, you shall not
reverse compile, disassemble or otherwise reverse engineer this
product to ascertain the source code for any AT&T Software.

(c) AT&T Corp. All rights reserved.  AT&T is a registered trademark of AT&T Corp.

***********************************************************************

Description:

		Program to test the in-memory API to the XMill (de)compression routines

History:

      10/11/2002  - created by Hedzer Westra <hedzer@adlibsoft.com>

*/

#include "stdafx.h"

#define MAXEXPRS 1024

#ifdef _DEBUG
	/* undefine to skip storing of XMI files */
	#define SAVE_OUTPUT
#endif

/* global err msg */
char *errstr = NULL;

/* compress and decompress 'buffer' using type 'lossy', 
 * save it to 'filename'.xmi and 'filename'.xml
 * and then check consistency with the original XML iff
 * compression was lossless.
 */ 
int testsettings::compdecomp(char *buffer, int buflen, char *filename, int targetsize, 
                             clock_t *t2t, clock_t *t3t, int *xmibuflenp, bool isFirst)
{
	char *xmibuf = NULL;
	char *xmlbuf = NULL;
	int xmibuflen = 0, xmlbuflen = 0;
	char *compr = NULL;
#ifdef SAVE_OUTPUT
	char dumstr[BUFLEN];
#endif
	clock_t t1 = clock();
	int stat = 0, cstat = 0;
	clock_t t2, t3;
	int bytesread = 0, endmarker = 0, len = 0;

   try {
	   /* compress */
	   if (blocksize > 0) {
		   /* read XML data per block */

		   /* init */
		   xmill->InitCompress(&xmibuf, &xmibuflen, &bytesread);
		   do {
			   /* must we signal end-of-data ? */
			   if ((len = min(blocksize, buflen-bytesread)) == buflen-bytesread) {
				   endmarker = XMILL_END_DATA;
			   } else {
				   endmarker = XMILL_END_NONE;
			   }
			   /* compress this block */
			   xmill->CompressBlock(
					   &buffer[bytesread], 
					   len,
					   &endmarker);
		   } while (bytesread < buflen);
		   /* check endmarker */
		   if (endmarker != XMILL_END_DATA) {
			   /* XML file not valid! */
			   //throw new XMillException(XMILL_ERR_PARSE, "There are unclosed XML tags!");
			   stat = XMILL_ERR_PARSE;
			   goto cleanup;
		   }
		   /* end the compression */
		   xmill->EndCompress();
	   } else {
		   /* compress a whole XML file at once */
         xmill->Compress(buffer, buflen, &xmibuf, &xmibuflen, &bytesread);
   	}
   } catch (XMillException *e) {
      stat = e->code;
      errstr = str_save(e->GetErrorMsg());
      delete e;
		t3 = t2 = clock();
		goto cleanup;
	}

	t2 = clock();

#ifdef SAVE_OUTPUT
	/* write to file */
	if (filename) {
		sprintf(dumstr, "%s.%s", filename, XMILL_EXT_XMI);
		FILE *f1 = fopen(dumstr, "wb");
		if (f1) {
			fwrite(xmibuf, 1, xmibuflen, f1);
			fclose(f1);
		}
	}
#endif

   try {
	   /* decompress */
	   if (blocksize > 0) {
		   /* read XMI data per block */

		   /* init */
		   xmill->InitDecompress(&xmlbuf, &xmlbuflen, &bytesread);
		   do {
			   /* decompress this block */
			   xmill->DecompressBlock(
					   &xmibuf[bytesread], 
					   min(blocksize, xmibuflen-bytesread),
					   &endmarker);
		   } while (bytesread < xmibuflen);
		   /* check endmarker */
		   if (endmarker != XMILL_END_DATA) {
			   /* XMI file not valid! */
			   stat = XMILL_ERR_PARSE;
			   goto cleanup;
		   }
		   /* end the decompression */
		   xmill->EndDecompress();
	   } else {
		   /* decompress in one go */
		   xmill->Decompress(xmibuf, xmibuflen, 
			   &xmlbuf, &xmlbuflen, &bytesread);
	   }
   } catch (XMillException *e) {
      stat = e->code;
      errstr = str_save(e->GetErrorMsg());
      delete e;
		t3 = clock();
		goto cleanup;
	}

	/* get timings */
	t3 = clock();
	t3 -= t2;
	t2 -= t1;
   *t2t += t2;
   *t3t += t3;

   *xmibuflenp = xmibuflen;

	/* free XMI data */
	free (xmibuf);

	/* check targetsize */
	if (  targetsize > 0 
		&& targetsize != xmibuflen
		&& stats >= TEST_STATS_ERR) {
		if (!mustfail) {
			printf("Targetsize not matched!\n");
		}
		stat = TEST_ERROR_TARGETSIZE;
	}

#ifdef SAVE_OUTPUT
	/* write output to file */
	if (filename) {
		sprintf(dumstr, "%s.xml", filename);
		FILE *f1 = fopen(dumstr, "wb");
		if (f1) {
			fwrite(xmlbuf, xmlbuflen, 1, f1);
			fclose(f1);
		}
	}
#endif

	cstat = checkintegrity(buffer, buflen, xmlbuf, xmlbuflen);
	if (stat == 0) {
		stat = cstat;
	}

cleanup:
	/* print stats */
	switch (bzip) {
		case XMILL_GPC_BZIP:
			compr = "bzip2";
			break;
		case XMILL_GPC_GZIP:
			compr = "gzip";
			break;
		case XMILL_GPC_NONE:
			compr = "nozip";
			break;
		case XMILL_GPC_PPMDI:
			compr = "ppmdi";
			break;
		default:
			compr = "unknown";
			break;
	}
	if (  (stats >= TEST_STATS_BPCTIME && isFirst)
		|| (stat != 0 && stats >= TEST_STATS_ERR && !mustfail)) {
      /* print stats */
		printf ("stats:\txml: %ld Bytes, xmi: %ld Bytes, target: %ld Bytes\n"
               "\tratio: %6.2lf%%, bpc: %4.2lf\n"
               "\ttype: %s, compressor: %s, idx: %ld, paths: %s\n",
			buflen, xmibuflen, targetsize,
			(double)xmibuflen * 100 / (double)buflen, 
         (double)xmibuflen * 8 / (double)buflen, 
			lossy ? "lossy" : "lossless",
			compr, gpcidx,
			exprs ? "yes" : "no");
	}

	/* free decompressed XML data */
	tryfree (xmlbuf);

	return stat;
}

int testsettings::checkintegrity(char *buffer, int buflen, char *xmlbuf, int xmlbuflen)
{
	int i = 0, j = 0, stat = 0;
	bool different = false;

	/* check that input==output */
	if (ignorewhite || lossy) {
		/* text should be the same except whitespace */
		while(i < buflen && j < xmlbuflen) {
			while (isspace(buffer[i])) i++;
			while (isspace(xmlbuf[j])) j++;
			if (buffer[i] != xmlbuf[j]) {
				different = true;
				break;
			}
			i++;
			j++;
		}
		if (different) {
			if (stats >= TEST_STATS_ERR && !mustfail) {
				printf("uncompressed data differs in pos %ld/%ld! ('%c' != '%c')\n", 
					i, j, buffer[i], xmlbuf[j]);
			}
			stat = TEST_ERROR_NOIGNMATCH;
		}
	} else {
		/* text should be exactly the same */
		if (  buflen != xmlbuflen
			&& stats >= TEST_STATS_ERR
			&& !mustfail) {
			printf("uncompressed data size differs: %ld != %ld!\n", buflen, xmlbuflen);
		}
		int thebuflen = min(xmlbuflen, buflen);
		if (memcmp(buffer, xmlbuf, thebuflen)) {
			while(i < thebuflen && buffer[i]==xmlbuf[i]) i++;
			stat = TEST_ERROR_NOEXACTMATCH;
			if (stats >= TEST_STATS_ERR && !mustfail) {
				printf("uncompressed data differs in pos %ld! ('%c' != '%c')\n", 
					i, buffer[i], xmlbuf[i]);
			}
		}
	}

cleanup:
	return stat;
}

/* decompress the xmifile and check consistency with xmlfile */
int testsettings::rundecomp(char *xmlfile, char *xmifile, int times)
{
	int stat = 0, i = 0;
	char *xmlbuf = NULL;
	char *xmibuf = NULL;
	char *xmlbuf2 = NULL;
	int xmlbuflen = 0;
	int xmibuflen = 0;
	int xmlbuflen2 = 0;
	clock_t t1, t2;
	int bytesread = 0;

	/* read XML and XMI file */
	if ((stat = readfile(xmlfile, &xmlbuf, &xmlbuflen)) != 0) {
		goto cleanup;
	}
	if ((stat = readfile(xmifile, &xmibuf, &xmibuflen)) != 0) {
		goto cleanup;
	}

	/* decompress it! */
	t1 = clock();
	for (i=0; i<times; i++) {
      try {
		   xmill->Decompress(xmibuf, 
				   blocksize > 0 ? min(blocksize, xmibuflen) : xmibuflen, 
				   &xmlbuf2, &xmlbuflen2,
				   &bytesread);
      } catch (XMillException *e) {
         stat = e->code;
         errstr = e->GetErrorMsg();
         delete e;
         goto cleanup;
	   }
		/* check that input==output */
		stat = checkintegrity(xmlbuf, xmlbuflen, xmlbuf2, xmlbuflen2);
   }

   t2 = clock() - t1;
	if (stats >= TEST_STATS_TIMING
	   || (stat != 0 && stats >= TEST_STATS_ERR && !mustfail)) {
		printf("Done uncompressing %s %ld times.\n", xmifile, times);
	}
	if (stat < 0) {
		stat = 0;
	}

	if (stats >= TEST_STATS_TIMING && !mustfail) {
   	/* print stats */
		printf ("xml: %ld Bytes, xmi: %ld Bytes\n", xmlbuflen, xmibuflen); 
   	/* print timings */
		printf ("total clocks: %ld\naverage throughput: %lf KiB XML/sec, %lf KiB XMI/sec\n",
         t2, 
			(double)xmlbuflen * (double)CLOCKS_PER_SEC * (double)times / (1024.0 * (double)t2),
			(double)xmibuflen * (double)CLOCKS_PER_SEC * (double)times / (1024.0 * (double)t2));
	}

cleanup:
	/* free some data */
	tryfree (xmlbuf);
	tryfree (xmibuf);
	tryfree (xmlbuf2);

	return stat;
}

/* compress XML file 'filename' 'times' times, using type 'lossy' */
int testsettings::runcompdecomp(char *filename, int targetsize)
{
	int i = 0, numexprs = 0;
	char *buffer = NULL;
	int buflen = 0;
	int stat = 0, thestat = 0;
	char **pathexprs = NULL;
	bool exprsnow = exprs;
   clock_t t2 = 0, t3 = 0;
   int xmibuflen;

	/* read the file to memory */
	if ((stat = readfile(filename, &buffer, &buflen)) != 0) {
		goto cleanup;
	}

	if (exprsnow) {
		exprsnow = false;
		pathexprs = (char**)malloc(MAXEXPRS*sizeof(char*));
		/* read .xmill file to memory */
		if ((stat = XMill::readExprsFromFile(filename, pathexprs, MAXEXPRS, &numexprs)) == 0) {
			/* set these path exprs */
         try {
			   xmill->Init (XMILL_OUT_STRING_OR_FILE, XMILL_INIT_USERINIT, lossy, xmill->GetCompressionMode(), pathexprs, 
                         false, 'n', true, -1, 1, gpcidx);
         } catch (XMillException *e) {
            stat = e->code;
            errstr = e->GetErrorMsg();
            delete e;
            goto cleanup;
	      }
			exprsnow = true;
		}
		stat = 0;
	}

	for (i=0; i<times; i++) {
		if (stats >= TEST_STATS_ALL) {
			printf("(de)compressing %s run %ld/%ld\n", filename, i+1, times);
      }
		/* compress and decompress the file, then check consistency */ 
		if ((stat = compdecomp(buffer, buflen, filename, targetsize, &t2, &t3, &xmibuflen, i==0)) != 0) {
			goto prematureexit;
		}
	}

prematureexit:
	/* cleanup pathexprs */
	if (exprsnow) {
      try {
		   xmill->Init (XMILL_OUT_STRING_OR_FILE, XMILL_INIT_USERINIT, lossy, xmill->GetCompressionMode(), NULL, 
                      false, 'n', true, -1, 1, gpcidx);
      } catch (XMillException *e) {
         stat = e->code;
         errstr = e->GetErrorMsg();
         delete e;
         goto cleanup;
	   }
	}

	if (stat != 0) {
		if (stats >= TEST_STATS_ERR && !mustfail) {
			printf("Error while compressing & decompressing %s in run %ld/%ld.\n", 
				filename, i+1, times);
			if (times > i+1) {
				printf("Skipped next %ld run(s).\n", times-i-1);
			}
			printf("\n");
		}
	} else if (stats >= TEST_STATS_TIMING && !mustfail) {
      /* print timings */
      printf ("timing for %s:\n"
   			   "\ttotal compress clocks: %ld\n\taverage throughput: %6.1lf KiB XML/s, %6.1lf KiB XMI/s\n"
					"\ttotal decompress clocks: %ld\n\taverage throughput: %6.1lf KiB XML/s, %6.1lf KiB XMI/s\n",
         filename,
			t2, (double)buflen * (double)CLOCKS_PER_SEC * (double)times / (1024.0 * (double)t2),
			(double)xmibuflen * (double)CLOCKS_PER_SEC * (double)times / (1024.0 * (double)t2),
			t3, (double)buflen * (double)CLOCKS_PER_SEC * (double)times / (1024.0 * (double)t3),
			(double)xmibuflen * (double)CLOCKS_PER_SEC * (double)times / (1024.0 * (double)t3));
		if (stats >= TEST_STATS_ALL) {
		   printf("Done compressing & decompressing %s %ld times.\n", 
			   filename, times);
      }
	}
	
cleanup:
	/* free XML data */
	tryfree(buffer);
	if (exprsnow) {
		i = 0;
		while (pathexprs && pathexprs[i]) {
			tryfree(pathexprs[i]);
			i++;
		}
	}
	tryfree(pathexprs);

	return stat;
}

testsettings::testsettings() 
{
   xmill = new XMill();
	times = 1;
	lossy = true;
	bzip = XMILL_GPC_BZIP;
   gpcidx = XMILL_GZIP_IDX;
	mode = TEST_COMPDECOMP;
	exprs = false;
	stats = TEST_STATS_WARN;
	ignorewhite = false;
	blocksize = 0;
	mustfail = false;
}

testsettings::~testsettings() 
{
	delete xmill;
}

void testsettings::setstats(char s)
{
	stats = s;
}

int testsettings::parsesetting(char *s, char *s1, bool *usedboth)
{
	if (usedboth) {
		*usedboth = false;
	}

		    if (!stricmp(s, "gzip")) {
		bzip = XMILL_GPC_GZIP;	
      gpcidx = XMILL_GZIP_IDX;
	} else if (!stricmp(s, "nozip")) {
		bzip = XMILL_GPC_NONE;	
      gpcidx = -1;
	} else if (!stricmp(s, "bzip2")) {
		bzip = XMILL_GPC_BZIP;	
      gpcidx = -1;
	} else if (!stricmp(s, "ppmdi")) {
		bzip = XMILL_GPC_PPMDI;	
      gpcidx = XMILL_PPMDI_IDX;
	} else if (!stricmp(s, "lossless")) {
		lossy = false;
	} else if (!stricmp(s, "lossy")) {
		lossy = true;
	} else if (!stricmp(s, "compdecomp")) {
		mode = TEST_COMPDECOMP;
	} else if (!stricmp(s, "decomp")) {
		mode = TEST_DECOMP;
	} else if (!stricmp(s, "targetsize")) {
		mode = TEST_TARGETSIZE;
	} else if (!stricmp(s, "exprs")) {
		exprs = true;
	} else if (!stricmp(s, "plain")) {
		exprs = false;
	} else if (!stricmp(s, "gpcidx") && s1 && usedboth) {
		gpcidx = atoi(s1);
		*usedboth = true;
	} else if (!stricmp(s, "verbose") && s1 && usedboth) {
		xmill->SetVerbose(atoi(s1));
		*usedboth = true;
	} else if (!stricmp(s, "stats") && s1 && usedboth) {
		setstats(atoi(s1));
		*usedboth = true;
	} else if (!stricmp(s, "silent")) {
		xmill->SetVerbose(XMILL_VERBOSE_SILENT);
	} else if (!stricmp(s, "noinc")) {
		xmill->SetNoIncrease();
	} else if (!stricmp(s, "allowinc")) {
		xmill->SetNoIncrease(false);
	} else if (!stricmp(s, "blocks") && s1 && usedboth) {
		blocksize = atoi(s1);
		*usedboth = true;
	} else if (!stricmp(s, "ignorewhite")) {
		ignorewhite = true;
	} else if (!stricmp(s, "exact")) {
		ignorewhite = false;
	} else if (!stricmp(s, "mustfail")) {
		mustfail = true;
	} else if (!stricmp(s, "mustnotfail")) {
		mustfail = false;
	} else if (!stricmp(s, "quit")) {
		return 1;
	}
	return 0;
}

int testsettings::run(char **files, int numfiles)
{
  	int i = 0, stat = 0;
	bool mustreinit = true;
	int lasterrstat = 0;

	switch (mode) {
		case TEST_COMPDECOMP:
	 		for (i=0; i<numfiles; i++) {
				if (mustreinit) {
               try {
					   xmill->Init (XMILL_OUT_STRING_OR_FILE, XMILL_INIT_USERINIT, lossy, bzip, NULL, false, 
                               'n', true, -1, 1, gpcidx);
               } catch (XMillException *e) {
                  stat = e->code;
                  errstr = e->GetErrorMsg();
                  delete e;
						if (stats >= TEST_STATS_ERR && !mustfail) {
							printf("error initializing: code %ld, text '%s'\n\n",
								stat, errstr ? errstr : "");
						}
						goto cleanup;
					}
					mustreinit = false;
				}
				/* compress and decompress all files a few times */
				if ((stat = runcompdecomp(files[i])) != 0) {
					if (stats >= TEST_STATS_ERR && !mustfail) {
						printf("error (de)compressing %s: code %ld, text '%s'\n\n", 
							files[i], stat, stat > 0 && errstr ? errstr : "");
					}
					/* we *must* reinitialize after a failure */
					mustreinit = true;
					lasterrstat = stat;
				} else if (stats >= TEST_STATS_TIMING) {
					printf("\n");
				}
			}
			break;

		case TEST_DECOMP:
			for (i=0; i<numfiles-1; i+=2) {
				if (mustreinit) {
               try {
					   xmill->Init (XMILL_OUT_STRING_OR_FILE, XMILL_INIT_USERINIT, lossy, bzip, NULL, false
                              , 'n', true, -1, 1, gpcidx);
               } catch (XMillException *e) {
                  stat = e->code;
                  errstr = e->GetErrorMsg();
                  delete e;
						if (stats >= TEST_STATS_ERR && !mustfail) {
							printf("error initializing: code %ld, text '%s'\n\n", 
								stat, errstr ? errstr : "");
						}
						goto cleanup;
					}
					mustreinit = false;
				}
				/* decompress all files a few times and check consistency */
				if ((stat = rundecomp(files[i], files[i+1], times)) != 0) {
				   if (stats >= TEST_STATS_ERR && !mustfail) {
						printf("error in %s / %s: code %ld, text '%s'\n\n", 
						files[i], files[i+1], stat, stat > 0 && errstr ? errstr : "");
					}
					/* we *must* reinitialize after a failure */
					mustreinit = true;
					lasterrstat = stat;
				} else if (stats >= TEST_STATS_TIMING) {
					printf("\n");
				}
			}
			break;

		case TEST_TARGETSIZE:
	 		for (i=1; i<numfiles; i++) {
				if (mustreinit) {
               try {
					   xmill->Init (XMILL_OUT_STRING_OR_FILE, XMILL_INIT_USERINIT, lossy, bzip, NULL, false
                              , 'n', true, -1, 1, gpcidx);
               } catch (XMillException *e) {
                  stat = e->code;
                  errstr = e->GetErrorMsg();
                  delete e;
						if (stats >= TEST_STATS_ERR && !mustfail) {
							printf("error initializing: code %ld, text '%s'\n\n",
								stat, errstr ? errstr : "");
						}
						goto cleanup;
					}
					mustreinit = false;
				}
				/* compress and decompress all files a few times */
				if ((stat = runcompdecomp(files[i], atoi(files[0]))) != 0) {
					if (stats >= TEST_STATS_ERR && !mustfail) {
						printf("error (de)compressing %s: code %ld, text '%s'\n\n", 
							files[i], stat, stat > 0 && errstr ? errstr : "");
					}
					/* we *must* reinitialize after a failure */
					mustreinit = true;
					lasterrstat = stat;
				} else if (stats >= TEST_STATS_TIMING) {
					printf("\n");
				}
			}
			break;

		default:
			stat = TEST_ERROR_MODE;
			break;
	}

cleanup:
	if (stat == 0 && lasterrstat != 0) {
		stat = lasterrstat;
	}

	return stat;
}

static char *trim(char *s)
{
	int i=0, j=strlen(s)-1, k=0;
	while(s[i] && isspace(s[i])) i++;
	while(j>=i && isspace(s[j])) j--;
	while (i<=j) {
		s[k++] = s[i++];
	}
	s[k] = '\0';
	return s;
}

int testsettings::run(FILE *script)
{
	char line[STRLEN+1], *str = NULL, *str1 = NULL;
	int numfiles = 0, stat = 0;
	char *files[MAXFILES];
	bool usedboth;
	bool founderrors = false;

	while (!feof(script)) {
		/* get a line from the script & trim whitespaces */
		fgets(line, STRLEN, script);
		trim(line);
		if (strlen(line) > 0) {
			/* take an action based on the first character */
			switch (line[0]) {
				case '*':
					/* setting */
					str = strtok(&line[1], " ");
					do {
						/* parse all settings */
						str1 = strtok(NULL, " ");
						if (parsesetting(str, str1, &usedboth) == 1) {
							/* 'quit' setting */
							goto cleanup;
						}
						if (usedboth) {
							/* skip setting argument */
							str = strtok(NULL, " ");
						} else {
							str = str1;
						}
					} while (str);
					break;

				case '#':
					/* comment */
					break;

				default:
					/* command */
					str = strtok(line, " ");
					times = atoi(str);
					numfiles = -1;
					while (files[++numfiles] = strtok(NULL, " "));
					if ((stat = run(files, numfiles)) != 0) {
						/* ignore error, continue */
						if (!mustfail) {
							founderrors = true;
						}
						stat = 0;
					} else if (mustfail) {
						printf("Previous test should fail but succeeded!\n");
						founderrors = true;
					}
			}
		}
	}

cleanup:
	if (stat == 0 && founderrors) {
		stat = TEST_ERROR_FOUND;
	}

	return stat;
}
