//////////////
// Isam.cpp //
//////////////

#include "isam.h"
#include <fstream.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>

static struct BTParms {
	char 		name[9], filename[9];
	int		keylen, dreclen, maxrecs, indxfd, datafd,
				kstart, count;
	t_func	keygen;
} btparms[40];

static int items = 0;

extern "C" {
	int (*Write)(int,const void *,unsigned) = write;
	int (*Read )(int,void *,unsigned) = read;
}
static void isam_init(void);

static void isam_init(void)
{
	ifstream btr("btparms.btr");
	char nwln, item[81], buf[20], fname[13], fcname[9];
	int i;

	if (!btr)
		eprintf("\n\nUnable to open btparms.btr.\n");

	for (i = 0; i < 40; i++) {
		btr.get (item, 80);
		if (!strlen(item))
			break;
		btr.get(nwln);
		strcpy(btparms[i].name, strtok(item, "^"));
		strtok(NULL, "^");
		strcpy(buf, strtok(NULL, "^"));
		btparms[i].keylen  = atoi(buf);
		strtok(NULL, "^");
		strcpy(buf, strtok(NULL, "^"));
		btparms[i].dreclen = atoi(buf);
		strtok(NULL, "^");
		strcpy(fname, strtok(NULL, "^"));
		strtok(NULL, "^");
		strcpy(buf, strtok(NULL, "^"));
		btparms[i].kstart = atoi(buf);
		strcpy(fcname, nospace(strtok(NULL, "^")));
		strcpy(btparms[i].filename, strtok(fname,"."));
		btparms[i].count = 0;
		btparms[i].indxfd = btparms[i].datafd = -1;
		if (strlen(fcname))
			btparms[i].keygen = cataloged_func (
				catalog_number(fcname));
		else
			btparms[i].keygen = (t_func) NULL;
	}

	items = i;
	btr.close();

	if (!items)
		eprintf("\n\nUnable to initialize isam system.\n");

	for (i = items; i < 40; i++) {
		strcpy(btparms[i].name,"");
		strcpy(btparms[i].filename,"");
		btparms[i].keygen = NULL;
		btparms[i].keylen = btparms[i].dreclen =
			btparms[i].count = btparms[i].kstart = 0;
		btparms[i].indxfd = btparms[i].datafd = -1;
	}
}

Isam::Isam(const char *datafilename, int e)
{
	int i, j;
	char buf[20];

	backingout = 0;
	elements = e;
	if (!items)						// this is the first Isam
		isam_init();				// initialize btparms

	strcpy(buf, nospace(datafilename));
	indices = 0;
										// find btree params
	for (i = 0; i < items; i++)
		if (!stricmp(buf, btparms[i].filename))
			btr[indices++] = i;
	if (!indices)					// couldn't find any, fatal
		eprintf("\n\nCan't find parameters for %s!\n",
			datafilename);
										// set up btree interfaces
	if(	!(btc = new BTC [indices])      				||
			!(inames = new char* [indices]) 				||
			!(loc = new long [elements])    				||
			!(oldrec = new char* [elements])				||
			!(rec = new char* [elements])   				||
			!(okey = new char [btparms[*btr].dreclen])||
			!(nkey = new char [btparms[*btr].dreclen])	 )
		eprintf("\n\nOut of memory.\n");
	for (i = 0; i < indices; i++) {
		inames[i] = btparms[btr[i]].name;
		btc[i].btvcount	= 1;
		if (btrinit(inames[i], btc+i) == ERR)
			eprintf("\n\nCouldn't initialize %s.\n",
				inames[i]);
		btc[i].btmulti 	= 0;	// record locking off
	}
	for (i = 0; i < elements; i++) {
		loc[i] = 0L;
		if (!(oldrec[i] = new char
			[btparms[*btr].dreclen + 1]) ||
			!(rec[i] = new char
			[btparms[*btr].dreclen + 1]) )
				eprintf("\n\nOut of memory.\n");
	}
	if (btparms[*btr].count) {	// open files if neccesary
		fd[0] = btparms[*btr].datafd;
		fd[1] = btparms[*btr].indxfd;
	}
	else {							// yup, it's neccesary
		strcpy(buf, btparms[*btr].filename);
		strcat(buf, ".dat");
		if ((fd[0]=bt_open(buf, O_RDWR,S_IRDWR)) == ERR)
			eprintf("\n\nCan't open %s.\n", buf);
		strcpy(buf, btparms[*btr].filename);
		strcat(buf, ".idx");
		if ((fd[1]=bt_open(buf, O_RDWR,S_IRDWR)) == ERR)
			eprintf("\n\nCan't open %s.\n", buf);
		btparms[*btr].datafd = fd[0];
		btparms[*btr].indxfd = fd[1];
	}

	(btparms[*btr].count)++;	// record that we are here
	clear();
}

