// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: main.cpp 
// Compiler Used: MSVC40, DJGPP 2.7.2.1, GCC 2.7.2.1, HP CPP 10.24
// Produced By: Doug Gaer  
// File Creation Date: 09/18/1997  
// Date Last Modified: 10/15/1998
// Copyright (c) 1997 Douglas M. Gaer
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
The VBD C++ classes are copyright (c) 1997, by Douglas M. Gaer.
All those who put this code or its derivatives in a commercial
product MUST mention this copyright in their documentation for
users of the products in which this code or its derivative
classes are used. Otherwise, you have the freedom to redistribute
verbatim copies of this source code, adapt it to your specific
needs, or improve the code and release your improvements to the
public provided that the modified files carry prominent notices
stating that you changed the files and the date of any change.

THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
THE ENTIRE RISK OF THE QUALITY AND PERFORMANCE OF THIS SOFTWARE
IS WITH YOU. SHOULD ANY ELEMENT OF THIS SOFTWARE PROVE DEFECTIVE,
YOU WILL ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR
CORRECTION.

This is a test program used to test the (P)ersistent base class
in a practical database application.
*/
// ----------------------------------------------------------- // 

#include <fstream.h> // Using ofstream to print to a text file
#include <ctype.h>
#include "grocery.hpp"
#include "terminal.hpp"
#include "config.hpp"
#include "timer.hpp"
#include "strutil.hpp"
#include "asprint.hpp"
#include "pscript.hpp"
#include "version.hpp"
#include "groc_sh.hpp"
#include "dbconfig.hpp"
#include "vbdstats.hpp"

static POD *DB;                // Global database pointer
const int KeyBuf = 25;         // Users input limit per string
const int CBuf = 81;           // Input limit for comments
const char *WildCard = "*";    // Used for wild card searches 
const int retrys = 3;          // Times to retry user input operations
const int StringOffset = 15;   // Display output limit per string
const int IntOffset = 8;       // Display output limit per number

// Define configurable parameters
int CacheSize = 15;  // Memory cache size for the index file
int AdminRights = 1; // Define user privileges 

// Define file access modes used in the application
VBDFile::AccessMode RWMode = VBDFile::READWRITE;
VBDFile::AccessMode ROMode = VBDFile::READONLY;

// Menu menu setup
typedef void (*MMF) (); // Pointer to (M)ain (M)enu (F)unctions
const int MMSelections = 5; // Main menu selections

// Main menu functions
void Exit();
void ISRMenu();     // Insertion/Removal menu
void FindMenu();    // Query menu
void DisplayMenu(); // Display menu
void FileMenu();    // File operations menu

const char *mainmenu[MMSelections] = {
  "(0) - Exit this program",
  "(1) - Add, Remove, or Change items in the database",
  "(2) - Find items in the database",
  "(3) - Display items in the database",
  "(4) - File Operations",
};

MMF MMFunctions[MMSelections] = {
  &Exit,
  &ISRMenu,
  &FindMenu,
  &DisplayMenu,
  &FileMenu,
};

// Insertion/Removal menu setup
typedef void (*ISRMF) (); // Pointer to (A)dd (M)enu (F)unctions 
const char *ISRMTitle = "--------- DATABASE INSERTION/REMOVAL MENU ---------";
const int ISRMSelections = 4; // Insertion/Removal menu selections

// Insertion/Removal menu functions
void Return();
void Add();
void Change();
void Remove();

const char *isrmenu[ISRMSelections] = {
  "(0) - Return to main menu",
  "(1) - Add items to the database",
  "(2) - Change an item in the database",
  "(3) - Remove an item from the database"
};

ISRMF ISRMFunctions[ISRMSelections] = {
  &Return,
  &Add,
  &Change,
  &Remove
};

// Find menu setup
typedef void (*FMF) (); // Pointer to (F)ind (M)enu (F)unctions 
const char *FMTitle = "---------- DATABASE QUERY MENU ----------";
const int FMSelections = 4; // Find menu selections

// Find menu functions
void Return();
void FindByName();
void FindByBrand();
void FindByStore();

const char *findmenu[FMSelections] = {
  "(0) - Return to main menu",
  "(1) - Find objects by Item Name",
  "(2) - Find objects by Brand",
  "(3) - Find objects by Store",
};

