// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- //
// C++ Source Code File Name: testprog.cpp 
// Compiler Used: MSVC, BCC32, GCC, HPUX aCC, SOLARIS CC
// Produced By: glNET Software
// File Creation Date: 09/18/1997
// Date Last Modified: 08/22/2001
// Copyright (c) 2001 glNET Software
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
 
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  
USA

This is a test program use to test the Persistent base class.

This example uses a fixed string as the key type and incorporates
an object ID and class ID into the data key. NOTE: The use a
fixed character string as a key type is intended to keep this
example as simple as possible and used for benchmarking
performance in the worst case scenario. Character strings are
hard to manipulate (in memory and on disk) and using the strcmp()
function is extremely slow.  

The object ID in this example is used to uniquely identify each
object in the B-tree index and record the object's location in
the data file. Using a data file address to ID objects guarantees
that each object will be unique allowing you to insert duplicate
key names. 

The class ID in this example is an optional parameter used in
place of RTTI. This optional feature allows you determine the
object type without entering the object. When the Persistent base
class was originally developed in 1997 RTTI was not supported by
some C++ compilers so this feature was implemented to allowed
applications obtain type information and used by the Persistent 
base class to store objects from multiple classes in a single
database.
*/
// ----------------------------------------------------------- //   
#include <iostream.h>
#include "grocery.h"

void PausePrg()
// Function used to pause the program.
{
  cout << endl;
  cout << "Press enter to continue..." << endl;
  cin.get();
}

void ClearInputStream(istream &s)
// Function used to clear the input stream.
{
  char c;
  s.clear();
  while(s.get(c) && c != '\n') { ; }
}

int Quit()
{
  cout << "Exiting..." << endl;
  return 0;
}

char *InputData()
// Function used to read a string from the input stream.
// Returns a null terminated string or a null value if an
// error occurs. NOTE: The calling function must free the
// memory allocated for this string.
{
  char buf[MAX_NAME_LENGTH];
  for(unsigned i = 0; i < MAX_NAME_LENGTH; i++) buf[i] = 0;
  cout << "Item name: ";
  cin.getline(buf, sizeof(buf));
  char *s = new char[strlen(buf)+1]; // Account for null terminator
  if(!s) return 0;
  strcpy(s, buf);
  return s;
}

void DisplayItem(Grocery &grocery, int full = 1)
// Function used to print a grocery list object to the stdout.
{
  cout << endl;
  cout << "Item's Name  = " << grocery.Name() << endl;
  if(full) {
    cout << "Stock Number = " << grocery.StockNumber() << endl;
    cout.setf(ios::showpoint | ios::fixed);
    cout.precision(2);
    cout << "Price        = $" << grocery.Price() << endl;
    cout << endl;
  }
}

void AddItem(const POD *DB)
// Function used to add an object to the database.
{
  char *buf = InputData();

  Grocery grocery(DB);
  grocery.SetName((Name_t)buf);
  int exists = grocery.FindObject();

  if(exists) {
    cout << "Item: " << buf << " already exists at address "
	 << grocery.ObjectAddress() << endl;
    delete buf;
    return;
  }

  int stock_number;
  double price;

  cout << "Enter the items's stock number: ";
  cin >> stock_number;
  if(cin) {
    grocery.SetStockNumber(stock_number);
    cout << "Enter the items's price:       $";
    cin >> price;
  } 
  else {
    cout << "Invalid entry. Object not added!" << endl;
    delete buf;
    return;
  }
  if(cin) {
    grocery.SetPrice(price);
  }
  else {
    cout << "Invalid entry. Object not added!" << endl;
    delete buf;
    return;
  }
  
  ClearInputStream(cin);
  
  if(!grocery.WriteObject()) {
    cout << "Could not add object to the database" << endl;
  }
  delete buf;
}

void DeleteItem(const POD *DB)
// Function used to delete an object from the database.
{
  char *buf = InputData();

  Grocery grocery(DB);
  grocery.SetName((Name_t)buf);
  if(!grocery.DeleteObject()) {
    cout << "Could not find item: " << buf << " in the database" << endl;
    delete buf;
    return;
  }

  cout << "Deleted item: " << buf << " at address "
       << grocery.ObjectAddress() << endl;
  delete buf;
}

void ListInOrder(const POD *DB, unsigned index_number)
// List the contents of the database using the index file.
{
  int i = 0;
  Grocery grocery(DB);
  GroceryKey key, compare_key;
  gxBtree *btx = DB->Index(index_number);

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

  // Walk through the tree starting at the first key
  if(btx->FindFirst(key)) {
    if(!grocery.ReadObject(key.ObjectID())) {
      cout << "Error reading the object" << endl;
      return;
    }
    DisplayItem(grocery);
    grocery.ClearName();
    
    while(btx->FindNext(key, compare_key)) {
      if(!grocery.ReadObject(key.ObjectID())) {
	cout << "Error reading the object" << endl;
	return;
      }
      DisplayItem(grocery);
      grocery.ClearName();
    }
  }
}

void ListInOrder(const POD *DB)
// List contents of the database without using the index file.
{
  FAU addr = (FAU)0;
  Grocery grocery(DB);
  unsigned count = 0;
  ClearInputStream(cin); // Clear input stream
  while(1) {
    addr = DB->OpenDataFile()->FindFirstObject(addr);
    if(!addr) break;
    if(!grocery.ReadObject(addr)) {
      cout << "Error reading the object" << endl;
      return;
    }
    DisplayItem(grocery);
    grocery.ClearName();
    count++;
    if(count == 2) {
      PausePrg();
      count = 0;
    }
  }
}

