/******************************************************************************
 *  zstr.cpp   - code for class 'zStr'- a module that reads compressed text
 *		files and provides lookup and parsing functions based on
 *		class StrKey
 */

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#ifndef __GNUC__
#include <io.h>
#else
#include <unistd.h>
#endif

#include <stdlib.h>
#include <utilfuns.h>
#include <zstr.h>
#include <swcomprs.h>

#include <sysdata.h>
#include <entriesblk.h>

SWORD_NAMESPACE_START

/******************************************************************************
 * zStr Statics
 */

int zStr::instance = 0;
const int zStr::IDXENTRYSIZE = 8;
const int zStr::ZDXENTRYSIZE = 8;


/******************************************************************************
 * zStr Constructor - Initializes data for instance of zStr
 *
 * ENT:	ipath - path of the directory where data and index files are located.
 */

zStr::zStr(const char *ipath, int fileMode, long blockCount, SWCompress *icomp) {
	char buf[127];

	nl = '\n';
	lastoff = -1;
	path = 0;
	stdstr(&path, ipath);

	compressor = (icomp) ? icomp : new SWCompress();
	this->blockCount = blockCount;
#ifndef O_BINARY		// O_BINARY is needed in Borland C++ 4.53
#define O_BINARY 0		// If it hasn't been defined than we probably
#endif				// don't need it.

	if (fileMode == -1) { // try read/write if possible
		fileMode = O_RDWR;
	}
		
	sprintf(buf, "%s.idx", path);
	idxfd = FileMgr::systemFileMgr.open(buf, fileMode|O_BINARY, true);

	sprintf(buf, "%s.dat", path);
	datfd = FileMgr::systemFileMgr.open(buf, fileMode|O_BINARY, true);

	sprintf(buf, "%s.zdx", path);
	zdxfd = FileMgr::systemFileMgr.open(buf, fileMode|O_BINARY, true);

	sprintf(buf, "%s.zdt", path);
	zdtfd = FileMgr::systemFileMgr.open(buf, fileMode|O_BINARY, true);

	if (datfd <= 0) {
		sprintf(buf, "Error: %d", errno);
		perror(buf);
	}

	cacheBlock = 0;
	cacheBlockIndex = -1;
	cacheDirty = false;

	instance++;
}


/******************************************************************************
 * zStr Destructor - Cleans up instance of zStr
 */

zStr::~zStr() {

	flushCache();

	if (path)
		delete [] path;

	--instance;

	FileMgr::systemFileMgr.close(idxfd);
	FileMgr::systemFileMgr.close(datfd);
	FileMgr::systemFileMgr.close(zdxfd);
	FileMgr::systemFileMgr.close(zdtfd);


	if (compressor)
		delete compressor;

}


/******************************************************************************
 * zStr::getidxbufdat	- Gets the index string at the given dat offset
 *				NOTE: buf is calloc'd, or if not null, realloc'd and must
 *					be free'd by calling function
 *
 * ENT:	ioffset	- offset in dat file to lookup
 *		buf		- address of pointer to allocate for storage of string
 */

void zStr::getKeyFromDatOffset(long ioffset, char **buf) {
	int size;
	char ch;
	if (datfd > 0) {
		lseek(datfd->getFd(), ioffset, SEEK_SET);
		for (size = 0; read(datfd->getFd(), &ch, 1) == 1; size++) {
			if ((ch == '\\') || (ch == 10) || (ch == 13))
				break;
		}
		*buf = (*buf) ? (char *)realloc(*buf, size*2 + 1) : (char *)malloc(size*2 + 1);
		if (size) {
			lseek(datfd->getFd(), ioffset, SEEK_SET);
			read(datfd->getFd(), *buf, size);
		}
		(*buf)[size] = 0;
		toupperstr_utf8(*buf, size*2);
	}
	else {
		*buf = (*buf) ? (char *)realloc(*buf, 1) : (char *)malloc(1);
		**buf = 0;
	}
}