FMF FMFunctions[FMSelections] = {
  &Return,
  &FindByName,
  &FindByBrand,
  &FindByStore,
};

// Display menu setup
typedef void (*DMF) (); // Pointer to (D)isplay (M)enu (F)unctions 
const char *DMTitle = "---------- DATABASE DISPLAY MENU ----------";
const int DMSelections = 3; // Display menu selections

// Display menu functions
void DisplayLBL(); // Display all objects line by line
void DisplayAll(); // Display all objects one at a time
  
const char *displaymenu[DMSelections] = {
  "(0) - Return to main menu",
  "(1) - Display all items line by line",
  "(2) - Display all items one a time"
};

DMF DMFunctions[DMSelections] = {
  &Return,
  &DisplayLBL,
  &DisplayAll
};

// File operations menu setup
typedef void (*FOMF) (); // Pointer to (F)ile (M)enu (F)unctions 
const char *FOMTitle = "---------- DATABASE FILE OPERATIONS MENU ----------";
const int FOMSelections = 2; // File operations menu selections

// File operations menu functions
void FileStats();

const char *filemenu[FOMSelections] = {
  "(0) - Return to main menu",
  "(1) - Display Variable Block Database statistics"
};

FOMF FOMFunctions[FOMSelections] = {
  &Return,
  &FileStats
};

// Function prototypes for functions use by all menu functions
void AddObject(Grocery &grocery, Coords *p);
void ChangeObject(Grocery &grocery, Coords *p, int adding = 0);
void DisplayObject(Grocery &grocery, Coords *p);
void LineByLine(Grocery &grocery, Coords *p);
void DisplayString(const char *s);

// Search functions
void FindBy(const char *MemberName, GrocDBItem item);
void GrocDBSearch(Grocery &grocery, GrocDBItem item, UString &str,
		 Coords *p, const char *wildcard = 0);

void MainMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(MMTitle));
    p->SetY(terminal->ScreenCenter(MMSelections));
    terminal->ClearScreen();
    terminal->Write(MMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < MMSelections; i++)
      terminal->Write(mainmenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*MMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*MMFunctions[1]) (); // Call the appropriate menu function
	break;
      case '2':
	(*MMFunctions[2]) (); // Call the appropriate menu function
	break;
      case '3':
	(*MMFunctions[3]) (); // Call the appropriate menu function
	break;
      case '4':
	(*MMFunctions[4]) (); // Call the appropriate menu function
	break;
      default:
	break;
    }
  }
}

void ISRMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(ISRMTitle));
    p->SetY(terminal->ScreenCenter(ISRMSelections));
    terminal->ClearScreen();
    terminal->Write(ISRMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < ISRMSelections; i++)
      terminal->Write(isrmenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*ISRMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*ISRMFunctions[1]) (); // Call the appropriate menu function
	break;
      case '2':
	(*ISRMFunctions[2]) (); // Call the appropriate menu function
	break;
      case '3':
	(*ISRMFunctions[3]) (); // Call the appropriate menu function
	break;
      case '4':
	(*ISRMFunctions[4]) (); // Call the appropriate menu function
	break;
      default:
	break;
    }
  }
}

void FindMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(FMTitle));
    p->SetY(terminal->ScreenCenter(FMSelections));
    terminal->ClearScreen();
    terminal->Write(FMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < FMSelections; i++)
      terminal->Write(findmenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*FMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*FMFunctions[1]) (); // Call the appropriate menu function
	break;
      case '2':
	(*FMFunctions[2]) (); // Call the appropriate menu function
	break;
      case '3':
	(*FMFunctions[3]) (); // Call the appropriate menu function
	break;
      default:
	break;
    }
  }
}

void DisplayMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(DMTitle));
    p->SetY(terminal->ScreenCenter(DMSelections));
    terminal->ClearScreen();
    terminal->Write(DMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < DMSelections; i++)
      terminal->Write(displaymenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*DMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*DMFunctions[1]) (); // Call the appropriate menu function
	break;

      case '2':
	(*DMFunctions[2]) (); // Call the appropriate menu function
	break;
	
      default:
	break;
    }
  }
}

void FileMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(FOMTitle));
    p->SetY(terminal->ScreenCenter(FOMSelections));
    terminal->ClearScreen();
    terminal->Write(FOMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < FOMSelections; i++)
      terminal->Write(filemenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*FOMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*FOMFunctions[1]) (); // Call the appropriate menu function
	break;

      default:
	break;
    }
  }
}

void Exit()
{
  // Nothing to do
}

void Return()
{
  // Nothing to do
}

void DisplayAll()
{
  if(DB->RebuildIndex()) {
    terminal->Write("The index file needs to be rebuilt.", 0, 1);
    terminal->AnyKey(0, 2);
    return;
  }

  Grocery grocery(DB);
  Coords *p = new Coords(0, 0);
  int c;

  terminal->ClearScreen();      
  dllist->Clear();
  
  // Load the index keys into memory
  CachePointer n = DB->Index()->GetRoot();
  rbtree->Clear();
  BtreeWalk(n, LoadKeys, DB->Index());

  BNodeBase *nxt; // BSTree base node pointer

  // Using iterator object to walk throught the rbtree
  TreeWalkerb tw(rbtree->GetRoot(), INORDER);
  
  // Walk through the rbtree using an iterator object
  while((nxt = tw.Next()) != 0) {
    RBNode<InMemCopy> *ptr = (RBNode<InMemCopy> *)nxt;
    dllist->StoreNode(ptr->Data);
  }

  rbtree->Clear(); // Clear the rbtree

  dllistptr = dllist->GetFront();
  while(!dllist->IsHeader(dllistptr)) {
    terminal->ClearScreen();      
    p->SetXY(0, 0); // Reset x and y positions
    grocery.ReadObject(dllistptr->Data.object_address);
    DisplayObject(grocery, p);
    terminal->Write("(C)-Change, (D)-Delete, (X)-Exit",
		    p->XPos(), p->YOffset(2));
    terminal->Write(", Any other key to continue");
    c = terminal->GetChar();
    switch(tolower(c)) {
      case 'c': 
	terminal->ClearScreen();
	p->SetXY(0, 0);
	ChangeObject(grocery, p);
	break;
	
      case 'd': {
	int yn = terminal->YesNo("Are you sure you want to delete (y/n)",
			     p->XPos(), p->YOffset(2));
	if(!yn) break;
	grocery.DeleteObject();
	break;
      }
      
      case 'x':
	dllist->Clear();
	delete p;
	return;

      default:
	break;
    }
    dllistptr = dllistptr->GetNext(); 
  }
}

void ItemBar(Coords *p)
{
  const char *col1 = "Name";
  const char *col2 = "Brand";
  const char *col3 = "Store";
  const char *col4 = "Price";
  const char *col5=  "Qty.";
  const char *col6 = "Line Total";
  
  int x = p->XPos(); 
  terminal->Write(col1, p->XPos(), p->YPos());
  terminal->Write(col2, p->XOffset(StringOffset+1), p->YPos());
  terminal->Write(col3, p->XOffset(StringOffset+1), p->YPos());
  terminal->Write(col4, p->XOffset(StringOffset+1), p->YPos());
  terminal->Write(col5, p->XOffset(IntOffset+1), p->YPos());
  terminal->Write(col6, p->XOffset(IntOffset+1), p->YPos());
  terminal->MoveCursor(0, p->YOffset(1));
  int i = 0;
  while (i++ < terminal->MaxCols()-1) terminal->Write('=');
  p->YOffset(1);
  p->SetX(x);
}

