// ---------------- btree.cpp

#include <string.h>
#include "parody.h"

// ---------- constructor to open a btree
Btree::Btree(Index& ndx, Key *ky) :
				index(ndx), LinkedListEntry(NULL)
{
	currnode = 0;
	trnode = NULL;
	nullkey = ky->Make();
	nullkey->Key::operator=(*ky);
	NodeNbr nd = 0;
	int clno = ky->classid;

	if (!index.NewFile())	{
		Node node(&index, 1);
		// ------- locate the class header
		while (clno)	{
			NodeNbr nx = node.NextNode();
			if (nx == 0)	{
				// need to link this one with new one(s) added
				nd = index.NewNode();
				node.SetNextNode(nd);
				break;
			}
			node = Node(&index, nx);
			--clno;
		}
		if (clno == 0)	{
			// ------ position to the tree header
			index.Nfile().seekg((long)
				(ky->indexno * sizeof(TreeHeader)), ios::cur);
			headeraddr = index.Nfile().tellp();
			// ------- read the btree header
			index.Nfile().read((char *)
						&header, sizeof(TreeHeader));
			return;
		}
	}
	// ------- build the class headers for new node or class
	index.ResetNewFile();
	if (nd == 0)
		nd = index.NewNode();
	else
		--clno;
	TreeHeader th;
	const int treect = nodedatalength / sizeof(TreeHeader);
	while (clno)	{
		Node node(&index, nd);
		for (int i = 0; i < treect; i++)
			index.Nfile().write((char*)&th,sizeof(TreeHeader));
		nd = index.NewNode();
		node.SetNextNode(nd);
		--clno;
	}

	Node node(&index, nd);
	// ------ position to the tree header
	for (int i = 0; i < ky->indexno; i++)
		index.Nfile().write((char *)&th, sizeof(TreeHeader));
	headeraddr = index.Nfile().tellp();
	// ------- write the btree header
	index.Nfile().write((char *)&header, sizeof(TreeHeader));
	// ------- pad out the rest of the node
	for (i++; i < treect; i++)
		index.Nfile().write((char *)&th, sizeof(TreeHeader));
	// ----- pad any residual node space
	int residual = nodedatalength-treect*sizeof(TreeHeader);
	if (residual)	{
		char *residue = new char[residual];
		memset(residue, 0, residual);
		index.Nfile().write(residue, residual);
		delete residue;
	}
	node.MarkNodeChanged();
}

// ---------- destructor for a btree
Btree::~Btree()
{
	index.Nfile().seekg(headeraddr);
	index.Nfile().write((char *)&header, sizeof(TreeHeader));
	delete nullkey;
}

// ---------------- insert a key into a btree
void Btree::Insert(void *keypointer)
{
	// ---- don't insert duplicate keys
	if (!Find(keypointer))	{

		Key *newkey = ((Key *)keypointer)->Make();
		*newkey = *(Key *)keypointer;

		NodeNbr rootnode = 0, leftnode = 0, rightnode = 0;
		Bool RootisLeaf = True;

		Bool done = False;
		// -------- insert key into btree
		while (currnode)	{
			int em = trnode->m();
			trnode->Insert(newkey);
								// first insertion is into leaf
								// if split, subsequent insertions
								// are into parents (non-leaves)
			if (!trnode->header.isleaf)
				trnode->currkey->lowernode = rightnode;

			done = (Bool) (trnode->header.keycount <= em);
			if (!done)
				// ---- node is full, 
				//      try to redistribute keys among siblings
				done = trnode->Redistribute(
							trnode->header.leftsibling);
			if (!done)
				done = trnode->Redistribute(
							trnode->header.rightsibling);

			if (done)
				break;
			// ---- cannot redistribute filled node, split it
			RootisLeaf = False;

			rightnode = index.NewNode();
			leftnode = currnode;

			TNode right(this, rightnode);
			right.SetNodeNbr(rightnode);
			right.MarkNodeChanged();

			// --- establish sibling and parent relationships 
			//     between current node and new right sibling
			right.header.rightsibling =
						trnode->header.rightsibling;
			trnode->header.rightsibling = rightnode;
			right.header.leftsibling = currnode;
			right.header.parent = trnode->header.parent;

			// ----- if the current node is a leaf, 
			//        so is the new sibling
			right.header.isleaf = trnode->header.isleaf;

			// ----- compute new key counts for the two nodes
			trnode->header.keycount = (em + 1) / 2;
			right.header.keycount = em-trnode->header.keycount;

			// ------ locate the middle key in the current node
			Key *middlekey = (Key *)
				(trnode->keys.FirstListEntry());
			for (int i = 0; i < trnode->header.keycount; i++)
				middlekey = (Key *) middlekey->NextListEntry();

			// ---- set the pointer to keys less than 
			//      those in new node
			if (!right.header.isleaf)
				right.header.lowernode = middlekey->lowernode;

			// ----- point to the keys to move (1 past middle)
			Key *movekey = (Key *) middlekey->NextListEntry();

			// ----- middle key inserts into parent
			middlekey->DeleteListEntry();
			*newkey = *middlekey;
			delete middlekey;

			// ---- move keys from current to new right node
			for (i = 0; i < right.header.keycount; i++)	{
				Key *nkey = (Key *) movekey->NextListEntry();
				movekey->DeleteListEntry();
				movekey->AppendListEntry(&right.keys);
				movekey = nkey;
			}

			// ---- prepare to insert key 
			//      into parent of split nodes
			currnode = trnode->header.parent;
			if (!currnode)	{
				// ---- no parent node, splitting the root node
				rootnode = index.NewNode();
				right.header.parent = rootnode;
				trnode->header.parent = rootnode;
			}

			// --- the former right sibling of the current node
			//     is now the right sibling of the split node
			//     and must record the new node as left sibling

			if (right.header.rightsibling)	{
				TNode farright(this, right.header.rightsibling);
				farright.header.leftsibling = rightnode;
				farright.MarkNodeChanged();
			}

			// --- children of the new split node point to
			//     the current split node as parent. They must
			//	    be adopted by the new split node

			if (!right.header.isleaf)
				right.Adoption();

			// ----- if splitting other than root, read parent
			//       position currkey to key where split node
			//       key will be inserted

			if (currnode)	{
				delete trnode;	// writes the split node to disk
				// --- get the parent of the split nodes
				trnode = new TNode(this, currnode);
				// -- position currkey where new key will insert
				trnode->SearchNode(newkey);
			}
		}

		if (!done)	{
			// ------ new root node ------ */
			delete trnode;
			if (rootnode == 0)
				rootnode = index.NewNode();
			trnode = new TNode(this, rootnode);
			trnode->header.isleaf = RootisLeaf;
			currnode = header.rootnode = rootnode;
			trnode->SetNodeNbr(rootnode);
			trnode->Insert(newkey);
			trnode->header.parent = 0;
			trnode->header.keycount = 1;
			if (!RootisLeaf)    {
				trnode->header.lowernode = leftnode;
				trnode->currkey->lowernode = rightnode;
			}
			trnode->MarkNodeChanged();
		}
		delete newkey;
	}
	delete trnode;
	trnode = NULL;
}