Isam::~Isam()
{
	int i, j;
										// clean up out mess
	for (i = 0; i < indices; i++)
		btrterm(btc+i);
	for (i = 0; i < elements; i++) {
		delete [] oldrec[i];
		delete [] rec[i];
	}
	delete [] btc;
	delete [] inames;
	delete [] loc;
	delete [] oldrec;
	delete [] rec;
	delete [] nkey;
	delete [] okey;
			// if we're last ones out, turn off the lights
	if (!(--(btparms[*btr].count))) {
		close (fd[0]);
		close (fd[1]);
		btparms[*btr].indxfd = -1;
		btparms[*btr].datafd = -1;
	}
}

int Isam::write ()
{
	int deltakey, old1, new1, ele, index, result, len,
		 start, notfirst;
	t_func fcn;
	if (*loc < 0)	// programmer hasn't followed rules
		eprintf ("No writes after gets!");
	for (ele = 0; ele < elements; ele++) {
		old1 = strlen(oldrec[ele]);
		new1 = strlen(   rec[ele]);
		if (!(old1 || new1) ||
				!strcmp(oldrec[ele], rec[ele]))
			continue;
		for (index = 0; index < indices; index++) {
						// generate the old and/or new key
			len   = btparms[btr[index]].keylen;
			start = btparms[btr[index]].kstart;
			if ((fcn = btparms[btr[index]].keygen) !=
					(t_func) NULL) {
				if (old1)
					strcpy(okey, fcn(oldrec[ele]));
				if (new1)
					strcpy(nkey, fcn(   rec[ele]));
			}
			else {
				if (old1)
					strnncpy(okey, oldrec[ele] + start, len);
				if (new1)
					strnncpy(nkey,    rec[ele] + start, len);
			}
			deltakey = (!old1||!new1||stricmp(okey,nkey));

			if (old1 && deltakey) {
				notfirst = 0;
				while (strlen(okey) >= len) {
					strnncpy(btc[index].btkey, ToUpper(okey),
						len);
					btc[index].btoptype = (new1 || index ||
						notfirst++ || backingout) ? DELTKY
						: DELETE;
					btc[index].btloc = loc[ele];
					result=cbtree(fd[0], fd[1], btc+index);
					if (result != BTCALLOK)
						backout (ele, 'D', index, result);
					strcpy(okey, okey + len);
				}
			}
			if (new1 && deltakey) {
				notfirst = 0;
				while (strlen(nkey) >= len) {
					strnncpy(btc[index].btkey, ToUpper(nkey),
						len);
					btc[index].btoptype =  (loc[ele] == 0L) ?
						INSERT : ISRTKY;
					btc[index].btloc = loc[ele];
					result = cbtree(fd[0], fd[1], btc+index);
					if (result == BTCALLOK)
						loc[ele] = btc[index].btloc;
					else
						backout(ele, 'I', index, result);
					strcpy(nkey, nkey + len);
				}
			}
		}
		if (!new1)
			continue;
		if (btseek (fd[0], loc[ele], btc->btdtalen)
				== -1L)
			backout(ele, 'S');
		if (Write(fd[0],rec[ele],(unsigned)btc->btdtalen)
				!= btc->btdtalen)
			backout(ele, 'W');
	}
	clear ();
	return 0;
}