void DisplayLBL()
{
  if(DB->RebuildIndex()) {
    terminal->Write("The index file needs to be rebuilt.", 0, 1);
    terminal->AnyKey(0, 2);
    return;
  }

  Grocery grocery(DB);
  int c;
  unsigned count = 0;
  Coords *p = new Coords(0, 0);
  terminal->ClearScreen();
  ItemBar(p);

  dllist->Clear();
  
  // Load the index keys into memory
  CachePointer n = DB->Index()->GetRoot();
  rbtree->Clear();
  BtreeWalk(n, LoadKeys, DB->Index());

  BNodeBase *nxt; // BSTree base node pointer

  // Using iterator object to walk throught the rbtree
  TreeWalkerb tw(rbtree->GetRoot(), INORDER);
  
  // Walk through the rbtree using an iterator object
  while((nxt = tw.Next()) != 0) {
    RBNode<InMemCopy> *ptr = (RBNode<InMemCopy> *)nxt;
    dllist->StoreNode(ptr->Data);
  }

  rbtree->Clear(); // Clear the rbtree

  dllistptr = dllist->GetFront();
  while(!dllist->IsHeader(dllistptr)) {
    // Display the first object
    grocery.ReadObject(dllistptr->Data.object_address);
    LineByLine(grocery, p);
    p->YOffset(1);
    count++;
    dllistptr = dllistptr->GetNext(); 
    if(count > 5) {
    terminal->Write("(X)-Exit", p->XPos(), p->YOffset(2));
    terminal->Write(", Any other key to continue");
    c = terminal->GetChar();
    switch(tolower(c)) {
      case 'x':
	dllist->Clear();
	delete p; // Free Coords pointer
	return;
	
      default:
	break;
    }
    terminal->ClearScreen();
    p->SetXY(0, 0);
    ItemBar(p);
    count = 0;
    }
  }

  terminal->AnyKey(p->XPos(), p->YOffset(2));
  dllist->Clear();
  delete p; // Free Coords pointer
}

void Remove()
{
  if(!AdminRights) {
    terminal->ClearScreen();
    terminal->Write("You do not have Admin User Privileges", 0, 1);
    terminal->AnyKey(0, 2);
    return;
  }

  terminal->ClearScreen();
  Coords *p = new Coords(0, 0);
  char buf[KeyBuf]; 
  Grocery grocery(DB);

  terminal->Write("Enter the name of the item to delete: ",
		  p->XPos(), p->YPos());
  terminal->GetString(buf);
  grocery.SetName(buf);

  FAU addr = grocery.FindObject();
  
  if(!addr) { 
    terminal->Write("Could not find: ", p->XPos(), p->YOffset(2));
    terminal->Write(buf);
    terminal->AnyKey(p->XPos(), p->YOffset(2));
    delete p; // Free the Coord pointer
    return;
  }
  int yn = terminal->YesNo("Are you sure you want to delete (y/n)",
			   p->XPos(), p->YOffset(2));
  if(!yn) {
    delete p; // Free Coord pointer
    return;
  }

  grocery.DeleteObject();
  
  terminal->Write("Deleted item ", p->XPos(), p->YOffset(2));
  terminal->Write(buf);

  terminal->AnyKey(p->XPos(), p->YOffset(2));
  delete p; // Free Coord pointer
}

void FileStats()
{
  VBDStats(terminal, DB->OpenDatabase());
  VBDStats(terminal, DB->OpenIndexFile());
}

void Add()
{
  if(!AdminRights) {
    terminal->ClearScreen();
    terminal->Write("You do not have Admin User Privileges", 0, 1);
    terminal->AnyKey(0, 2);
    return;
  }

  int r = retrys;
  
  Coords *p = new Coords(0, 0);
  char buf[KeyBuf];
  terminal->ClearScreen();

  while(1) {
    terminal->Write("Enter the name of item to add: ", p->XPos(), p->YPos());
    terminal->GetString(buf);
    if(!*buf) r--; else break;
    if(!r) {
      delete p;
      return;
    }
  }

  Grocery grocery(DB);
  UString key_buf(buf);

  // Remove any leading space from the key name entry
  int offset = key_buf.Find(" ");
  if(offset == 0) key_buf.DeleteAt(offset, 1);
    
  if(key_buf == "") { // Check for a key name valid input
    delete p;
    return;
  }

  grocery.SetName(key_buf);
  FAU addr = grocery.FindObject();
   
  if(addr) { 
    terminal->Write(buf, p->XPos(), p->YOffset(2));
    terminal->Write(" entry already exists."); 
    terminal->AnyKey(p->XPos(), p->YOffset(2));
    delete p; // Free Coords pointer
    return;
  }

  AddObject(grocery, p);
  delete p; // Free Coords pointer
}