void FindItem(const POD *DB)
// Function used to find an object in the database.
{
  char *buf = InputData();

  Grocery grocery(DB);
  grocery.SetName((Name_t)buf);
  if(!grocery.FindObject()) {
    cout << "Could not find item: " << grocery.Name()
	 << " in the database" << endl;
    delete buf;
    return;
  }

  cout << "Found item: " << buf << " at address "
       << grocery.ObjectAddress() << endl;
  delete buf;
  
  if(!grocery.ReadObject()) {
    cout << "Error reading the object" << endl;
    grocery.ClearName(); // Prevent memory leak
    return;
  }
  DisplayItem(grocery);
  grocery.ClearName(); // Prevent memory leak
}

void BuildDatabase(const POD *DB)
// Function used to build a test database.
{
  const int NUM_OBJECTS = 1000;
  int i;
  Grocery grocery(DB);
  char name[MAX_NAME_LENGTH];
  int stock_number = 2000;
  double price = 0.95;

  cout << "Adding " << NUM_OBJECTS << " objects to the database..." << endl;
  for(i = 0; i < NUM_OBJECTS; i++) {
    sprintf(name, "Item %i", i);

    // Set the item's name, stock number, price
    grocery.SetName((Name_t)name);
    grocery.SetStockNumber(StockNumber_t(stock_number+i));
    grocery.SetPrice(Price_t(price+i));

    // Write the grocery list item to the database
    if(!grocery.WriteObject()) {
      cout << "Could not add object number " << i << " to the database"
	   << endl;
      return;
    }
  }
}
 
void Menu()
// Console based user menu.
{
  cout << "(A, a)    Add object to the database" << endl;
  cout << "(B, b)    Build a test database" << endl;
  cout << "(D, d)    Delete object from the database" << endl;
  cout << "(F, f)    Find item by name" << endl;          
  cout << "(L, l)    List without using index file" << endl; 
  cout << "(H, h, ?) Help (prints this menu)" << endl;
  cout << "(I, i)    List using the index file" << endl;
  cout << "(Q, q)    Quit" << endl;
  cout << "(X, x)    Compare the index file to the data file" << endl;
  cout << "(Y, y)    Rebuild the index file" << endl;
}

void Compare(const POD *DB)
// Function used to compare the contents of the data file to the
// index file.
{
  if(!DB->UsingIndex()) {
    cout << "This database is not using the indexing sub-system" << endl;
    return;
  }
  
  Grocery grocery(DB);
  cout << endl;
  cout << "Comparing the index file to the data file..." << endl;
  int rv = grocery.CompareIndex(0);
  if(!rv) {
    cout << "The index file does not match the data file!" << endl;
    cout << "The index file needs to be rebuilt." << endl;
    cout << endl;
    return;
  }

  cout << "The index file checks good" << endl;
  cout << endl;
}

void Rebuild(const POD *DB, const char *fname)
// Function used to rebuild the index file if the index file
// entries no longer match the data file entries.
{
  if(!DB->UsingIndex()) return;
  
  Grocery grocery(DB);
  cout << endl;
  cout << "Rebuilding the index file..." << endl;
  // Rebuild index number 0 with room for 1 tree header
  int rv = grocery.RebuildIndexFile(fname, 0, 1, POD_DEFAULT_ORDER);
  if(!rv) {
    cout << "The index file was not rebuilt!" << endl;
    cout << endl;
    return;
  }

  cout << "The index file was rebuilt." << endl;
  cout << "A new index file named " << fname << " was created.";
  cout << endl;
}

int main()
{
  const char *data_file = "grocery.gxd";
  const char *index_file = "grocery.btx";
  GroceryKey key_type;

  // Create or open an existing database using a single index file
  POD pod;
  gxDatabaseError err = pod.Open(data_file, index_file, key_type,
				 POD_DEFAULT_ORDER);
  // Test for any errors
  if(err != gxDBASE_NO_ERROR) {
    cout << gxDatabaseExceptionMessage(err) << endl;
    return 1;
  }
  
  char c;
  int rv = 1;
  Menu();
  
  while(rv) {
    if (!cin) { 
      ClearInputStream(cin);
      if (!cin) { 
	cout << "Input stream error" << endl;
	return 0;
      }
    }
    cout << '>';
    cin >> c;
    if (!cin) continue;
    switch(c) {
      case 'a' : case 'A' : ClearInputStream(cin); AddItem(&pod); break;
      case 'b' : case 'B' : BuildDatabase(&pod); break;
      case 'd' : case 'D' : ClearInputStream(cin); DeleteItem(&pod); break;
      case 'f' : case 'F' : ClearInputStream(cin); FindItem(&pod); break;
      case 'l' : case 'L' : ListInOrder(&pod); break;	 
      case 'i' : case 'I' : ListInOrder(&pod, 0); break;
      case 'h' : case 'H' : case '?' : Menu(); break;
      case 'q' : case 'Q' : rv = Quit(); break;
      case 'x' : case 'X' : Compare(&pod); break;
      case 'y' : case 'Y' : Rebuild(&pod, "newindex.btx"); break;

      default:
        cout << "Unrecognized command" << endl;
    }
  }

  return 0;
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //