/******************************************************************************
 *  VerseKey.cpp - code for class 'VerseKey'- a standard Biblical verse key
 */

#include <swmacs.h>
#include <utilfuns.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>

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

#include <utilstr.h>
#include <swkey.h>
#include <swlog.h>
#include <versekey.h>
#include <localemgr.h>
#include <roman.h>

SWORD_NAMESPACE_START

static const char *classes[] = {"VerseKey", "SWKey", "SWObject", 0};
SWClass VerseKey::classdef(classes);

/******************************************************************************
 *  Initialize static members of VerseKey
 */

#include <canon.h>	// Initialize static members of canonical books structure

struct sbook *VerseKey::builtin_books[2]       = {0,0};
const char    VerseKey::builtin_BMAX[2]        = {39, 27};
long         *VerseKey::offsets[2][2]  = {{VerseKey::otbks, VerseKey::otcps}, {VerseKey::ntbks, VerseKey::ntcps}};
int           VerseKey::instance       = 0;
VerseKey::LocaleCache   VerseKey::localeCache;


/******************************************************************************
 * VerseKey::init - initializes instance of VerseKey
 */

void VerseKey::init() {
	myclass = &classdef;
	if (!instance)
		initstatics();

	instance++;
	autonorm = 1;		// default auto normalization to true
	headings = 0;		// default display headings option is false
	upperBound = 0;
	lowerBound = 0;
	boundSet = false;
	testament = 0;
	book = 0;
	chapter = 0;
	verse = 0;
	locale = 0;

	setLocale(LocaleMgr::systemLocaleMgr.getDefaultLocaleName());
}

/******************************************************************************
 * VerseKey Constructor - initializes instance of VerseKey
 *
 * ENT:	ikey - base key (will take various forms of 'BOOK CH:VS'.  See
 *		VerseKey::parse for more detailed information)
 */

VerseKey::VerseKey(const SWKey *ikey) : SWKey(*ikey)
{
	init();
	if (ikey)
		parse();
}


/******************************************************************************
 * VerseKey Constructor - initializes instance of VerseKey
 *
 * ENT:	ikey - text key (will take various forms of 'BOOK CH:VS'.  See
 *		VerseKey::parse for more detailed information)
 */

VerseKey::VerseKey(const char *ikey) : SWKey(ikey)
{
	init();
	if (ikey)
		parse();
}


VerseKey::VerseKey(VerseKey const &k) : SWKey(k)
{
	init();
	autonorm = k.autonorm;
	headings = k.headings;
	testament = k.Testament();
	book = k.Book();
	chapter = k.Chapter();
	verse = k.Verse();
	if (k.isBoundSet()) {
		LowerBound(k.LowerBound());
		UpperBound(k.UpperBound());
	}
}


VerseKey::VerseKey(const char *min, const char *max) : SWKey()
{
	init();
	LowerBound(min);
	UpperBound(max);
	setPosition(TOP);
}


SWKey *VerseKey::clone() const
{
	return new VerseKey(*this);
}


/******************************************************************************
 * VerseKey Destructor - cleans up instance of VerseKey
 *
 * ENT:	ikey - text key
 */

VerseKey::~VerseKey() {
	if (upperBound)
		delete upperBound;
	if (lowerBound)
		delete lowerBound;
	if (locale)
		delete [] locale;

	--instance;
}


void VerseKey::setLocale(const char *name) {
	char *BMAX;
	struct sbook **books;
	bool useCache = false;

	if (localeCache.name)
		useCache = (!strcmp(localeCache.name, name));

	if (!useCache)	{	// if we're setting params for a new locale
		stdstr(&(localeCache.name), name);
		localeCache.abbrevsCnt = 0;
	}

	SWLocale *locale = (useCache) ? localeCache.locale : LocaleMgr::systemLocaleMgr.getLocale(name);
	localeCache.locale = locale;

	if (locale) {
		locale->getBooks(&BMAX, &books);
		setBooks(BMAX, books);
		setBookAbbrevs(locale->getBookAbbrevs(), localeCache.abbrevsCnt);
		localeCache.abbrevsCnt = abbrevsCnt;
	}
	else {
		setBooks(builtin_BMAX, builtin_books);
		setBookAbbrevs(builtin_abbrevs, localeCache.abbrevsCnt);
		localeCache.abbrevsCnt = abbrevsCnt;
	}
	stdstr(&(this->locale), localeCache.name);

	if (lowerBound)
		LowerBound().setLocale(name);
	if (upperBound) 
		UpperBound().setLocale(name);
}

void VerseKey::setBooks(const char *iBMAX, struct sbook **ibooks) {
	BMAX = iBMAX;
	books = ibooks;
}


void VerseKey::setBookAbbrevs(const struct abbrev *bookAbbrevs, unsigned int size) {
	abbrevs = bookAbbrevs;
	if (!size) {
		for (abbrevsCnt = 0; *abbrevs[abbrevsCnt].ab; abbrevsCnt++) {
			/*
			if (strcmp(abbrevs[abbrevsCnt-1].ab, abbrevs[abbrevsCnt].ab) > 0) {
				fprintf(stderr, "ERROR: book abbreviation (canon.h or locale) misordered at entry: %s\n", abbrevs[abbrevsCnt].ab);
				exit(-1);
			}
			*/
		}
        for (int t = 0; t < 2; t++) {
            for (int i = 0; i < BMAX[t]; i++) {
                int bn = getBookAbbrev(books[t][i].name);
                if ((bn-1)%39 != i) {
				SWLog::systemlog->LogError("Book: %s does not have a matching toupper abbrevs entry! book number returned was: %d", books[t][i].name, bn);
                }
            }
        }
    }
	else abbrevsCnt = size;
}


