/*
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:

		In-memory API to the XMill (de)compression routines

History:

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

*/

#include "stdafx.h"

#ifdef _DEBUG

void *checkAddress = NULL;
int mallocNum = 0;

void setCheckAddress(void *c)
{
	checkAddress = c;
}

void checkDelete (void *c)
{
	if (checkAddress == c) {
		printf("Found delete for address %08x\n", c);
	}
}

void checkFree (void *c)
{
	if (checkAddress == c) {
		printf("Found free() for address %08x\n", c);
	}
}

void* checkedMalloc (unsigned int size)
{
	mallocNum++;
	void *c = malloc(size);
	if (c == checkAddress) {
		printf("Found malloc() for address %08x\n", c);
	}
	return c;
}

#endif

XMill::XMill(int outtype, bool lossy, char usebzip, char **pathexpr, bool usedos,
				 char igntype, bool ignore, int indenttype, 
				 int indentcount, int zlibidx)
{
	FirstInit();
	Init(outtype, XMILL_INIT_INIT, lossy, usebzip, pathexpr, usedos, igntype, ignore,
		  indenttype, indentcount, zlibidx);
}

XMill::XMill(char *s)
{
	FirstInit();
	session->InitMore();
}

void XMill::FirstInit()
{
#ifdef WIN32
#ifndef __MINGW32__
	CoInitialize(NULL);
#endif
#endif
	saxclient = NULL;
	session = new Session();
	settings = new Settings(session);
	session->SetSettings(settings);
#ifdef NOTHREAD
	::session = session;
#endif

}

XMill::~XMill()
{
	trydel (session);
	trydel (settings);
}

void XMill::SetVerbose(char verb)
{
	settings->verbose = verb;
}

void XMill::SetNoIncrease(bool inc)
{
	settings->noincrease = (char)inc;
}

/* 0 = GZIP
   1 = BZIP2
	todo: -1 = none
 */
char XMill::GetCompressionMode()
{
	return settings->use_bzip;
}

/* 
 * pathexpr:
 *  NULL-terminated array of ASCIIZ path expressions
 * indenttype:
 *  XMLINDENT_NONE
 *  XMLINDENT_TABS
 *  XMLINDENT_SPACES (needs count) 
 *  XMLINDENT_WRAP (needs count) 
 */
void XMill::Init(int outtype, char type,
				  bool lossy, 
				  char usebzip,
				  char **pathexpr, 
				  bool usedos, 
				  char igntype, bool ignore, 
				  int indenttype, int indentcount,
				  int zlibidx,
				  bool copyxml)
{
	int i = 0;

	/* delete some instances */
	session->DeInit();

	if (type != XMILL_INIT_REINIT) {
		/* only (re)set settings if this is not a reinit request */
		settings->Init(outtype, lossy, usebzip, usedos, igntype, ignore, zlibidx,
							copyxml);
	}
	session->Init(type, indenttype, indentcount, pathexpr);
}

void XMill::Compress(char *inbuf, int inlen, char **outbuf, int *outlen, int *bytesread) 
{
	int endmarker = XMILL_END_DATA;

	InitCompress(outbuf, outlen, bytesread);
	CompressBlock(inbuf, inlen == 0 ? strlen(inbuf) : inlen, &endmarker);
	if (endmarker != XMILL_END_DATA) {
		/* XML file not valid! */
		throw new XMillException(XMILL_ERR_PARSE, "There are unclosed XML tags!");
   }
	EndCompress();
}

void XMill::InitCompress(char **outbuf, int *outlen, int *bytesread)
{
	if (!bytesread) {
		throw new XMillException(XMILL_ERR_NULL_POINTER, "Please supply a bytesread pointer");
	}

	/* re-init memory cutoff; decompress also uses it, but for another purpose */
	session->memory_cutoff = XMILL_MEMORY_CUTOFF;

	/* init buffers */
	session->memoutput->myCreateFile (outbuf, outlen);

	/* set ptr to report # Bytes */
	session->parse->setBytesReadPtr(bytesread);
}

