    // ------------------------------- //
    // -------- 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: 05/25/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.
    */
    // ----------------------------------------------------------- //
    #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 --------- //
    // ------------------------------- //