/******************************************************************************
 * VerseKey::initstatics - initializes statics.  Performed only when first
 *						instance on VerseKey (or descendent) is created.
 */

void VerseKey::initstatics() {
	int l1, l2, chaptmp = 0;

	builtin_books[0] = otbooks;
	builtin_books[1] = ntbooks;

	for (l1 = 0; l1 < 2; l1++) {
		for (l2 = 0; l2 < builtin_BMAX[l1]; l2++) {
			builtin_books[l1][l2].versemax = &vm[chaptmp];
			chaptmp += builtin_books[l1][l2].chapmax;
		}
	}
}


/******************************************************************************
 * VerseKey::parse - parses keytext into testament|book|chapter|verse
 *
 * RET:	error status
 */

char VerseKey::parse()
{

	
	testament = 2;
	book      = BMAX[1];
	chapter   = 1;
	verse     = 1;
	int booklen   = 0;

	int error = 0;

	if (keytext) {
		ListKey tmpListKey = VerseKey::ParseVerseList(keytext);
		if (tmpListKey.Count()) {
			SWKey::setText((const char *)tmpListKey);
			for (int i = 1; i < 3; i++) {
				for (int j = 1; j <= BMAX[i-1]; j++) {
					int matchlen = strlen(books[i-1][j-1].name);
					if (!strncmp(keytext, books[i-1][j-1].name, matchlen)) {
						if (matchlen > booklen) {
							booklen = matchlen;
							testament = i;
							book = j;
						}
					}
				}
			}

			if (booklen) {
				sscanf(&keytext[booklen], "%d:%d", &chapter, &verse);
			}
			else	error = 1;
		} else error = 1;
	}
	Normalize(1);
	freshtext();

	return (this->error) ? this->error : (this->error = error);
}


/******************************************************************************
 * VerseKey::freshtext - refreshes keytext based on
 *				testament|book|chapter|verse
 */

void VerseKey::freshtext() const
{
	char buf[2024];
	int realtest = testament;
	int realbook = book;

	if (book < 1) {
		if (testament < 1)
			sprintf(buf, "[ Module Heading ]");
		else sprintf(buf, "[ Testament %d Heading ]", (int)testament);
	}
	else {
		if (realbook > BMAX[realtest-1]) {
			realbook -= BMAX[realtest-1];
			if (realtest < 2)
				realtest++;
			if (realbook > BMAX[realtest-1])
				realbook = BMAX[realtest-1];
		}
		sprintf(buf, "%s %d:%d", books[realtest-1][realbook-1].name, chapter, verse);
	}

	stdstr((char **)&keytext, buf);
}



/******************************************************************************
 * VerseKey::getBookAbbrev - Attempts to find a book abbreviation for a buffer
 *
 * ENT:	abbr - key for which to search;
 * RET:	book number or < 0 = not valid
 */

int VerseKey::getBookAbbrev(const char *iabbr)
{
	int loop, diff, abLen, min, max, target, retVal = -1;

	char *abbr = 0;

	for (int i = 0; i < 2; i++) {
		stdstr(&abbr, iabbr);
		strstrip(abbr);
		if (!i)
			toupperstr(abbr);
		abLen = strlen(abbr);

		if (abLen) {
			min = 0;
//			max = abbrevsCnt - 1;
			max 	= abbrevsCnt;
			while(1) {
				target = min + ((max - min) / 2);
				diff = strncmp(abbr, abbrevs[target].ab, abLen);
				if ((!diff)||(target >= max)||(target <= min))
					break;
				if (diff > 0)
					min = target;
				else	max = target;
			}
			for (; target > 0; target--) {
				if (strncmp(abbr, abbrevs[target-1].ab, abLen))
					break;
			}
				
			retVal = (!diff) ? abbrevs[target].book : -1;
		}
		if (retVal > 0)
			break;
	}
	delete [] abbr;
	return retVal;
}

/******************************************************************************
 * VerseKey::ParseVerseList - Attempts to parse a buffer into separate
 *				verse entries returned in a ListKey
 *
 * ENT:	buf		- buffer to parse;
 *	defaultKey	- if verse, chap, book, or testament is left off,
 *				pull info from this key (ie. Gen 2:3; 4:5;
 *				Gen would be used when parsing the 4:5 section)
 *	expandRange	- whether or not to expand eg. John 1:10-12 or just
 *				save John 1:10
 *
 * RET:	ListKey reference filled with verse entries contained in buf
 *
 * COMMENT: This code works but wreaks.  Rewrite to make more maintainable.
 */