void AddObject(Grocery &grocery, Coords *p)
{
  char buf[KeyBuf];
  double price = 0;
  int quantity = 0;
  char purchasing = ' ';
  double total = 0;

  terminal->Write("Enter the brand name: ",
                  p->XPos(), p->YOffset(1));
  terminal->GetString(buf);
  grocery.SetBrand(buf);
  terminal->Write("Enter the name of the store: ",
                  p->XPos(), p->YOffset(1));
  terminal->GetString(buf);
  grocery.SetStore(buf);
  terminal->Write("Enter the item's price: $",
                  p->XPos(), p->YOffset(1));
  price = terminal->GetFloat();
  grocery.SetPrice(price);
  terminal->Write("Enter the quantity: ", p->XPos(), p->YOffset(1));
  quantity = terminal->GetInt();
  grocery.SetQuantity(quantity);
  terminal->Write("Purchasing (y/n): ", p->XPos(), p->YOffset(1));
  if(terminal->GetYesNo()) purchasing = 'Y'; else purchasing = 'N';
  grocery.SetPurchasing(purchasing);
  FLOAT64 LineTotal = grocery.GetPrice() * grocery.GetQuantity();
  grocery.SetLineTotal(LineTotal);
  terminal->Write("Line total = $", p->XPos(), p->YOffset(1));
  terminal->Write(grocery.GetLineTotal());
 
  terminal->Write("(A)-Abort (C)-Change, Any other key to accept entry",
		  p->XPos(), p->YOffset(2));
  int c = terminal->GetChar();
  switch(c) {
    case 'c': case 'C': 
      terminal->ClearScreen();
      p->SetXY(0, 0);
      ChangeObject(grocery, p, 1); 
      break;
    case 'a':
      return;
    default:
      break;
  }

  int find = 0;
  FAU addr = grocery.AddObject(find); // Write the object to the file
  if(!addr) {
    terminal->Write("Could not add item to database",
                    p->XPos(), p->YOffset(2));
    terminal->AnyKey(p->XPos(), p->YOffset(2));
    return;
  }
  
  terminal->Write("Item was added to the database",
                  p->XPos(), p->YOffset(2));

  terminal->Write("Add another (y/n)", p->XPos(), p->YOffset(2));
  if(terminal->GetYesNo()) Add(); else return; 
}

void FindBy(const char *MemberName, GrocDBItem item)
{
  Coords *p = new Coords(0, 0);
  char buf[KeyBuf]; 
  unsigned offset = 0;

  terminal->ClearScreen();
  
  Grocery grocery(DB);

  terminal->Write("Enter complete name or use ", p->XPos(), p->YPos());
  terminal->Write(WildCard);
  terminal->Write(" for a wild card.");
  terminal->Write(MemberName, p->XPos(), p->YOffset(2));
  terminal->Write(" -> ");
  terminal->GetString(buf);
  UString str(buf);
  offset = str.Find(WildCard, offset); // Look for wild card character

  if(DB->RebuildIndex()) {
    terminal->Write("The index file needs to be rebuilt.",
		    p->XPos(), p->YOffset(2));
    terminal->AnyKey(p->XPos(), p->YOffset(1));
    delete p;
    return;
  }
  
  if(offset == UString::NoMatch) { // No wild cards found
    grocery.SetName(buf);
    GrocDBSearch(grocery, item, str, p);
    delete p; // Free the Coord pointer
    return;
  }

  GrocDBSearch(grocery, item, str, p, WildCard);
  delete p; // Free the Coord pointer
}

void FindByName()
{
  FindBy("Item Name", NAME);
}

void FindByBrand()
{
  FindBy("Brand Name", BRAND);
}

void FindByStore()
{
  FindBy("Store Name", STORE);
}

void Change()
{
  if(!AdminRights) {
    terminal->ClearScreen();
    terminal->Write("You do not have Admin User Privileges", 0, 1);
    terminal->AnyKey(0, 2);
    return;
  }

  Coords *p = new Coords(0, 0);
  char buf[KeyBuf];
  
  terminal->ClearScreen();
  terminal->Write("Enter name of item to change: ", p->XPos(), p->YPos());
  terminal->GetString(buf);

  Grocery grocery(DB);
  grocery.SetName(buf);

  FAU addr = grocery.FindObject();
   
  if(!addr) { 
    terminal->Write("Could not find: ", p->XPos(), p->YOffset(2));
    terminal->Write(buf);
    terminal->AnyKey(p->XPos(), p->YOffset(2));
    delete p; // Free Coords pointer
    return;
  }

  ChangeObject(grocery, p);
  delete p; // Free Coords pointer
}