void XMill::CompressBlock(char *inbuf, int inlen, int *endmarker)
{
	if (!endmarker) {
		throw new XMillException(XMILL_ERR_NULL_POINTER, "Please supply an endmarker pointer");
	}

	if (inlen == 0) {
		return;
	}

	/* init buffers */
	session->parse->setData(inbuf, inlen);

	/* set maximum compressed size */
	/* Note: this might stop the compression prematurely when the compressed size
		*temporarily* exceeds the maxlength, but would not exceed it at the end. */
	session->memoutput->setMaxLength (inlen + *(session->parse->bytesreadptr));

	/* signal last block */
	if (*endmarker == XMILL_END_DATA) {
		session->parse->SetLastBlock();
	}

	/* compress the in-mem XML file */
	*endmarker = CompressIt(session->parse, session->memoutput);
}

void XMill::EndCompress()
{
	/* close the output to flush all data */
	session->memoutput->CloseFile();

	/* print info */
	if(settings->verbose >= XMILL_VERBOSE_STATS) {
		session->PrintSpecialContainerSizeSum();
#ifdef PROFILE
		session->curpath->PrintProfile();
#endif
	}

	/* reset some stuph */
	session->Init(XMILL_INIT_REINIT);
}

void XMill::InitDecompress(char **outbuf, int *outlen, int *bytesread)
{
	if (!bytesread) {
		throw new XMillException(XMILL_ERR_NULL_POINTER, "Please supply a bytesread pointer");
	}

	saved_use_bzip = settings->use_bzip;
	decompoutbuf = outbuf;
	decompoutlen = outlen;

	if (session->memoutput2) {
		session->memoutput2->myCreateFile(outbuf, outlen);
	} else {
		/* MSXML */
		if (!session->xmloutput->myCreateFile(NULL)) {
			throw new XMillException(XMILL_ERR_NULL_POINTER, "could not create MSXML instance");
		}
	}

	/* set ptr to report # Bytes */
	session->meminput->setBytesReadPtr(bytesread);
}

void XMill::DecompressBlock(char *inbuf, int inlen, int *endmarker)
{
	session->meminput->setData(inbuf, inlen);

	/* decompress the in-mem XMI file */
	UncompressIt(session->meminput, session->xmloutput);
}

void XMill::EndDecompress()
{
	/* close to Flush() all data */
	session->xmloutput->CloseFile();

	/* make sure the XML is ASCIIZ */
	(*decompoutbuf)[*decompoutlen] = '\0';

	settings->use_bzip = saved_use_bzip;
}

void XMill::Decompress(char *inbuf, int buflen, char **outbuf, int *outlen, int *bytesread)
{
	int endmarker = 0;

	InitDecompress(outbuf, outlen, bytesread);
	DecompressBlock(inbuf, buflen == 0 ? strlen(inbuf) : buflen, &endmarker);
   EndDecompress();
}

/* read a file to memory */
int readfile(char *filename, char **buf, int *buflength)
{
	FILE *f = NULL;
	char *buffer = NULL;
	int len = 0;
	int stat = 0;

	if (!filename) {
		f = stdin;
	} else {
		f = fopen(filename, "rb");
	}

	if (!f) {
		stat = XMILL_ERR_FILENOTFOUND;
		goto cleanup;
	}

   /* get file length */
   fseek(f, 0, SEEK_END);
   len = ftell(f);
   buffer = (char*)mymalloc(len+1);
   fseek(f, 0, SEEK_SET);

   /* read file */
	fread(buffer, 1, len, f);
	buffer[len] = '\0';

	if (buf)
		*buf = buffer;
	if (buflength)
		*buflength = len;

cleanup:
	return stat;
}

/* standard Unix escaping with \ */
static char* str_unescape(char *s)
{
	int i = 0, j = 0;
	char c;
	bool insidestring = false;

	while (s && (c=s[i++])) {
		switch (c) {
			case '"':
				insidestring = !insidestring;
				break;
			case '\\':
				if (!insidestring)
					break;
				/* found an escape */
				switch ((c=s[i++])) {
					case 't':
						/* tab */
						c = '\t';
						break;
					case 'n':
						/* newline */
						c = '\n';
						break;
					case 'r':
						/* carriage return */
						c = '\r';
						break;
					default:
						/* other; just copy char itself */
						break;
				}
				break;
			default:
				break;
		}
		s[j++] = c;
	}
	s[j] = '\0';
	return s;
}