ListKey VerseKey::ParseVerseList(const char *buf, const char *defaultKey, bool expandRange) {
	SWKey textkey;

	char book[2048];
	char number[2048];
	int tobook = 0;
	int tonumber = 0;
	int chap = -1, verse = -1;
	int bookno = 0;
	VerseKey curkey, lBound;
	curkey.setLocale(getLocale());
	lBound.setLocale(getLocale());
	int loop;
	char comma = 0;
	char dash = 0;
	const char *orig = buf;
	int q;
	ListKey tmpListKey;
	ListKey internalListKey;
	SWKey tmpDefaultKey = defaultKey;
	char lastPartial = 0;
	bool inTerm = true;
	int notAllDigits;

	curkey.AutoNormalize(0);
	tmpListKey << tmpDefaultKey;
	tmpListKey.GetElement()->userData = (void *)buf;
	
	while (*buf) {
		switch (*buf) {
		case ':':
			number[tonumber] = 0;
			tonumber = 0;
			if (*number)
				chap = atoi(number);
			*number = 0;
			break;

		case ' ':
			inTerm = true;
			while (true) {
				if ((!*number) || (chap < 0))
					break;
				for (q = 1; ((buf[q]) && (buf[q] != ' ')); q++);
				if (buf[q] == ':')
					break;
				inTerm = false;
				break;
			}
			if (inTerm) {
				book[tobook++] = ' ';
				break;
			}
		case '-': 
		case ',': // on number new verse
		case ';': // on number new chapter
			number[tonumber] = 0;
			tonumber = 0;
			if (*number) {
				if (chap >= 0)
					verse = atoi(number);
				else	chap = atoi(number);
			}
			*number = 0;
			book[tobook] = 0;
			tobook = 0;
			bookno = -1;
			if (*book) {
				for (loop = strlen(book) - 1; loop+1; loop--) {
					if ((isdigit(book[loop])) || (book[loop] == ' ')) {
						book[loop] = 0;
						continue;
					}
					else {
						if ((SW_toupper(book[loop])=='F')&&(loop)) {
							if ((isdigit(book[loop-1])) || (book[loop-1] == ' ') || (SW_toupper(book[loop-1]) == 'F')) {
								book[loop] = 0;
								continue;
							}
						}
					}
					break;
				}

                                for (loop = strlen(book) - 1; loop+1; loop--) {
					if (book[loop] == ' ') {
						if (isroman(&book[loop+1])) {
							if (verse == -1) {
								verse = chap;
								chap = from_rom(&book[loop+1]);
								book[loop] = 0;
					       	        }
                                                }
	        				break;
                                        }
                                }

                                if ((!stricmp(book, "V")) || (!stricmp(book, "VER"))) {	// Verse abbrev
                                        if (verse == -1) {
                                                verse = chap;
		                		chap = VerseKey(tmpListKey).Chapter();
                                                *book = 0;
                                        }
                                }
                                if ((!stricmp(book, "ch")) || (!stricmp(book, "chap"))) {	// Verse abbrev
					strcpy(book, VerseKey(tmpListKey).getBookName());
                                }
				bookno = getBookAbbrev(book);
			}
			if (((bookno > -1) || (!*book)) && ((*book) || (chap >= 0) || (verse >= 0))) {
				char partial = 0;
				curkey.Verse(1);
				curkey.Chapter(1);
				curkey.Book(1);

				if (bookno < 0) {
					curkey.Testament(VerseKey(tmpListKey).Testament());
					curkey.Book(VerseKey(tmpListKey).Book());
				}
				else {
					curkey.Testament(1);
					curkey.Book(bookno);
				}

				if (((comma)||((verse < 0)&&(bookno < 0)))&&(!lastPartial)) {
//				if (comma) {
					curkey.Chapter(VerseKey(tmpListKey).Chapter());
					curkey.Verse(chap);  // chap because this is the first number captured
				}
				else {
					if (chap >= 0) {
						curkey.Chapter(chap);
					}
					else {
						partial++;
						curkey.Chapter(1);
					}
					if (verse >= 0) {
						curkey.Verse(verse);
					}
					else {
						partial++;
						curkey.Verse(1);
					}
				}

				if ((*buf == '-') && (expandRange)) {	// if this is a dash save lowerBound and wait for upper
					VerseKey newElement;
					newElement.LowerBound(curkey);
					newElement.setPosition(TOP);
					tmpListKey << newElement;
					tmpListKey.GetElement()->userData = (void *)buf;
				}
				else {
					if (!dash) { 	// if last separator was not a dash just add
						if (expandRange && partial) {
							VerseKey newElement;
							newElement.LowerBound(curkey);
							if (partial > 1)
								curkey.setPosition(MAXCHAPTER);
							if (partial > 0)
								curkey = MAXVERSE;
							newElement.UpperBound(curkey);
							newElement = TOP;
							tmpListKey << newElement;
							tmpListKey.GetElement()->userData = (void *)buf;
						}
						else {
							tmpListKey << (const SWKey &)(const SWKey)(const char *)curkey;
							tmpListKey.GetElement()->userData = (void *)buf;
						}
					}
					else	if (expandRange) {
						VerseKey *newElement = SWDYNAMIC_CAST(VerseKey, tmpListKey.GetElement());
						if (newElement) {
							if (partial > 1)
								curkey = MAXCHAPTER;
							if (partial > 0)
								curkey = MAXVERSE;
							newElement->UpperBound(curkey);
							*newElement = TOP;
							tmpListKey.GetElement()->userData = (void *)buf;
						}
					}
				}
				lastPartial = partial;
			}
			*book = 0;
			chap = -1;
			verse = -1;
			if (*buf == ',')
				comma = 1;
			else	comma = 0;
			if (*buf == '-')
				dash = 1;
			else	dash = 0;
			break;
		case 10:	// ignore these
		case 13: 
		case '[': 
		case ']': 
		case '(': 
		case ')': 
			break;
		case '.':
			if (buf > orig)			// ignore (break) if preceeding char is not a digit
				for (notAllDigits = tobook; notAllDigits; notAllDigits--) {
					if ((!isdigit(book[notAllDigits-1])) && (!strchr(" .", book[notAllDigits-1])))
						break;
				}
				if (!notAllDigits)
					break;

			number[tonumber] = 0;
			tonumber = 0;
			if (*number)
				chap = atoi(number);
			*number = 0;
			break;
			
		default:
			if (isdigit(*buf)) {
				number[tonumber++] = *buf;
			}
			else {
				switch (*buf) {
				case ' ':    // ignore these and don't reset number
				case 'f':
				case 'F':
					break;
				default:
					number[tonumber] = 0;
					tonumber = 0;
					break;
				}
			}
			if (chap == -1)
				book[tobook++] = *buf;
		}
		buf++;
	}
	number[tonumber] = 0;
	tonumber = 0;
	if (*number) {
		if (chap >= 0)
			verse = atoi(number);
		else	chap = atoi(number);
	}
	*number = 0;
	book[tobook] = 0;
	tobook = 0;
	if (*book) {
		for (loop = strlen(book) - 1; loop+1; loop--) {
			if ((isdigit(book[loop])) || (book[loop] == ' ')) {
				book[loop] = 0;
				continue;
			}
			else {
				if ((SW_toupper(book[loop])=='F')&&(loop)) {
					if ((isdigit(book[loop-1])) || (book[loop-1] == ' ') || (SW_toupper(book[loop-1]) == 'F')) {
						book[loop] = 0;
						continue;
					}
				}
			}
			break;
		}

		for (loop = strlen(book) - 1; loop+1; loop--) {
			if (book[loop] == ' ') {
				if (isroman(&book[loop+1])) {
					if (verse == -1) {
						verse = chap;
						chap = from_rom(&book[loop+1]);
						book[loop] = 0;
					}
				}
				break;
			}
          }
               
		if ((!stricmp(book, "V")) || (!stricmp(book, "VER"))) {	// Verse abbrev.
			if (verse == -1) {
				verse = chap;
				chap = VerseKey(tmpListKey).Chapter();
				*book = 0;
			}
		}
			
		if ((!stricmp(book, "ch")) || (!stricmp(book, "chap"))) {	// Verse abbrev
			strcpy(book, VerseKey(tmpListKey).getBookName());
		}
		bookno = getBookAbbrev(book);
	}
	if (((bookno > -1) || (!*book)) && ((*book) || (chap >= 0) || (verse >= 0))) {
		char partial = 0;
		curkey.Verse(1);
		curkey.Chapter(1);
		curkey.Book(1);

		if (bookno < 0) {
			curkey.Testament(VerseKey(tmpListKey).Testament());
			curkey.Book(VerseKey(tmpListKey).Book());
		}
		else {
			curkey.Testament(1);
			curkey.Book(bookno);
		}

		if (((comma)||((verse < 0)&&(bookno < 0)))&&(!lastPartial)) {
//		if (comma) {
			curkey.Chapter(VerseKey(tmpListKey).Chapter());
			curkey.Verse(chap);  // chap because this is the first number captured
		}
		else {
			if (chap >= 0) {
				curkey.Chapter(chap);
			}
			else {
				partial++;
				curkey.Chapter(1);
			}
			if (verse >= 0) {
				curkey.Verse(verse);
			}
			else {
				partial++;
				curkey.Verse(1);
			}
		}

		if ((*buf == '-') && (expandRange)) {	// if this is a dash save lowerBound and wait for upper
			VerseKey newElement;
			newElement.LowerBound(curkey);
			newElement = TOP;
			tmpListKey << newElement;
			tmpListKey.GetElement()->userData = (void *)buf;
		}
		else {
			if (!dash) { 	// if last separator was not a dash just add
				if (expandRange && partial) {
					VerseKey newElement;
					newElement.LowerBound(curkey);
					if (partial > 1)
						curkey = MAXCHAPTER;
					if (partial > 0)
						curkey = MAXVERSE;
					newElement.UpperBound(curkey);
					newElement = TOP;
					tmpListKey << newElement;
					tmpListKey.GetElement()->userData = (void *)buf;
				}
				else {
					tmpListKey << (const SWKey &)(const SWKey)(const char *)curkey;
					tmpListKey.GetElement()->userData = (void *)buf;
				}
			}
			else if (expandRange) {
				VerseKey *newElement = SWDYNAMIC_CAST(VerseKey, tmpListKey.GetElement());
				if (newElement) {
					if (partial > 1)
						curkey = MAXCHAPTER;
					if (partial > 0)
						curkey = MAXVERSE;
					newElement->UpperBound(curkey);
					*newElement = TOP;
					tmpListKey.GetElement()->userData = (void *)buf;
				}
			}
		}
	}
	*book = 0;
	tmpListKey = TOP;
	tmpListKey.Remove();	// remove defaultKey
	internalListKey = tmpListKey;
	internalListKey = TOP;	// Align internalListKey to first element before passing back;

	return internalListKey;
}