// ---------------- find a key in a btree
Bool Btree::Find(void *keypointer)
{
	currnode = header.rootnode;
	while (currnode)	{
	  	delete trnode;
   	   	trnode = new TNode(this, currnode);

		if (trnode->SearchNode((Key *)keypointer))	{
			// ---- search key is equal to a key in the node
			((Key*)keypointer)->fileaddr =
						trnode->currkey->fileaddr;
			return True;
		}
		if (trnode->header.isleaf)
			break;
		if (trnode->currkey == trnode->keys.FirstListEntry())
			// ---- search key is < lowest key in node
			currnode = trnode->header.lowernode;
		else if (trnode->currkey)
			// --- search key is < current key in node
			currnode = ((Key *)
				(trnode->currkey->PrevListEntry()))->lowernode;
		else
			// --- search key is > highest key in node
			currnode =
			((Key *)(trnode->keys.LastListEntry()))->lowernode;
	}
	return False;
}

// ---------------- delete a key from a btree
void Btree::Delete(void *keypointer)
{
	if (Find(keypointer))	{
		if (!trnode->header.isleaf)	{

			// --- if not found in leaf node, go down to leaf
			TNode *leaf =
				new TNode(this, trnode->currkey->lowernode);
			while (!leaf->header.isleaf)	{
				NodeNbr lf = leaf->header.lowernode;
				delete leaf;
				leaf = new TNode(this, lf);
			}

			// ---- Move the left-most key from the leaf
			//		to where deleted key was in higher node
			Key *movekey = (Key *) leaf->keys.FirstListEntry();
			movekey->DeleteListEntry();
			leaf->header.keycount--;
			leaf->MarkNodeChanged();

			movekey->InsertListEntry(
					trnode->currkey, &trnode->keys);
			movekey->lowernode = trnode->currkey->lowernode;
			trnode->currkey->DeleteListEntry();
			delete trnode->currkey;
			trnode->MarkNodeChanged();
			delete trnode;

			trnode = leaf;
			trnode->currkey =
				(Key *) trnode->keys.FirstListEntry();
			currnode = trnode->GetNodeNbr();
		}
		else 	{
			// ---- delete the key from the node
			trnode->currkey->DeleteListEntry();
			delete trnode->currkey;
			trnode->header.keycount--;
			trnode->MarkNodeChanged();
			if (trnode->header.keycount == 0)
				header.rootnode = 0;
		}
		// ---- if the node shrinks to half capacity,
		//      try to combine it with a sibling node
		while (trnode->header.keycount > 0 &&
					trnode->header.keycount <= trnode->m() / 2) {
			if (trnode->header.rightsibling)	{
				TNode *right =
					new TNode(this, trnode->header.rightsibling);
				if (trnode->Implode(*right))	{
					delete right;
					NodeNbr parent = trnode->header.parent;
					if (parent == 0)	{
						header.rootnode = trnode->GetNodeNbr();
						break;
					}
					delete trnode;
					trnode = new TNode(this, parent);
					continue;
				}
				delete right;
			}
			if (trnode->header.leftsibling)	{
				TNode *left =
					new TNode(this, trnode->header.leftsibling);
				if (left->Implode(*trnode))	{
					delete trnode;
					NodeNbr parent = left->header.parent;
					if (parent == 0)	{
						header.rootnode = left->GetNodeNbr();
						trnode = left;
						break;
					}
					delete left;
					trnode = new TNode(this, parent);
					continue;
				}
				delete left;
			}

			// --- could not combine with either sibling, 
			//     try to redistribute
			if (!trnode->Redistribute(
						trnode->header.leftsibling))
				trnode->Redistribute(
						trnode->header.rightsibling);
			break;
		}
	}
	delete trnode;
	trnode = NULL;
}