/******************************************************************************
 * zStr::getidxbuf	- Gets the index string at the given idx offset
 *						NOTE: buf is calloc'd, or if not null, realloc'd
 *							and must be freed by calling function
 *
 * ENT:	ioffset	- offset in idx file to lookup
 *		buf		- address of pointer to allocate for storage of string
 */

void zStr::getKeyFromIdxOffset(long ioffset, char **buf) {
	__u32 offset;
	
	if (idxfd > 0) {
		lseek(idxfd->getFd(), ioffset, SEEK_SET);
		read(idxfd->getFd(), &offset, sizeof(__u32));
		offset = swordtoarch32(offset);
		getKeyFromDatOffset(offset, buf);
	}
}


/******************************************************************************
 * zStr::findoffset	- Finds the offset of the key string from the indexes
 *
 * ENT:	key		- key string to lookup
 *		offset	- address to store the starting offset
 *		size		- address to store the size of the entry
 *		away		- number of entries before of after to jump
 *					(default = 0)
 *
 * RET: error status
 */

signed char zStr::findKeyIndex(const char *ikey, long *idxoff, long away) {
	char *trybuf = 0, *key = 0, quitflag = 0;
	signed char retval = 0;
	__s32 headoff, tailoff, tryoff = 0, maxoff = 0;
	__u32 start, size;

	if (idxfd->getFd() >= 0) {
		tailoff = maxoff = lseek(idxfd->getFd(), 0, SEEK_END) - IDXENTRYSIZE;
		if (*ikey) {
			headoff = 0;
			stdstr(&key, ikey, 3);
			toupperstr_utf8(key, strlen(key)*3);

			while (headoff < tailoff) {
				tryoff = (lastoff == -1) ? headoff + (((((tailoff / IDXENTRYSIZE) - (headoff / IDXENTRYSIZE))) / 2) * IDXENTRYSIZE) : lastoff;
				lastoff = -1;

				getKeyFromIdxOffset(tryoff, &trybuf);

				if (!*trybuf && tryoff) {		// In case of extra entry at end of idx (not first entry)
					tryoff += (tryoff > (maxoff / 2))?-IDXENTRYSIZE:IDXENTRYSIZE;
					retval = -1;
					break;
				}

				int diff = strcmp(key, trybuf);
				if (!diff)
					break;

				if (diff < 0)
					tailoff = (tryoff == headoff) ? headoff : tryoff;
				else headoff = tryoff;
				if (tailoff == headoff + IDXENTRYSIZE) {
					if (quitflag++)
						headoff = tailoff;
				}
			}
			if (headoff >= tailoff)
				tryoff = headoff;
			if (trybuf)
				free(trybuf);
			delete [] key;
		}
		else	{ tryoff = 0; }

		lseek(idxfd->getFd(), tryoff, SEEK_SET);

		start = size = 0;
		retval = (read(idxfd->getFd(), &start, sizeof(__u32))==sizeof(__u32)) ? retval : -1;
		retval = (read(idxfd->getFd(), &size, sizeof(__u32))==sizeof(__u32)) ? retval : -1;
		start = swordtoarch32(start);
		size  = swordtoarch32(size);

		if (idxoff)
			*idxoff = tryoff;

		while (away) {
			__u32 laststart = start;
			__u32 lastsize = size;
			__s32 lasttry = tryoff;
			tryoff += (away > 0) ? IDXENTRYSIZE : -IDXENTRYSIZE;

			bool bad = false;
			if (((long)(tryoff + (away*IDXENTRYSIZE)) < -IDXENTRYSIZE) || (tryoff + (away*IDXENTRYSIZE) > (maxoff+IDXENTRYSIZE)))
				bad = true;
			else	if (lseek(idxfd->getFd(), tryoff, SEEK_SET) < 0)
				bad = true;
			if (bad) {
				retval = -1;
				start = laststart;
				size = lastsize;
				tryoff = lasttry;
				if (idxoff)
					*idxoff = tryoff;
				break;
			}
			read(idxfd->getFd(), &start, sizeof(__u32));
			read(idxfd->getFd(), &size, sizeof(__u32));
			start = swordtoarch32(start);
			size  = swordtoarch32(size);

			if (idxoff)
				*idxoff = tryoff;


			if (((laststart != start) || (lastsize != size)) && (start >= 0) && (size)) 
				away += (away < 0) ? 1 : -1;
		}
	
		lastoff = tryoff;
	}
	else {
		if (idxoff)
			*idxoff = 0;
		retval = -1;
	}
	return retval;
}