/******************************************************************************
 * VerseKey::LowerBound	- sets / gets the lower boundary for this key
 */

VerseKey &VerseKey::LowerBound(const char *lb)
{
	if (!lowerBound) 
		initBounds();

	(*lowerBound) = lb;
	lowerBound->Normalize();
	lowerBound->setLocale( this->getLocale() );
	boundSet = true;
	return (*lowerBound);
}


/******************************************************************************
 * VerseKey::UpperBound	- sets / gets the upper boundary for this key
 */

VerseKey &VerseKey::UpperBound(const char *ub)
{
	if (!upperBound) 
		initBounds();

// need to set upperbound parsing to resolve to max verse/chap if not specified
	   (*upperBound) = ub;
	if (*upperBound < *lowerBound)
		*upperBound = *lowerBound;
	upperBound->Normalize();
	upperBound->setLocale( this->getLocale() );

// until we have a proper method to resolve max verse/chap use this kludge
	int len = strlen(ub);
	bool alpha = false;
	bool versespec = false;
	bool chapspec = false;
	for (int i = 0; i < len; i++) {
		if (isalpha(ub[i]))
			alpha = true;
		if (ub[i] == ':')	// if we have a : we assume verse spec
			versespec = true;
		if ((isdigit(ub[i])) && (alpha))	// if digit after alpha assume chap spec
			chapspec = true;
	}
	if (!chapspec)
		*upperBound = MAXCHAPTER;
	if (!versespec)
		*upperBound = MAXVERSE;
	

// -- end kludge
	boundSet = true;
	return (*upperBound);
}