void Isam::backout(int ele,char op,int index,int result)
{	// backs out record add/change/delete on error
	char *trec;
	if (!(trec = new char [btparms[*btr].dreclen]) ||
			backingout) // I quit!  Error in error backout!
		eprintf("\n\nBackout error, error type was %c\n",
			backingout);
	if (index >= 0)
		indices = index + 1; // those beyond index are ok
	backingout = op;
	elements = 1;
	strcpy(trec,rec[ele]);
	strcpy(rec[0],oldrec[ele]);
	strcpy(oldrec[0],trec);
	loc[0] = loc[ele];
	write();
	switch (backingout) {
	case 'I':
		eprintf(
"\n\nINSERT error, element %d, index %s, result %d\n",
			ele, inames[index], result);
	case 'D':
		eprintf(
"\n\nDELETE error, element %d, index %s, result %d\n",
			ele, inames[index], result);
	case 'S':
		eprintf ("\n\nSeek error, element %d\n", ele);
	case 'W':
		eprintf ("\n\nWrite error, element %d\n", ele);
	}
}

int Isam::read(const char *key, int ele_limit, int idx,
	int ele)
{
	int i = 0, j = 0;

	if ((ele_limit + ele) > elements)
		eprintf("\n\nNot enough elements!\n");
	free_svkey(btc+idx);
	btc[idx].btoptype = GETALL;
	strcpy(btc[idx].btkey, ToUpper(key));
	btc[idx].btloc = 0L;
	while (cbtree(fd[0], fd[1], btc + idx) == BTCALLOK) {
		while (btc[idx].btrecnum[i-j] != 0L) {
			if (i < ele_limit) {
				loc[i+ele] = btc[idx].btrecnum[i-j];
				btseek(fd[0], loc[i+ele], btc[idx].btdtalen);
				Read  (fd[0], rec[i+ele], btc[idx].btdtalen);
			}
			i++;
			if (!((i-j) < btc[idx].btmax))
				break;
		}
		if ((i-j) < btc[idx].btmax)
			break;
		j += btc[idx].btmax;
	}
	for (j = 0; j < ele_limit; j++)
		strcpy(oldrec[j], rec[j]);
	return i;
}

int Isam::getfirst(int index)
{
	return getxxx (index, GETFRST);
}

int Isam::getnext(int index)
{
	return getxxx (index, GETNXT);
}

int Isam::getge (char *key, int index)
{
	*loc = -1;
	btc[index].btoptype = GETGE;
	strcpy(btc[index].btkey, ToUpper(key));
	if (!(cbtree(fd[0], fd[1], btc + index) == BTCALLOK))
		return 0;
	btseek(fd[0], btc[index].btloc, btc[index].btdtalen);
	Read  (fd[0], rec[0], btc[index].btdtalen);
	return 1;
}

int Isam::getxxx(int index, int opt)
{
	*loc = -1;
	btc[index].btoptype = opt;
	if (!(cbtree(fd[0], fd[1], btc + index) == BTCALLOK))
		return 0;
	btseek(fd[0], btc[index].btloc, btc[index].btdtalen);
	Read  (fd[0], rec[0], btc[index].btdtalen);
	return 1;
}

void Isam::clear()
{
	int i;
	for (i = 0; i < elements; i++) {
		loc[i] = 0L;
		memset (oldrec[i],'\0', btparms[*btr].dreclen + 1);
		memset (   rec[i],'\0', btparms[*btr].dreclen + 1);
	}
}

int Isam::keynum (const char *btname)
{
	int i;

	if (!items)
		eprintf("\n\nBtparms not initialized!\n");
	if (indices < 1)
		eprintf("\n\nNo indices.\n");
	for (i = 0; i < indices; i++)
		if (!stricmp(inames[i], nospace(btname)))
			return i;
	eprintf ("\n\nIndex %s not found.\n", btname);
	return -1;
}

