    // ------------------------------- //
    // -------- Start of File -------- //
    // ------------------------------- //
    // ----------------------------------------------------------- //
    // C++ Source Code File Name: testprog.cpp
    // Compiler Used: MSVC, BCC32, GCC, HPUX aCC, SOLARIS CC
    // Produced By: DataReel Software Development Team
    // File Creation Date: 09/18/1997
    // Date Last Modified: 06/17/2016
    // Copyright (c) 2001-2025 DataReel Software Development
    // ----------------------------------------------------------- //
    // ------------- 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.

    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 allow
    applications to obtain type information and used by the Persistent
    base class to store objects from multiple classes in a single
    database.
    */
    // ----------------------------------------------------------- //
    #include "gxdlcode.h"

    #if defined (__USE_ANSI_CPP__) // Use the ANSI Standard C++ library
    #include <iostream>
    using namespace std; // Use unqualified names for Standard C++ library
    #else // Use the old iostream library by default
    #include <iostream.h>
    #endif // __USE_ANSI_CPP__

    #include "grocery.h"

    void PausePrg()
    // Function used to pause the program.
    {
      cout << "\n";
      cout << "Press enter to continue..." << "\n";
      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..." << "\n";
      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 << "\n";
      cout << "Item's Name  = " << grocery.Name() << "\n";
      if(full) {
        cout << "Stock Number = " << grocery.StockNumber() << "\n";
        cout.setf(ios::showpoint | ios::fixed);
        cout.precision(2);
        cout << "Price        = $" << grocery.Price() << "\n";
        cout << "\n";
      }
    }

    void AddItem(POD *DB)
    // Function used to add an object to the database.
    {
      // Ensure that the in memory buffers and the file data
      // stay in sync during multiple file access.
      DB->TestDatabase();

      char *buf = InputData();

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

      if(exists) {
        cout << "Item: " << buf << " already exists" << "\n";
        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!" << "\n";
        delete buf;
        return;
      }
      if(cin) {
        grocery.SetPrice(price);
      }
      else {
        cout << "Invalid entry. Object not added!" << "\n";
        delete buf;
        return;
      }

      ClearInputStream(cin);

      if(!grocery.WriteObject()) {
        cout << "Could not add object to the database" << "\n";
      }
      delete buf;

      DB->Flush(); // Flushing the database buffers following manual insertion
    }

    void DeleteItem(POD *DB)
    // Function used to delete an object from the database.
    {
      // Ensure that the in memory buffers and the file data
      // stay in sync during multiple file access.
      DB->TestDatabase();

      char *buf = InputData();

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

      cout << "Deleted item: " << buf << "\n";
      delete buf;

      DB->Flush(); // Flushing the database buffers following manual deletion
    }

    void PrintBtreeHeader(POD *DB, unsigned index_number)
    {
      gxBtreeHeader hdr;
      DB->Index(index_number)->ReadBtreeHeader(hdr);
      cout << "B-tree header information" << "\n";

      cout << "Node order:      " << hdr.node_order << "\n";
      cout << "Key size:        " << hdr.key_size << "\n";
      cout << "Number of nodes: " << hdr.n_nodes << "\n";
      cout << "B-tree height:   " << hdr.btree_height << "\n";
      cout << "Root Address:    " << (long)hdr.root << "\n";
      // NOTE: The root node FAU is cast to a long for compilers
      // with native 64-bit int ostream overloads
      cout << "Number of trees: " << hdr.num_trees << "\n";
      cout << "\n";
    }

    void ListInOrder(POD *DB, unsigned index_number)
    // List the contents of the database using the index file.
    {
      // Ensure that the in memory buffers and the file data
      // stay in sync during multiple file access.
      DB->TestDatabase();

      Grocery grocery(DB);
      GroceryKey key, compare_key;
      gxBtree *btx = DB->Index(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" << "\n";
          return;
        }
        DisplayItem(grocery);
        grocery.ClearName();

        while(btx->FindNext(key, compare_key)) {
          if(!grocery.ReadObject(key.ObjectID())) {
    	cout << "Error reading the object" << "\n";
    	return;
          }
          DisplayItem(grocery);
          grocery.ClearName();
        }
      }
    }

    void ListInOrder(POD *DB)
    // List contents of the database without using the index file.
    {
      // Ensure that the in memory buffers and the file data
      // stay in sync during multiple file access.
      DB->TestDatabase();

      FAU_t addr = (FAU_t)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" << "\n";
          return;
        }
        DisplayItem(grocery);
        grocery.ClearName();
        count++;
        if(count == 2) {
          PausePrg();
          count = 0;
        }
      }
    }

    void FindItem(POD *DB)
    // Function used to find an object in the database.
    {
      // Ensure that the in memory buffers and the file data
      // stay in sync during multiple file access.
      DB->TestDatabase();

      char *buf = InputData();

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

      cout << "Found item: " << buf << "\n";
      delete buf;

      if(!grocery.ReadObject()) {
        cout << "Error reading the object" << "\n";
        grocery.ClearName(); // Prevent memory leak
        return;
      }
      DisplayItem(grocery);
      grocery.ClearName(); // Prevent memory leak
    }

    void BuildDatabase(POD *DB)
    // Function used to build a test database.
    {
      // Ensure that the in memory buffers and the file data
      // stay in sync during multiple file access.
      DB->TestDatabase();

      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..." << "\n";
      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"
    	   << "\n";
          return;
        }
      }

      // Flush the database files after a batch insert
      DB->Flush();
    }

    void Menu()
    // Console based user menu.
    {
      cout << "(A, a)    Add object to the database" << "\n";
      cout << "(B, b)    Build a test database" << "\n";
      cout << "(D, d)    Delete object from the database" << "\n";
      cout << "(F, f)    Find item by name" << "\n";
      cout << "(L, l)    List without using index file" << "\n";
      cout << "(H, h, ?) Help (prints this menu)" << "\n";
      cout << "(I, i)    List using the index file" << "\n";
      cout << "(Q, q)    Quit" << "\n";
      cout << "(S, s)    Display B-tree stats" << "\n";
      cout << "(X, x)    Compare the index file to the data file" << "\n";
      cout << "(Y, y)    Rebuild the index file" << "\n";
    }

    void Compare(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" << "\n";
        return;
      }

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

      Grocery grocery(DB);
      cout << "\n";
      cout << "Comparing the index file to the data file..." << "\n";
      int rv = grocery.CompareIndex(0);
      if(!rv) {
        cout << "The index file does not match the data file!" << "\n";
        cout << "The index file needs to be rebuilt." << "\n";
        cout << "\n";
        return;
      }

      cout << "The index file checks good" << "\n";
      cout << "\n";
    }

    void Rebuild(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;

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

      Grocery grocery(DB);
      cout << "\n";
      cout << "Rebuilding the index file..." << "\n";
      // 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!" << "\n";
        cout << "\n";
        return;
      }

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

    int main(int argv, char **argc)
    {
      char rev_letter = gxDatabaseRevisionLetter; // Set the default rev letter
      if(argv == 2) { // Set a specified revision letter
        rev_letter = *argc[1];
        if(rev_letter == '0') rev_letter = '\0';
        // Valid rev letters are:
        // Rev 0
        // Rev 'A' or 'a'
        // Rev 'B' or 'b'
        // Rev 'C' or 'c'
        // Rev 'D' or 'd'
        // Rev 'E' or 'e'
        // NOTE: The gxDatabase class will set invalid revision letters
        // to the version set by the gxDatabaseRevisionLetter constant.
      }

      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;

      gxDatabaseAccessMode mode = gxDBASE_READWRITE;
      BtreeNodeOrder_t order = POD_DEFAULT_ORDER;
      int num_trees = 1; // Use one tree per index file
      int use_index = 1;
      FAU_t static_size = (FAU_t)0;
      __SBYTE__ df_rev_letter = rev_letter;
      __SBYTE__ if_rev_letter = rev_letter;
      gxDatabaseError err = pod.Open(data_file, index_file, key_type,
    				 order, mode, use_index,
    				 static_size, num_trees, df_rev_letter,
    				 if_rev_letter);
      // Test for any errors
      if(err != gxDBASE_NO_ERROR) {
        cout << gxDatabaseExceptionMessage(err) << "\n";
        return 1;
      }

      char c;
      int rv = 1;
      Menu();

      while(rv) {
        if (!cin) {
          ClearInputStream(cin);
          if (!cin) {
    	cout << "Input stream error" << "\n";
    	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 's' : case 'S' : PrintBtreeHeader(&pod, 0); break;
          case 'x' : case 'X' : Compare(&pod); break;
          case 'y' : case 'Y' : Rebuild(&pod, "newindex.btx"); break;

          default:
            cout << "Unrecognized command" << "\n";
        }
      }

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