/******************************************************************************
 * VerseKey::LowerBound	- sets / gets the lower boundary for this key
 */

VerseKey &VerseKey::LowerBound() const
{
	if (!lowerBound) 
		initBounds();

	return (*lowerBound);
}


/******************************************************************************
 * VerseKey::UpperBound	- sets / gets the upper boundary for this key
 */

VerseKey &VerseKey::UpperBound() const
{
	if (!upperBound) 
		initBounds();

	return (*upperBound);
}


/******************************************************************************
 * VerseKey::ClearBounds	- clears bounds for this VerseKey
 */

void VerseKey::ClearBounds()
{
	initBounds();
}


void VerseKey::initBounds() const
{
	if (!upperBound) {
		upperBound = new VerseKey();
		upperBound->AutoNormalize(0);
		upperBound->Headings(1);
	}
	if (!lowerBound) {
		lowerBound = new VerseKey();
		lowerBound->AutoNormalize(0);
		lowerBound->Headings(1);
	}

	lowerBound->Testament(0);
	lowerBound->Book(0);
	lowerBound->Chapter(0);
	lowerBound->Verse(0);

	upperBound->Testament(2);
	upperBound->Book(BMAX[1]);
	upperBound->Chapter(books[1][BMAX[1]-1].chapmax);
	upperBound->Verse(books[1][BMAX[1]-1].versemax[upperBound->Chapter()-1]);
	boundSet = false;
}


/******************************************************************************
 * VerseKey::copyFrom - Equates this VerseKey to another VerseKey
 */

void VerseKey::copyFrom(const VerseKey &ikey) {
	SWKey::copyFrom(ikey);

	parse();
}


/******************************************************************************
 * VerseKey::copyFrom - Equates this VerseKey to another SWKey
 */

void VerseKey::copyFrom(const SWKey &ikey) {
	SWKey::copyFrom(ikey);

	parse();
}


/******************************************************************************
 * VerseKey::getText - refreshes keytext before returning if cast to
 *				a (char *) is requested
 */

const char *VerseKey::getText() const {
	freshtext();
	return keytext;
}


const char *VerseKey::getShortText() const {
	static char *stext = 0;
	char buf[2047];
	freshtext();
	if (book < 1) {
		if (testament < 1)
			sprintf(buf, "[ Module Heading ]");
		else sprintf(buf, "[ Testament %d Heading ]", (int)testament);
	}
	else {
		sprintf(buf, "%s %d:%d", books[testament-1][book-1].prefAbbrev, chapter, verse);
	}
	stdstr(&stext, buf);
	return stext;
}


const char *VerseKey::getBookName() const {
	return books[testament-1][book-1].name;
}


const char *VerseKey::getBookAbbrev() const {
	return books[testament-1][book-1].prefAbbrev;
}
/******************************************************************************
 * VerseKey::setPosition(SW_POSITION)	- Positions this key
 *
 * ENT:	p	- position
 *
 * RET:	*this
 */