/******************************************************************************
 * zStr::preptext	- Prepares the text before returning it to external
 *					objects
 *
 * ENT:	buf	- buffer where text is stored and where to store the prep'd
 *				text.
 */

void zStr::prepText(SWBuf &buf) {
	unsigned int to, from; 
	char space = 0, cr = 0, realdata = 0, nlcnt = 0;
	char *rawBuf = buf.getRawData();
	for (to = from = 0; rawBuf[from]; from++) {
		switch (rawBuf[from]) {
		case 10:
			if (!realdata)
				continue;
			space = (cr) ? 0 : 1;
			cr = 0;
			nlcnt++;
			if (nlcnt > 1) {
//				*to++ = nl;
				rawBuf[to++] = 10;
//				*to++ = nl[1];
//				nlcnt = 0;
			}
			continue;
		case 13:
			if (!realdata)
				continue;
//			*to++ = nl[0];
			rawBuf[to++] = 10;
			space = 0;
			cr = 1;
			continue;
		}
		realdata = 1;
		nlcnt = 0;
		if (space) {
			space = 0;
			if (rawBuf[from] != ' ') {
				rawBuf[to++] = ' ';
				from--;
				continue;
			}
		}
		rawBuf[to++] = rawBuf[from];
	}
	buf.setSize(to);

	while (to > 1) {			// remove trailing excess
		to--;
		if ((rawBuf[to] == 10) || (rawBuf[to] == ' '))
			buf.setSize(to);
		else break;
	}
}


/******************************************************************************
 * zStr::getText	- gets text at a given offset
 *
 * ENT:
 *	offset	- idxoffset where the key is located.
 *	buf		- buffer to store text
 *	idxbuf	- buffer to store index key
 *			NOTE: buffer will be alloc'd / realloc'd and 
 *			should be free'd by the client
 *
 */

void zStr::getText(long offset, char **idxbuf, char **buf) {
	char *ch;
	char *idxbuflocal = 0;
	getKeyFromIdxOffset(offset, &idxbuflocal);
	__u32 start;
	__u32 size;

	do {
		lseek(idxfd->getFd(), offset, SEEK_SET);
		read(idxfd->getFd(), &start, sizeof(__u32));
		read(idxfd->getFd(), &size, sizeof(__u32));
		start = swordtoarch32(start);
		size = swordtoarch32(size);

		*buf = (*buf) ? (char *)realloc(*buf, size*2 + 1) : (char *)malloc(size*2 + 1);
		*idxbuf = (*idxbuf) ? (char *)realloc(*idxbuf, size*2 + 1) : (char *)malloc(size*2 + 1);
		memset(*buf, 0, size + 1);
		memset(*idxbuf, 0, size + 1);
		lseek(datfd->getFd(), start, SEEK_SET);
		read(datfd->getFd(), *buf, (int)(size));

		for (ch = *buf; *ch; ch++) {		// skip over index string
			if (*ch == 10) {
				ch++;
				break;
			}
		}
		memmove(*buf, ch, size - (unsigned long)(ch-*buf));

		// resolve link
		if (!strncmp(*buf, "@LINK", 5)) {
			for (ch = *buf; *ch; ch++) {		// null before nl
				if (*ch == 10) {
					*ch = 0;
					break;
				}
			}
			findKeyIndex(*buf + 6, &offset);
		}
		else break;
	}
	while (true);	// while we're resolving links

	if (idxbuflocal) {
		__u32 localsize = strlen(idxbuflocal);
		localsize = (localsize < (size - 1)) ? localsize : (size - 1);
		strncpy(*idxbuf, idxbuflocal, localsize);
		(*idxbuf)[localsize] = 0;
		free(idxbuflocal);
	}
	__u32 block = 0;
	__u32 entry = 0;
	memmove(&block, *buf, sizeof(__u32));
	memmove(&entry, *buf + sizeof(__u32), sizeof(__u32));
	block = swordtoarch32(block);
	entry = swordtoarch32(entry);
	getCompressedText(block, entry, buf);
}