char* nospace(const char *arg)
{
	static char rtn[80];
	int i, j = 0, k = strlen(arg);
	for (i = 0; i < k; i++)
		if (arg[i] != ' ')
			rtn[j++] = arg[i];
	rtn[j] = '\0';
	return rtn;
}

int eprintf(const char *format, ... )
{
	int rtn;
	va_list argptr;
	va_start(argptr, format);
	rtn = vprintf(format, argptr);
	va_end (argptr);
	exit (1);
	return rtn;
}

char *ToUpper(const char *c)
{
	static char d[257];
	int i;

	for (i = 0; i < 256, c[i] != '\0'; i++)
		d[i] = toupper(c[i]);
	d[i] = '\0';
	return d;
}

void Isam::reindex(rel_func func)
{
	char buf[20], cmd[80];
	int tad, i, rlen = btparms[*btr].dreclen, x, y;
	long l;

	clear();
	strcpy(buf, btparms[*btr].filename);
	strcat(buf, ".tad");
	if (bt_open(buf, O_RDWR,S_IRDWR) != ERR)
							// I can't cook in a dirty kitchen!
							// Besides that, the last re-index
							// must have failed and I don't know
							// where the real data is, now.
		eprintf("\n\n%s still exists!\n", buf);
	sprintf(cmd, "ren %s.dat %s.tad",
		btparms[*btr].filename, btparms[*btr].filename);
	close (fd[0]);
	system (cmd);								// ren .dat to .tad
	if ((tad = bt_open(buf, O_RDWR,S_IRDWR)) == ERR)
		eprintf("\n\nCan't open %s\n", buf);// ren didn't go
	strcpy(buf, btparms[*btr].filename);	// recreate .dat
	strcat(buf, ".dat");
	if((fd[0]=bt_open(buf, O_NEW|O_RDWR, S_IRDWR) ) == ERR)
		eprintf ("\n\nCouldn't recreate %s.dat\n",
			btparms[*btr].filename);
	initdat(fd[0], btc);					// create file header
	btparms[*btr].datafd = fd[0];
	strcpy(buf, btparms[*btr].filename);	// unlink .idx
	strcat(buf, ".idx");
	close (fd[1]);
	unlink (buf);
	for (i = 0; i < indices; i++) {
		btrterm(btc + i);		// to btrinit() with no .idx
		if (btrinit(inames[i], btc+i) == ERR)
			eprintf("\n\nCouldn't re-initialize %s.\n",
				inames[i]);
		creatbtr(btc + i);
	}
	for (i = 0; i < indices; i++) {			// re-initialize
		btrterm(btc + i);
		if (btrinit(inames[i], btc+i) == ERR)
			eprintf("\n\nCouldn't re-initialize %s.\n",
				inames[i]);
	}				// We now have empty datafile and indexfile
	strcpy(buf, btparms[*btr].filename);	// re-open .idx
	strcat(buf, ".idx");
	if((fd[1]=bt_open(buf, O_RDWR, S_IRDWR) ) == ERR)
		eprintf ("\n\nCouldn't recreate %s.idx\n",
			btparms[*btr].filename);
	btparms[*btr].indxfd = fd[1];
	clrscr();										// we're ready
	l = 2L; i = 0;
	while (1) {
		if (!(l%10)) {
			if (l < 11L) {
				gotoxy(10,10);
				cprintf("Processing %s location ",
					btparms[*btr].filename);
				x = wherex(); y = wherey();
			}
			gotoxy(x, y);
			cprintf("%ld", l);
		}
		btseek(tad, l++, rlen);
		if (Read (tad, *rec, rlen) == rlen) {
			if (**rec != '~') {
				if (func)
					if (func(*rec))
						continue;
				write();
				i++;
			}
		}
		else
			break;
	}
	printf ("\n\n%d records added.\n", i);
	close (tad);
	strcpy(buf, btparms[*btr].filename);
	strcat(buf, ".tad");
	unlink (buf);				// clean kitchen for next time
}