void VerseKey::setPosition(SW_POSITION p) {
	switch (p) {
	case POS_TOP:
		testament = LowerBound().Testament();
		book      = LowerBound().Book();
		chapter   = LowerBound().Chapter();
		verse     = LowerBound().Verse();
		break;
	case POS_BOTTOM:
		testament = UpperBound().Testament();
		book      = UpperBound().Book();
		chapter   = UpperBound().Chapter();
		verse     = UpperBound().Verse();
		break;
	case POS_MAXVERSE:
		Normalize();
		verse     = books[testament-1][book-1].versemax[chapter-1];
		break;
	case POS_MAXCHAPTER:
		verse     = 1;
		Normalize();
		chapter   = books[testament-1][book-1].chapmax;
		break;
	} 
	Normalize(1);
	Error();	// clear error from normalize
}


/******************************************************************************
 * VerseKey::increment	- Increments key a number of verses
 *
 * ENT:	step	- Number of verses to jump forward
 *
 * RET: *this
 */

void VerseKey::increment(int step) {
	char ierror = 0;
	Index(Index() + step);
	while ((!verse) && (!headings) && (!ierror)) {
		Index(Index() + 1);
		ierror = Error();
	}

	error = (ierror) ? ierror : error;
}


/******************************************************************************
 * VerseKey::decrement	- Decrements key a number of verses
 *
 * ENT:	step	- Number of verses to jump backward
 *
 * RET: *this
 */

void VerseKey::decrement(int step) {
	char ierror = 0;

	Index(Index() - step);
	while ((!verse) && (!headings) && (!ierror)) {
		Index(Index() - 1);
		ierror = Error();
	}
	if ((ierror) && (!headings))
		(*this)++;

	error = (ierror) ? ierror : error;
}


/******************************************************************************
 * VerseKey::Normalize	- checks limits and normalizes if necessary (e.g.
 *				Matthew 29:47 = Mark 2:2).  If last verse is
 *				exceeded, key is set to last Book CH:VS
 * RET: *this
 */

void VerseKey::Normalize(char autocheck)
{
	error = 0;

	if ((autocheck) && (!autonorm))	// only normalize if we were explicitely called or if autonorm is turned on
		return;

	if ((headings) && (!verse))		// this is cheeze and temporary until deciding what actions should be taken.
		return;					// so headings should only be turned on when positioning with Index() or incrementors

	while ((testament < 3) && (testament > 0)) {

		if (book > BMAX[testament-1]) {
			book -= BMAX[testament-1];
			testament++;
			continue;
		}

		if (book < 1) {
			if (--testament > 0) {
				book += BMAX[testament-1];
			}
			continue;
		}

		if (chapter > books[testament-1][book-1].chapmax) {
			chapter -= books[testament-1][book-1].chapmax;
			book++;
			continue;
		}

		if (chapter < 1) {
			if (--book > 0) {
				chapter += books[testament-1][book-1].chapmax;
			}
			else	{
				if (testament > 1) {
					chapter += books[0][BMAX[0]-1].chapmax;
				}
			}
			continue;
		}

		if (verse > books[testament-1][book-1].versemax[chapter-1]) { // -1 because e.g chapter 1 of Matthew is books[1][0].versemax[0]
			verse -= books[testament-1][book-1].versemax[chapter++ - 1];
			continue;
		}

		if (verse < 1) {
			if (--chapter > 0) {
				verse += books[testament-1][book-1].versemax[chapter-1];
			}
			else	{
				if (book > 1) {
					verse += books[testament-1][book-2].versemax[books[testament-1][book-2].chapmax-1];
				}
				else	{
					if (testament > 1) {
						verse += books[0][BMAX[0]-1].versemax[books[0][BMAX[0]-1].chapmax-1];
					}
				}
			}
			continue;
		}

		break;  // If we've made it this far (all failure checks continue) we're ok
	}

	if (testament > 2) {
		testament = 2;
		book      = BMAX[testament-1];
		chapter   = books[testament-1][book-1].chapmax;
		verse     = books[testament-1][book-1].versemax[chapter-1];
		error     = KEYERR_OUTOFBOUNDS;
	}

	if (testament < 1) {
		error     = ((!headings) || (testament < 0) || (book < 0)) ? KEYERR_OUTOFBOUNDS : 0;
		testament = ((headings) ? 0 : 1);
		book      = ((headings) ? 0 : 1);
		chapter   = ((headings) ? 0 : 1);
		verse     = ((headings) ? 0 : 1);
	}
	if (_compare(UpperBound()) > 0) {
		*this = UpperBound();
		error = KEYERR_OUTOFBOUNDS;
	}
	if (_compare(LowerBound()) < 0) {
		*this = LowerBound();
		error = KEYERR_OUTOFBOUNDS;
	}
}


/******************************************************************************
 * VerseKey::Testament - Gets testament
 *
 * RET:	value of testament
 */

char VerseKey::Testament() const
{
	return testament;
}


/******************************************************************************
 * VerseKey::Book - Gets book
 *
 * RET:	value of book
 */

char VerseKey::Book() const
{
	return book;
}


/******************************************************************************
 * VerseKey::Chapter - Gets chapter
 *
 * RET:	value of chapter
 */

int VerseKey::Chapter() const
{
	return chapter;
}


/******************************************************************************
 * VerseKey::Verse - Gets verse
 *
 * RET:	value of verse
 */

int VerseKey::Verse() const
{
	return verse;
}


