    // ------------------------------- //
    // -------- 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-2024 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

    Test program for the InfoHog database API.
    */
    // ----------------------------------------------------------- //
    #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 "fstring.h"
    #include "infohog.h"
    #include "gxfloat.h"
    #include "ustring.h"

    #ifdef __MSVC_DEBUG__
    #include "leaktest.h"
    #endif

    // Type definitions
    typedef FString Name_t;        // String type
    typedef gxFLOAT64 Price_t;     // Floating point type
    typedef gxINT32 StockNumber_t; // Integer type

    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;
    }

    void InputData(Name_t &key)
    // Function used to read a string from the input stream.
    {
      cout << "Item name: ";
      cin >> key;
    }

    void DisplayItem(InfoHog<Name_t> &infohog, int full = 1)
    // Function used to print a infohog list object to the stdout.
    {
      cout << "\n";
      if(infohog.GetMember(0)) {
        cout << "Item Name    = " << (char *)infohog.GetMember(0) << "\n";
      }
      if(full) {
        if(infohog.GetMember(1)) {
          cout << "Stock Number = "
    	   << *((StockNumber_t *)infohog.GetMember(1)) << "\n";
        }
        if(infohog.GetMember(2)) {
          cout.setf(ios::showpoint | ios::fixed);
          cout.precision(2);
          cout << "Price        = $"
    	   << *((Price_t *)infohog.GetMember(2)) << "\n";
        }
      }
    }

    void PrintBtreeHeader(POD *DB, unsigned index_number)
    {
      gxBtreeHeader hdr;
      UString intbuf;
      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";
      intbuf << clear << (FAU_t)hdr.root;
      cout << "Root Address:    " << intbuf.c_str() << "\n";
      cout << "Number of trees: " << hdr.num_trees << "\n";
      cout << "\n";
    }

    void Menu()
    // Console based user menu.
    {
      cout << "(A, a)    Add object to the database" << "\n";
      cout << "(B, b)    Build a test database" << "\n";
      cout << "(C)       Change data members" << "\n";
      cout << "(c)       Change object" << "\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 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 = 0;
      InfoHog<Name_t> infohog(DB);

      char name[InfoHogNameLength];
      StockNumber_t stock_number = (StockNumber_t)2000;
      Price_t price = (Price_t)0.95;

      cout << "Adding " << NUM_OBJECTS << " objects to the database..." << "\n";
      for(i = 0; i < NUM_OBJECTS; i++) {
        sprintf(name, "Item-%i", i);
        stock_number += i;
        price += i;
        Name_t key(name);
        if(!infohog.SetMember(&key, sizeof(key), 0)) {
          cout << "Could not set data member 0" << "\n";
          return;
        }
        if(!infohog.SetMember(&stock_number, sizeof(stock_number), 1)) {
          cout << "Could not set data member 1" << "\n";
          return;
        }
        if(!infohog.SetMember(&price, sizeof(price), 2)) {
          cout << "Could not set data member 2" << "\n";
          return;
        }
        if(!infohog.WriteObject()) {
          cout << "Could not add object number " << i << " to the database"
    	   << "\n";
          return;
        }
      }

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

    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();

      InfoHog<Name_t> infohog(DB);
      InfoHogKey<Name_t>key, compare_key;
      gxBtree *btx = DB->Index(index_number);

      // Walk through the tree starting at the first key
      if(btx->FindFirst(key)) {
        if(!infohog.ReadObject(key.ObjectID())) {
          cout << "Error reading the object" << "\n";
          return;
        }
        DisplayItem(infohog);

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

    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;
      InfoHog<Name_t> infohog(DB);
      unsigned count = 0;
      ClearInputStream(cin); // Clear input stream
      while(1) {
        addr = DB->OpenDataFile()->FindFirstObject(addr);
        if(!addr) break;
        if(!infohog.ReadObject(addr)) {
          cout << "Error reading the object" << "\n";
          return;
        }
        DisplayItem(infohog);
        count++;
        if(count == 2) {
          PausePrg();
          count = 0;
        }
      }
    }

    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();

      Name_t key;
      InputData(key);

      InfoHog<Name_t> infohog(DB);
      infohog.SetMember(&key, sizeof(key), 0);

      int exists = infohog.FindObject();

      if(exists) {
        cout << "Item: " << key << " already exists" << "\n";
        return;
      }

      int stock_number;
      double price;

      cout << "Enter the items's stock number: ";
      cin >> stock_number;
      if(cin) {
        StockNumber_t sn = stock_number;
        infohog.SetMember(&sn, sizeof(StockNumber_t), 1);
        cout << "Enter the items's price:       $";
        cin >> price;
      }
      else {
        cout << "Invalid entry. Object not added!" << "\n";
        return;
      }
      if(cin) {
        Price_t p = price;
        infohog.SetMember(&p, sizeof(Price_t), 2);
      }
      else {
        cout << "Invalid entry. Object not added!" << "\n";
        return;
      }

      ClearInputStream(cin);

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

      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();

      Name_t key;
      InputData(key);

      InfoHog<Name_t> infohog(DB);
      infohog.SetMember(&key, sizeof(key), 0);
      if(!infohog.DeleteObject()) {
        cout << "Could not find item: " << key << " in the database" << "\n";
        return;
      }

      cout << "Deleted item: " << key << "\n";

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

    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();

      Name_t key;
      InputData(key);

      InfoHog<Name_t> infohog(DB);
      infohog.SetMember(&key, sizeof(key), 0);

      if(!infohog.FindObject()) {
        cout << "Could not find item: " << key << " in the database"
    	 << "\n";
        return;
      }

      cout << "Found item: " << key << "\n";

      if(!infohog.ReadObject()) {
        cout << "Error reading the object" << "\n";
        return;
      }
      DisplayItem(infohog);
    }

    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();

      InfoHog<Name_t> infohog(DB);
      cout << "\n";
      cout << "Comparing the index file to the data file..." << "\n";
      int rv = infohog.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();

      InfoHog<Name_t> infohog(DB);
      cout << "\n";
      cout << "Rebuilding the index file..." << "\n";

      // Rebuild index number 0
      int rv = infohog.RebuildIndexFile(fname, 0,
    				    InfoHogNumTrees, InfoHogNodeOrder);
      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";
    }

    void ChangeItem(POD *DB, int change_object = 1)
    {
      Name_t key;
      InputData(key);

      InfoHog<Name_t> infohog(DB);
      infohog.SetMember(&key, sizeof(key), 0);

      if(!infohog.FindObject()) {
        cout << "Could not find item: " << key << " in the database"
    	 << "\n";
        return;
      }

      cout << "Found item: " << key << "\n";

      if(!infohog.ReadObject()) {
        cout << "Error reading the object" << "\n";
        return;
      }
      DisplayItem(infohog);

      ClearInputStream(cin);
      cout << "Entering changes to this object..." << "\n";
      Name_t new_key;
      InputData(new_key);

      InfoHog<Name_t> infohog_new(DB);
      infohog_new.SetMember(&new_key, sizeof(new_key), 0);

      int stock_number;
      double price;

      cout << "Enter the items's stock number: ";
      cin >> stock_number;
      if(cin) {
        StockNumber_t sn = stock_number;
        infohog_new.SetMember(&sn, sizeof(StockNumber_t), 1);
        cout << "Enter the items's price:       $";
        cin >> price;
      }
      else {
        cout << "Invalid entry. Object not added!" << "\n";
        return;
      }
      if(cin) {
        Price_t p = price;
        infohog_new.SetMember(&p, sizeof(Price_t), 2);
      }
      else {
        cout << "Invalid entry. Object not added!" << "\n";
        return;
      }

      ClearInputStream(cin);
      int find = 0;

      if(change_object) { // Change the object
        if(!infohog.ChangeObject(infohog_new, find)) {
          cout << "Could not change this object" << "\n";
          return;
        }
        return;
      }

      // Change the object member by member
      if(!infohog.ChangeMember(infohog_new.GetMember(0),
      			   infohog_new.GetMemberLen(0), 0, find)) {
        cout << "Could not change key member" << "\n";
        return;
      }
      if(!infohog.ChangeMember(infohog_new.GetMember(1),
    			   infohog_new.GetMemberLen(1), 1, find)) {
        cout << "Could not change member 1" << "\n";
        return;
      }
      if(!infohog.ChangeMember(infohog_new.GetMember(2),
    			   infohog_new.GetMemberLen(2), 2, find)) {
        cout << "Could not change member 2" << "\n";
        return;
      }
    }

    int main(int argv, char **argc)
    {
    #ifdef __MSVC_DEBUG__
      InitLeakTest();
    #endif

      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.
      }

      InfoHogKey<Name_t> key_type;
      FStringCaseCompare = 0; // Do not compare the case of FString objects

      const char *data_file = "grocery.ihd";
      const char *index_file = "grocery.ihx";

      // Create or open an existing database using a single index file
      gxDatabaseAccessMode mode = gxDBASE_READWRITE;
      BtreeNodeOrder_t order = InfoHogNodeOrder;
      int num_trees = InfoHogNumTrees;
      int use_index = InfoHogUseIndexFile;
      FAU_t static_size = InfoHogStaticArea;
      __SBYTE__ df_rev_letter = rev_letter;
      __SBYTE__ if_rev_letter = rev_letter;

      POD pod;
      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 'c' :
    	ClearInputStream(cin); ChangeItem(&pod);
    	break;
          case 'C' :
    	ClearInputStream(cin); ChangeItem(&pod, 0);
    	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.ihx"); break;
          default:
            cout << "Unrecognized command" << "\n";
        }
      }

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