// ------ return the address of the current key
Key *Btree::Current()
{
	if (trnode == NULL)
		return NULL;
	return trnode->currkey;
}

// ------ return the address of the first key
Key *Btree::First()
{
	currnode = header.rootnode;
	if (currnode)	{
	  	delete trnode;
   	   	trnode = new TNode(this, currnode);
		while (!trnode->header.isleaf)	{
			currnode = trnode->header.lowernode;
			delete trnode;
			trnode = new TNode(this, currnode);
		}
		trnode->currkey =
			(Key *) (trnode->keys.FirstListEntry());
	}
	return Current();
}

// ------ return the address of the last key
Key *Btree::Last()
{
	currnode = header.rootnode;
	if (currnode)	{
	  	delete trnode;
   	   	trnode = new TNode(this, currnode);
		while (!trnode->header.isleaf)	{
			currnode =
				((Key *)(trnode->keys.LastListEntry()))->
						lowernode;
			delete trnode;
			trnode = new TNode(this, currnode);
		}
		trnode->currkey =
			(Key *) (trnode->keys.LastListEntry());
	}
	return Current();
}

// ------ return the address of the next key
Key *Btree::Next()
{
	if (trnode == NULL || trnode->currkey == NULL)
		return First();
	if (!trnode->header.isleaf)	{
		// --- current key is not in a leaf
		currnode = trnode->currkey->lowernode;
	  	delete trnode;
   	   	trnode = new TNode(this, currnode);
		// ----- go down to the leaf
		while (!trnode->header.isleaf)	{
			currnode = trnode->header.lowernode;
		  	delete trnode;
   		   	trnode = new TNode(this, currnode);
		}
		// ---- use the first key in the leaf as the next one
		trnode->currkey =
			(Key *) (trnode->keys.FirstListEntry());
	}
	else	{
		// ------ current key is in a leaf
		Key *thiskey = nullkey->Make();
		*thiskey = *(trnode->currkey);

		// ----- point to the next key in the leaf
		trnode->currkey =
			(Key *) (trnode->currkey->NextListEntry());
		while (trnode->currkey == NULL &&
						currnode != header.rootnode)	{
			// --- current key was the last one in the leaf
			TNode pnode(this, trnode->Parent());
			pnode.SearchNode(thiskey);
			currnode = pnode.GetNodeNbr();
			*trnode = pnode;
		}
		delete thiskey;
	}
	return Current();
}

// ------ return the address of the previous key
Key *Btree::Previous()
{
	if (trnode == NULL || trnode->currkey == NULL)
		return Last();
	if (!trnode->header.isleaf)	{
		// --- current key is not in a leaf
		Key *ky = (Key *) (trnode->currkey->PrevListEntry());
		if (ky != NULL)
			currnode = ky->lowernode;
		else
			currnode = trnode->header.lowernode;
	  	delete trnode;
   	   	trnode = new TNode(this, currnode);
		// ----- go down to the leaf
		while (!trnode->header.isleaf)	{
			currnode =
				((Key *)
				(trnode->keys.LastListEntry()))->lowernode;
		  	delete trnode;
   		   	trnode = new TNode(this, currnode);
		}
		// ---- use the last key in the leaf as the next one
		trnode->currkey =
			(Key *) (trnode->keys.LastListEntry());
	}
	else	{
		// ------ current key is in a leaf
		Key *thiskey = nullkey->Make();
		*thiskey = *(trnode->currkey);

		// ----- point to the previous key in the leaf
		trnode->currkey =
			(Key *) (trnode->currkey->PrevListEntry());
		while (trnode->currkey == NULL &&
						currnode != header.rootnode)	{
			// --- current key was the first one in the leaf
			TNode pnode(this, trnode->Parent());
			pnode.SearchNode(thiskey);
			if (pnode.currkey == NULL)
				pnode.currkey =
					(Key *) pnode.keys.LastListEntry();
			else 
				pnode.currkey =
				(Key *) (pnode.currkey->PrevListEntry());
			currnode = pnode.GetNodeNbr();
			*trnode = pnode;
		}
		delete thiskey;
	}
	return Current();
}