/******************************************************************************
 * VerseKey::Testament - Sets/gets testament
 *
 * ENT:	itestament - value which to set testament
 *		[MAXPOS(char)] - only get
 *
 * RET:	if unchanged ->          value of testament
 *	if   changed -> previous value of testament
 */

char VerseKey::Testament(char itestament)
{
	char retval = testament;

	if (itestament != MAXPOS(char)) {
		testament = itestament;
		Normalize(1);
	}
	return retval;
}


/******************************************************************************
 * VerseKey::Book - Sets/gets book
 *
 * ENT:	ibook - value which to set book
 *		[MAXPOS(char)] - only get
 *
 * RET:	if unchanged ->          value of book
 *	if   changed -> previous value of book
 */

char VerseKey::Book(char ibook)
{
	char retval = book;

	Chapter(1);
	book = ibook;
	Normalize(1);

	return retval;
}


/******************************************************************************
 * VerseKey::Chapter - Sets/gets chapter
 *
 * ENT:	ichapter - value which to set chapter
 *		[MAXPOS(int)] - only get
 *
 * RET:	if unchanged ->          value of chapter
 *	if   changed -> previous value of chapter
 */

int VerseKey::Chapter(int ichapter)
{
	int retval = chapter;

	Verse(1);
	chapter = ichapter;
	Normalize(1);

	return retval;
}


/******************************************************************************
 * VerseKey::Verse - Sets/gets verse
 *
 * ENT:	iverse - value which to set verse
 *		[MAXPOS(int)] - only get
 *
 * RET:	if unchanged ->          value of verse
 *	if   changed -> previous value of verse
 */

int VerseKey::Verse(int iverse)
{
	int retval = verse;

	verse = iverse;
	Normalize(1);

	return retval;
}


/******************************************************************************
 * VerseKey::AutoNormalize - Sets/gets flag that tells VerseKey to auto-
 *				matically normalize itself when modified
 *
 * ENT:	iautonorm - value which to set autonorm
 *		[MAXPOS(char)] - only get
 *
 * RET:	if unchanged ->          value of autonorm
 *		if   changed -> previous value of autonorm
 */

char VerseKey::AutoNormalize(char iautonorm)
{
	char retval = autonorm;

	if (iautonorm != MAXPOS(char)) {
		autonorm = iautonorm;
		Normalize(1);
	}
	return retval;
}


/******************************************************************************
 * VerseKey::Headings - Sets/gets flag that tells VerseKey to include
 *					chap/book/testmnt/module headings
 *
 * ENT:	iheadings - value which to set headings
 *		[MAXPOS(char)] - only get
 *
 * RET:	if unchanged ->          value of headings
 *		if   changed -> previous value of headings
 */

char VerseKey::Headings(char iheadings)
{
	char retval = headings;

	if (iheadings != MAXPOS(char)) {
		headings = iheadings;
		Normalize(1);
	}
	return retval;
}


/******************************************************************************
 * VerseKey::findindex - binary search to find the index closest, but less
 *						than the given value.
 *
 * ENT:	array	- long * to array to search
 *		size		- number of elements in the array
 *		value	- value to find
 *
 * RET:	the index into the array that is less than but closest to value
 */

int VerseKey::findindex(long *array, int size, long value)
{
	int lbound, ubound, tval;

	lbound = 0;
	ubound = size - 1;
	while ((ubound - lbound) > 1) {
		tval = lbound + (ubound-lbound)/2;
		if (array[tval] <= value)
			lbound = tval;
		else ubound = tval;
	}
	return (array[ubound] <= value) ? ubound : lbound;
}


/******************************************************************************
 * VerseKey::Index - Gets index based upon current verse
 *
 * RET:	offset
 */

long VerseKey::Index() const
{
	long  offset;

	if (!testament) { // if we want module heading
		offset = 0;
		verse  = 0;
	}
	else {
		if (!book)
			chapter = 0;
		if (!chapter)
			verse   = 0;

		offset = offsets[testament-1][0][book];
		offset = offsets[testament-1][1][(int)offset + chapter];
		if (!(offset|verse)) // if we have a testament but nothing else.
			offset = 1;
	}
	return (offset + verse);
}


/******************************************************************************
 * VerseKey::Index - Gets index based upon current verse
 *
 * RET:	offset
 */

long VerseKey::NewIndex() const
{
	static long otMaxIndex = 32300 - 8245;  // total positions - new testament positions
//	static long otMaxIndex = offsets[0][1][(int)offsets[0][0][BMAX[0]] + books[0][BMAX[0]].chapmax];
	return ((testament-1) * otMaxIndex) + Index();
}


/******************************************************************************
 * VerseKey::Index - Sets index based upon current verse
 *
 * ENT:	iindex - value to set index to
 *
 * RET:	offset
 */