/******************************************************************************
 * zStr::getCompressedText - Get text entry from a compressed index / zdata
 * 		file.
 */

void zStr::getCompressedText(long block, long entry, char **buf) {

	__u32 size = 0;

	if (cacheBlockIndex != block) {
		__u32 start = 0;

		lseek(zdxfd->getFd(), block * ZDXENTRYSIZE, SEEK_SET);
		read(zdxfd->getFd(), &start, sizeof(__u32));
		read(zdxfd->getFd(), &size, sizeof(__u32));
		start = swordtoarch32(start);
		size = swordtoarch32(size);

		SWBuf buf;
		buf.setSize(size + 5);
		lseek(zdtfd->getFd(), start, SEEK_SET);
		read(zdtfd->getFd(), buf.getRawData(), size);

		flushCache();

		unsigned long len = size;
		buf.setSize(size);
		rawZFilter(buf, 0); // 0 = decipher

		compressor->zBuf(&len, buf.getRawData());
		char *rawBuf = compressor->Buf(0, &len);
		cacheBlock = new EntriesBlock(rawBuf, len);
		cacheBlockIndex = block;
	}
	size = cacheBlock->getEntrySize(entry);
	*buf = (*buf) ? (char *)realloc(*buf, size*2 + 1) : (char *)malloc(size*2 + 1);
	strcpy(*buf, cacheBlock->getEntry(entry));
}


/******************************************************************************
 * zLD::settext	- Sets text for current offset
 *
 * ENT: key	- key for this entry
 *	buf	- buffer to store
 *      len     - length of buffer (0 - null terminated)
 */