void ChangeObject(Grocery &grocery, Coords *p, int adding)
{
  char buf[KeyBuf];

  // Buffer the object's data to allow changes to be canceled
  Grocery ob;
  ob.Copy(grocery);
  FAU addr;
  
  // Temp buffers used to record changes
  Price pr = 0;
  Quantity qty = 0;
  Purchasing pur = ' ';
  FLOAT64 LineTotal;
  Grocery changed(DB);
  
  char *prompt = "Press the number of your selection >";  
  int c, x, y, x_prev, y_prev, x_prompt, y_prompt;
  x_prev = p->XPos(); y_prev = p->YPos();

  while(1) {
    p->SetXY(x_prev, y_prev);
    terminal->Write("(0) Cancel change", p->XPos(), p->YOffset(2));
    if(adding) {
    terminal->Write("(1) Item's Name  =  ", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetName());
    terminal->ClearLine();
    }
    else {
    terminal->Write("(-) Item's Name  =  ", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetName());
    terminal->ClearLine();
    }
    terminal->Write("(2) Brand        =  ", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetBrand());
    terminal->ClearLine();
    terminal->Write("(3) Store        =  ", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetStore());
    terminal->ClearLine();
    terminal->Write("(4) Price        = $", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetPrice());
    terminal->ClearLine();
    terminal->Write("(5) Quantity     =  ", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetQuantity());
    terminal->ClearLine();
    terminal->Write("(6) Purchasing   =  ", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetPurchasing());
    terminal->ClearLine();
    terminal->Write("(-) Total        =  ", p->XPos(), p->YOffset(1));
    terminal->Write(grocery.GetLineTotal());
    terminal->ClearLine();
    terminal->Write("(A) Accept changes", p->XPos(), p->YOffset(1));

    terminal->Write(prompt, p->XPos(), p->YOffset(2));
    x_prompt = p->XPos() + strlen(prompt);
    y_prompt = p->YPos();
  
    x = p->XPos();
    y = p->YPos() + 2;

    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	grocery.Copy(ob);
	return;

      case '1':
	// User not allowed to change name unless adding a new object
	if(!adding) break; 
	terminal->Write("Enter new name: ", x, y);
	terminal->GetString(buf);
	grocery.SetName(buf);
	if(grocery.FindObject()) {
	  terminal->Write("Item already exists", x, y+1);
	  terminal->AnyKey();
	  terminal->ClearLine(x, y+1);
	  terminal->ClearLine(x, y);
	  terminal->MoveCursor(x_prev, y_prev);
	  break;
	}
	terminal->ClearLine(x, y);
	terminal->MoveCursor(x_prev, y_prev);
	break;

      case '2':
	terminal->Write("Enter new brand: ", x, y);
	terminal->GetString(buf);
	grocery.SetBrand(buf);
	terminal->ClearLine(x, y);
	terminal->MoveCursor(x_prev, y_prev);
	break;

      case '3':
	terminal->Write("Enter new store: ", x, y);
	terminal->GetString(buf);
	grocery.SetStore(buf);
	terminal->ClearLine(x, y);
	terminal->MoveCursor(x_prev, y_prev);
	break;

      case '4':
	terminal->Write("Enter new price: $", x, y);
	pr = terminal->GetFloat();
	grocery.SetPrice(pr);
	terminal->ClearLine(x, y);
	terminal->MoveCursor(x_prev, y_prev);
	LineTotal = grocery.GetPrice() * grocery.GetQuantity();
	grocery.SetLineTotal(LineTotal);
	break;

      case '5':
	terminal->Write("Enter new quantity: ", x, y);
	qty = terminal->GetInt();
	grocery.SetQuantity(qty);
	terminal->ClearLine(x, y);
	terminal->MoveCursor(x_prev, y_prev);
	LineTotal = grocery.GetPrice() * grocery.GetQuantity();
	grocery.SetLineTotal(LineTotal);
	break;

      case '6':
	terminal->Write("Purchasing (y/n): ", x, y);
        if(terminal->GetYesNo()) pur = 'Y'; else pur = 'N'; 
        grocery.SetPurchasing(pur);
	terminal->ClearLine(x, y);
	terminal->MoveCursor(x_prev, y_prev);
	break;

      case '7':
	break;
	
      case 'A': case 'a':
	if(adding) return; // New object, no need to reallocate space
	if(grocery.FullCompare(ob)) return; // The object has not changed
	changed.Copy(grocery);  // Make a copy of the changes

	// Remove the original object 
	grocery.DeleteObject();
	changed.AddObject(0); // Add the changed object back to the database
	return;

      default:
	break;
    }
  }
}