long VerseKey::Index(long iindex)
{
	long  offset;

// This is the dirty stuff --------------------------------------------

	if (!testament)
		testament = 1;

	if (iindex < 1) {				// if (-) or module heading
		if (testament < 2) {
			if (iindex < 0) {
				testament = 0;  // previously we changed 0 -> 1
				error     = KEYERR_OUTOFBOUNDS;
			}
			else testament = 0;		// we want module heading
		}
		else {
			testament--;
			iindex = (offsets[testament-1][1][offsize[testament-1][1]-1] + books[testament-1][BMAX[testament-1]-1].versemax[books[testament-1][BMAX[testament-1]-1].chapmax-1]) + iindex; // What a doozy! ((offset of last chapter + number of verses in the last chapter) + iindex)
		}
	}

// --------------------------------------------------------------------


	if (testament) {
		if ((!error) && (iindex)) {
			offset  = findindex(offsets[testament-1][1], offsize[testament-1][1], iindex);
			verse   = iindex - offsets[testament-1][1][offset];
			book    = findindex(offsets[testament-1][0], offsize[testament-1][0], offset);
			chapter = offset - offsets[testament-1][0][VerseKey::book];
			verse   = (chapter) ? verse : 0;  // funny check. if we are index=1 (testmt header) all gets set to 0 exept verse.  Don't know why.  Fix if you figure out.  Think its in the offsets table.
			if (verse) {		// only check if -1 won't give negative
				if (verse > books[testament-1][book-1].versemax[chapter-1]) {
					if (testament > 1) {
						verse = books[testament-1][book-1].versemax[chapter-1];
						error = KEYERR_OUTOFBOUNDS;
					}
					else {
						testament++;
						Index(verse - books[testament-2][book-1].versemax[chapter-1]);
					}
				}
			}
		}
	}
	if (_compare(UpperBound()) > 0) {
		*this = UpperBound();
		error = KEYERR_OUTOFBOUNDS;
	}
	if (_compare(LowerBound()) < 0) {
		*this = LowerBound();
		error = KEYERR_OUTOFBOUNDS;
	}
	return Index();
}


/******************************************************************************
 * VerseKey::compare	- Compares another SWKey object
 *
 * ENT:	ikey - key to compare with this one
 *
 * RET:	>0 if this versekey is greater than compare versekey
 *	<0 <
 *	 0 =
 */

int VerseKey::compare(const SWKey &ikey)
{
	VerseKey ivkey = (const char *)ikey;
	return _compare(ivkey);
}


/******************************************************************************
 * VerseKey::_compare	- Compares another VerseKey object
 *
 * ENT:	ikey - key to compare with this one
 *
 * RET:	>0 if this versekey is greater than compare versekey
 *	<0 <
 *	 0 =
 */

int VerseKey::_compare(const VerseKey &ivkey)
{
	long keyval1 = 0;
	long keyval2 = 0;

	keyval1 += Testament() * 1000000000;
	keyval2 += ivkey.Testament() * 1000000000;
	keyval1 += Book() * 1000000;
	keyval2 += ivkey.Book() * 1000000;
	keyval1 += Chapter() * 1000;
	keyval2 += ivkey.Chapter() * 1000;
	keyval1 += Verse();
	keyval2 += ivkey.Verse();
	keyval1 -= keyval2;
	keyval1 = (keyval1) ? ((keyval1 > 0) ? 1 : -1) /*keyval1/labs(keyval1)*/:0; // -1 | 0 | 1
	return keyval1;
}


const char *VerseKey::getOSISRef() const {
	static char buf[5][254];
	static char loop = 0;

	if (loop > 4)
		loop = 0;

	static char *osisotbooks[] = {
			"Gen","Exod","Lev","Num","Deut","Josh","Judg","Ruth","1Sam","2Sam",
			"1Kgs","2Kgs","1Chr","2Chr","Ezra","Neh","Esth","Job","Ps",
			"Prov",		// added this.  Was not in OSIS spec
			"Eccl",
			"Song","Isa","Jer","Lam","Ezek","Dan","Hos","Joel","Amos","Obad",
			"Jonah","Mic","Nah","Hab","Zeph","Hag","Zech","Mal","Bar","PrAzar",
			"Bel","Sus","1Esd","2Esd","AddEsth","EpJer","Jdt","1Macc","2Macc","3Macc",
			"4Macc","PrMan","Ps151","Sir","Tob","Wis"};
	static char *osisntbooks[] = {
			"Matt","Mark","Luke","John","Acts","Rom","1Cor","2Cor","Gal","Eph",
			"Phil","Col","1Thess","2Thess","1Tim","2Tim","Titus","Phlm","Heb","Jas",
			"1Pet","2Pet","1John","2John","3John","Jude","Rev"};
	static char **osisbooks[] = { osisotbooks, osisntbooks };
	if (Verse())
		sprintf(buf[loop], "%s.%d.%d", osisbooks[Testament()-1][Book()-1], (int)Chapter(), (int)Verse());
	else if (Chapter())
		sprintf(buf[loop], "%s.%d", osisbooks[Testament()-1][Book()-1], (int)Chapter());
	else if (Book())
		sprintf(buf[loop], "%s", osisbooks[Testament()-1][Book()-1]);
	else	sprintf(buf[loop], "");
	return buf[loop++];
}


/******************************************************************************
 * VerseKey::getRangeText - returns parsable range text for this key
 */

const char *VerseKey::getRangeText() const {
	if (isBoundSet()) {
		char buf[1023];
		sprintf(buf, "%s-%s", (const char *)LowerBound(), (const char *)UpperBound());
		stdstr(&rangeText, buf);
	}
	else stdstr(&rangeText, getText());
	return rangeText;
}

SWORD_NAMESPACE_END