void zStr::setText(const char *ikey, const char *buf, long len) {

	__u32 start, outstart;
	__u32 size, outsize;
	__s32 endoff;
	long idxoff = 0;
	__s32 shiftSize;
	static const char nl[] = {13, 10};
	char *tmpbuf = 0;
	char *key = 0;
	char *dbKey = 0;
	char *idxBytes = 0;
	char *outbuf = 0;
	char *ch = 0;

	len = (len < 0) ? strlen(buf) : len;
	stdstr(&key, ikey, 3);
	toupperstr_utf8(key, strlen(key)*3);

	char notFound = findKeyIndex(ikey, &idxoff, 0);
	if (!notFound) {
		getKeyFromIdxOffset(idxoff, &dbKey);
		int diff = strcmp(key, dbKey);
		if (diff < 0) {
		}
		else if (diff > 0) {
			idxoff += IDXENTRYSIZE;
		}
		else if ((!diff) && (len > 0 /*we're not deleting*/)) { // got absolute entry
			do {
				lseek(idxfd->getFd(), idxoff, SEEK_SET);
				read(idxfd->getFd(), &start, sizeof(__u32));
				read(idxfd->getFd(), &size, sizeof(__u32));
				start = swordtoarch32(start);
				size = swordtoarch32(size);

				tmpbuf = new char [ size + 2 ];
				memset(tmpbuf, 0, size + 2);
				lseek(datfd->getFd(), start, SEEK_SET);
				read(datfd->getFd(), tmpbuf, size);

				for (ch = tmpbuf; *ch; ch++) {		// skip over index string
					if (*ch == 10) {
						ch++;
						break;
					}
				}
				memmove(tmpbuf, ch, size - (unsigned long)(ch-tmpbuf));

				// resolve link
				if (!strncmp(tmpbuf, "@LINK", 5) && (len)) {
					for (ch = tmpbuf; *ch; ch++) {		// null before nl
						if (*ch == 10) {
							*ch = 0;
							break;
						}
					}
					findKeyIndex(tmpbuf + IDXENTRYSIZE, &idxoff);
					delete [] tmpbuf;
				}
				else break;
			}
			while (true);	// while we're resolving links
		}
	}

	endoff = lseek(idxfd->getFd(), 0, SEEK_END);

	shiftSize = endoff - idxoff;

	if (shiftSize > 0) {
	        idxBytes = new char [ shiftSize ];
		lseek(idxfd->getFd(), idxoff, SEEK_SET);
		read(idxfd->getFd(), idxBytes, shiftSize);
	}

	outbuf = new char [ len + strlen(key) + 5 ];
	sprintf(outbuf, "%s%c%c", key, 13, 10);
	size = strlen(outbuf);
	if (len > 0) {	// NOT a link
		if (!cacheBlock) {
			flushCache();
			cacheBlock = new EntriesBlock();
			cacheBlockIndex = (lseek(zdxfd->getFd(), 0, SEEK_END) / ZDXENTRYSIZE);
		}
		else if (cacheBlock->getCount() >= blockCount) {
			flushCache();
			cacheBlock = new EntriesBlock();
			cacheBlockIndex = (lseek(zdxfd->getFd(), 0, SEEK_END) / ZDXENTRYSIZE);
		}
		__u32 entry = cacheBlock->addEntry(buf);
		cacheDirty = true;
		outstart = archtosword32(cacheBlockIndex);
		outsize = archtosword32(entry);
		memcpy (outbuf + size, &outstart, sizeof(__u32));
		memcpy (outbuf + size + sizeof(__u32), &outsize, sizeof(__u32));
		size += (sizeof(__u32) * 2);
	}
	else {	// link
		memcpy(outbuf + size, buf, len);
		size += len;
	}

	start = lseek(datfd->getFd(), 0, SEEK_END);

	outstart = archtosword32(start);
	outsize  = archtosword32(size);

	lseek(idxfd->getFd(), idxoff, SEEK_SET);
	if (len > 0) {
		lseek(datfd->getFd(), start, SEEK_SET);
		write(datfd->getFd(), outbuf, size);

		// add a new line to make data file easier to read in an editor
		write(datfd->getFd(), &nl, 2);
		
		write(idxfd->getFd(), &outstart, sizeof(__u32));
		write(idxfd->getFd(), &outsize, sizeof(__u32));
		if (idxBytes) {
			write(idxfd->getFd(), idxBytes, shiftSize);
		}
	}
	else {	// delete entry
		if (idxBytes) {
			write(idxfd->getFd(), idxBytes+IDXENTRYSIZE, shiftSize-IDXENTRYSIZE);
			lseek(idxfd->getFd(), -1, SEEK_CUR);	// last valid byte
			FileMgr::systemFileMgr.trunc(idxfd);	// truncate index
		}
	}

	if (idxBytes)
		delete [] idxBytes;
	delete [] key;
	delete [] outbuf;
	free(dbKey);
}


/******************************************************************************
 * zLD::linkentry	- links one entry to another
 *
 * ENT: testmt	- testament to find (0 - Bible/module introduction)
 *	destidxoff	- dest offset into .vss
 *	srcidxoff		- source offset into .vss
 */

void zStr::linkEntry(const char *destkey, const char *srckey) {
	char *text = new char [ strlen(destkey) + 7 ];
	sprintf(text, "@LINK %s", destkey);
	setText(srckey, text);
	delete [] text;
}