static char* findEndOfExpr(char *expr)
{
	int numbrackets = 0;
	bool insidestring = false;
	char c;

	while (expr && (c=expr[0])) {
		switch (c) {
			case '(':
				if (!insidestring)
					numbrackets++;
				break;
			case ')':
				if (!insidestring) {
					if (--numbrackets < 0)
						return NULL;
				}
				break;
			case '"':
				insidestring = !insidestring;
				break;
			case '\\':
				if (insidestring) {
					/* also skip the escaping \ of an escaped character, so that we
					   don't accidentally recognize \" as a string terminator */
					expr++;
				}
				break;
			case '\r':
			case '\n':
				if (!insidestring && numbrackets == 0) {
					/* end of path expr reached */
					return expr;
				}
				break;
			default:
				break;
		}
		/* next character */
		expr++;
	}

	/* end of string reached, check if there are unclosed parameters or strings */
	if (insidestring || numbrackets > 0) {
		return NULL;
	}

	return expr;
}

int XMill::readExprsFromFile(char *filename, char **pathexprs, int maxexprs, int *numexprs)
{
	int i = 0, stat = 0, j = 0;
	char xmillfile[STRLEN+1];
	char *ops = NULL, *opsptr = NULL, *ptr = NULL;
	int opslen = 0;

	/* check parameters */
	if (!filename || !filename[0] || !pathexprs || !numexprs || maxexprs < 2) {
		stat = XMILL_ERR_ARGUMENTS;
		goto cleanup;
	}

	/* get .xmill file name */
	strncpy(xmillfile, filename, STRLEN-sizeof(XMILL_EXT_XMILL));
	i = strlen(xmillfile)-1;
	while (i>0 && xmillfile[i--] != '.');
	if (i == 0) {
		i = strlen(xmillfile)-3;
	}
	strcpy(&xmillfile[i+2], XMILL_EXT_XMILL);

	/* initialize expr list */
	pathexprs[0] = NULL;
	*numexprs = 0;

	/* read .xmill file to memory */
	if ((stat = readfile(xmillfile, &ops, &opslen)) == 0) {
		/* parse the file */
		i = 0;
		j = 0;
		while(i<opslen-(sizeof(XMILL_PATHEXPR_OPT)-1) && j<maxexprs-1) {
			/* skip whitespace */
			while (i<opslen-(sizeof(XMILL_PATHEXPR_OPT)-1) && isspace(ops[i])) i++;
			/* is this a path expression? */
			if (!strnicmp(&ops[i], XMILL_PATHEXPR_OPT, sizeof(XMILL_PATHEXPR_OPT)-1)) {
				/* find the end of this expr */
				if (!(opsptr = findEndOfExpr(&ops[i+(sizeof(XMILL_PATHEXPR_OPT)-1)])) 
					|| opsptr == &ops[i+(sizeof(XMILL_PATHEXPR_OPT)-1)]) {
					stat = XMILL_ERR_XMILL_SYNTAX;
					goto cleanup;
				}
				/* NULL-terminate the path expression and store it */
				ptr = &ops[i+(sizeof(XMILL_PATHEXPR_OPT)-1)];
				i = opsptr - ops + 1;
				ops[i-1] = '\0';
				str_unescape(ptr);
				pathexprs[j++] = str_save(ptr);
			} else {
				/* skip this line */
				while (i < opslen-(sizeof(XMILL_PATHEXPR_OPT)-1) 
						&& ops[i] != '\n' 
						&& ops[i]!='\r') 
					i++;
			}
		}
		/* NULL-terminate the list */
		pathexprs[j] = NULL;
	}

	/* return # path exprs that were read */
	*numexprs = j;

cleanup:
	tryfree(ops);

	return stat;
}