void DisplayObject(Grocery &grocery, Coords *p)
// Display's the complete object with all details.
{
  terminal->Write("Item's Name  =  ", p->XPos(), p->YPos());
  terminal->Write(grocery.GetName());
  terminal->Write("Brand        =  ", p->XPos(), p->YOffset(1));
  terminal->Write(grocery.GetBrand());
  terminal->Write("Store        =  ", p->XPos(), p->YOffset(1));
  terminal->Write(grocery.GetStore());
  terminal->Write("Price        = $", p->XPos(), p->YOffset(1));
  terminal->Write(grocery.GetPrice());
  terminal->Write("Quantity     =  ", p->XPos(), p->YOffset(1));
  terminal->Write(grocery.GetQuantity());
  terminal->Write("Purchasing   =  ", p->XPos(), p->YOffset(1));
  terminal->Write(grocery.GetPurchasing());
  terminal->Write("Line total   =  ", p->XPos(), p->YOffset(1));
  terminal->Write(grocery.GetLineTotal());
}

void DisplayString(const char *s)
{
  int len = strlen(s);
  int offset = StringOffset;
  char *buf = new char[len + 1];
  buf[len] = '\0';
  strcpy(buf, s);
  if(len < StringOffset) offset = len;
  for(int i = 0; i < offset; i++)
    terminal->Write(buf[i]);
  delete[] buf;
}

void LineByLine(Grocery &grocery, Coords *p)
// Display objects line by line with partial details
{
  int x = p->XPos();

  terminal->MoveCursor(p->XPos(), p->YPos());
  DisplayString(grocery.GetName());
  terminal->MoveCursor(p->XOffset(StringOffset+1), p->YPos()); 
  DisplayString(grocery.GetBrand());
  terminal->MoveCursor(p->XOffset(StringOffset+1), p->YPos()); 
  DisplayString(grocery.GetStore());
  terminal->MoveCursor(p->XOffset(StringOffset+1), p->YPos()); 
  terminal->Write(grocery.GetPrice());
  terminal->MoveCursor(p->XOffset(IntOffset+1), p->YPos()); 
  terminal->Write(grocery.GetQuantity());
  terminal->MoveCursor(p->XOffset(IntOffset+1), p->YPos()); 
  terminal->Write(grocery.GetLineTotal());
  int i = 0;
  terminal->MoveCursor(0, p->YOffset(1));
  while (i++ < terminal->MaxCols()-1) terminal->Write('-');
  p->SetX(x);
}

void Version()
{
  printf("\n%s program version %.3f\n", ProgramName, VersionNumber);
  exit(0);
}