void zStr::flushCache() {
	if (cacheBlock) {
		if (cacheDirty) {
			__u32 start = 0;
			unsigned long size = 0;
			__u32 outstart = 0, outsize = 0;

			const char *rawBuf = cacheBlock->getRawData(&size);
			compressor->Buf(rawBuf, &size);
			compressor->zBuf(&size);

			SWBuf buf;
			buf.setSize(size + 5);
			memcpy(buf.getRawData(), compressor->zBuf(&size), size); // 1 = encipher
			buf.setSize(size);
			rawZFilter(buf, 1); // 1 = encipher

			long zdxSize = lseek(zdxfd->getFd(), 0, SEEK_END);
			long zdtSize = lseek(zdtfd->getFd(), 0, SEEK_END);

			if ((cacheBlockIndex * ZDXENTRYSIZE) > (zdxSize - ZDXENTRYSIZE)) {	// New Block
				start = zdtSize;
			}
			else {
				lseek(zdxfd->getFd(), cacheBlockIndex * ZDXENTRYSIZE, SEEK_SET);
				read(zdxfd->getFd(), &start, sizeof(__u32));
				read(zdxfd->getFd(), &outsize, sizeof(__u32));
				start = swordtoarch32(start);
				outsize = swordtoarch32(outsize);
				if (start + outsize >= zdtSize) {	// last entry, just overwrite
					// start is already set
				}
				else	if (size < outsize) {	// middle entry, but smaller, that's fine and let's preserve bigger size
					size = outsize;
				}
				else {	// middle and bigger-- we have serious problems, for now let's put it at the end = lots of wasted space
					start = zdtSize;
				}
			}



			outstart = archtosword32(start);
			outsize  = archtosword32((__u32)size);

			lseek(zdxfd->getFd(), cacheBlockIndex * ZDXENTRYSIZE, SEEK_SET);
			lseek(zdtfd->getFd(), start, SEEK_SET);
			write(zdtfd->getFd(), buf, size);

			// add a new line to make data file easier to read in an editor
			write(zdtfd->getFd(), &nl, 2);
			
			write(zdxfd->getFd(), &outstart, sizeof(__u32));
			write(zdxfd->getFd(), &outsize, sizeof(__u32));
		}
		delete cacheBlock;
		cacheBlock = 0;
	}
	cacheBlockIndex = -1;
	cacheDirty = false;
}


/******************************************************************************
 * zLD::CreateModule	- Creates new module files
 *
 * ENT: path	- directory to store module files
 * RET: error status
 */

signed char zStr::createModule(const char *ipath) {
	char *path = 0;
	char *buf = new char [ strlen (ipath) + 20 ];
	FileDesc *fd, *fd2;

	stdstr(&path, ipath);

	if ((path[strlen(path)-1] == '/') || (path[strlen(path)-1] == '\\'))
		path[strlen(path)-1] = 0;

	sprintf(buf, "%s.dat", path);
	unlink(buf);
	fd = FileMgr::systemFileMgr.open(buf, O_CREAT|O_WRONLY|O_BINARY, S_IREAD|S_IWRITE);
	fd->getFd();
	FileMgr::systemFileMgr.close(fd);

	sprintf(buf, "%s.idx", path);
	unlink(buf);
	fd2 = FileMgr::systemFileMgr.open(buf, O_CREAT|O_WRONLY|O_BINARY, S_IREAD|S_IWRITE);
	fd2->getFd();
	FileMgr::systemFileMgr.close(fd2);

	sprintf(buf, "%s.zdt", path);
	unlink(buf);
	fd2 = FileMgr::systemFileMgr.open(buf, O_CREAT|O_WRONLY|O_BINARY, S_IREAD|S_IWRITE);
	fd2->getFd();
	FileMgr::systemFileMgr.close(fd2);

	sprintf(buf, "%s.zdx", path);
	unlink(buf);
	fd2 = FileMgr::systemFileMgr.open(buf, O_CREAT|O_WRONLY|O_BINARY, S_IREAD|S_IWRITE);
	fd2->getFd();
	FileMgr::systemFileMgr.close(fd2);

	delete [] path;
	
	return 0;
}

SWORD_NAMESPACE_END