void GrocDBSearch(Grocery &grocery, GrocDBItem item, UString &str,
		  Coords *p, const char *wildcard)
{
  terminal->Write("Searching for ", p->XPos(), p->YOffset(2));
  terminal->Write(str.c_str());

  int offset;
  offset = ObjectsFound = 0;

  if(wildcard != 0) {
    while(1) { // Remove the wild card characters from the string
      offset = str.Find(wildcard, offset);
      if (offset == UString::NoMatch) break;
      str.DeleteAt(offset, strlen(wildcard));
      offset++;
    }
  }

  // Clear the data structures
  dllist->Clear();
  rbtree->Clear();

  CachePointer n = DB->Index()->GetRoot();

  // Ensure that the memory buffers and the file data
  // stay in sync during multiple file access.
  DB->Index()->TestTree();

  if(wildcard != 0)
    BtreeSearch(n, BtreeNodeSearch, item, grocery, str, 1);
  else
    BtreeSearch(n, BtreeNodeSearch, item, grocery, str);

  if(ObjectsFound == 0) {
    terminal->Write("No matches found.", p->XPos(), p->YOffset(2));
    terminal->AnyKey(p->XPos(), p->YOffset(1));
    return;
  }

  if(ObjectsFound > 1) { // Found multiple matches
    terminal->Write("Found ", p->XPos(), p->YOffset(2));
    terminal->Write(ObjectsFound);
    terminal->Write(" matching.");
    terminal->AnyKey(p->XPos(), p->YOffset(1));
  }
  else {
    terminal->Write("Found matching entry for:  ", p->XPos(), p->YOffset(2));
    terminal->Write(str.c_str());
    terminal->AnyKey(p->XPos(), p->YOffset(1));
  }

  BNodeBase *nxt; // BSTree base node pointer

  // Using iterator object to walk throught the rbtree
  TreeWalkerb tw(rbtree->GetRoot(), INORDER);
  
  // Walk through the rbtree using an iterator object
  while((nxt = tw.Next()) != 0) {
    RBNode<InMemCopy> *ptr = (RBNode<InMemCopy> *)nxt;
    dllist->StoreNode(ptr->Data);
  }

  rbtree->Clear(); // Clear the rbtree

  // Display all the matches
  dllistptr = dllist->GetFront();
  while(!dllist->IsHeader(dllistptr)) {
    terminal->ClearScreen();
    p->SetXY(0, 0);
    grocery.ReadObject(dllistptr->Data.object_address);
    DisplayObject(grocery, p);
    terminal->Write("(C)-Change, (D)-Delete, (X)-Exit",
		    p->XPos(), p->YOffset(2));
    terminal->Write(", Any other key to continue");
    int c = terminal->GetChar();
    switch(c) {
      case 'c': case 'C': 
	terminal->ClearScreen();
	p->SetXY(0, 0);
	grocery.ReadObject(dllistptr->Data.object_address);
        ChangeObject(grocery, p);
	break;
	
      case 'd': case 'D': {
	int yn = terminal->YesNo("Are you sure you want to delete (y/n)",
				 p->XPos(), p->YOffset(2));
	if(!yn) break;
	grocery.ReadObject(dllistptr->Data.object_address);
        grocery.DeleteObject();
	break;
      }
      
      case 'x': case 'X':
	dllist->Clear(); // Free dllist memory
	return;

      default:
	break;
    }
    dllistptr = dllistptr->GetNext();
  }
  dllist->Clear(); // Free dllist memory
}

int main(int argc, char **argv)
{
  // Display the program version information and exit the program
  if(argc >= 2) {
    if(strcmp(argv[1], "version") == 0) Version();
  }

  char *FileName;
  char *CurrentCfgFile;
  char *CfgFile;

  Config *CfgData = new Config;
  char *AdminUser = 0;
  
  // Look for CfgFile name in environment
  if((CurrentCfgFile = getenv("GDBCFG")) == 0)
    CfgFile = (char *)DefaultCfgFile;
  else
    CfgFile = CurrentCfgFile;

  // Use default file of CFG file name if no file is specifed on 
  if(argc < 2) {
    int FileStatus = CfgData->Load((char *)CfgFile);
    if(!FileStatus)
      FileName = (char *)DefaultDBFileName;
    else {
      FileName = CfgData->GetStrValue("DBFileName");
      AdminUser = CfgData->GetStrValue("AdminUser");
      if(!FileName) {
	printf("\nDBFileName section missing in config file: %s\n", CfgFile);
	exit(0);
      }
    }
  }
  else {
    FileName = argv[1];
  }

  if(!AdminUser) {
    AdminRights = 1;
  }
  else {
    int result = strcmp(AdminUser, "FALSE");
    if(result == 0) {
      AdminRights = 0;
    }
    else {
      AdminRights = 1;
    }
  }

  CfgData->UnLoad(); // Unload the Config file from memory

  // Initialize global database pointer
  // NOTE: Index file and data file will share the same file name
  // with different file extensions. The data file will have a 
  // .pod extension and the index file will have a .btx extension.
  if(AdminRights == 1) 
    DB = new POD(FileName, RWMode, 1, CacheSize);
  else
    DB = new POD(FileName, ROMode, 1, CacheSize);

  terminal->init();
  MainMenu();
  terminal->finish();
  delete DB;
  return 0;
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
