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

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

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 << endl;
  if(infohog.GetMember(0)) {
    cout << "Item Name    = " << (char *)infohog.GetMember(0) << endl;
  }
  if(full) {
    if(infohog.GetMember(1)) {
      cout << "Stock Number = " 
	   << *((StockNumber_t *)infohog.GetMember(1)) << endl;
    }
    if(infohog.GetMember(2)) {
      cout.setf(ios::showpoint | ios::fixed);
      cout.precision(2);
      cout << "Price        = $" 
	   << *((Price_t *)infohog.GetMember(2)) << endl;
    }
  }
}

void Menu()
// Console based user menu.
{
  cout << "(A, a)    Add object to the database" << endl;
  cout << "(B, b)    Build a test database" << endl;
  cout << "(C)       Change data members" << endl;
  cout << "(c)       Change object" << 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 BuildDatabase(const POD *DB)
// Function used to build a test database.
{
  const int NUM_OBJECTS = 10;
  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..." << endl;
  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" << endl;
      return;
    }
    if(!infohog.SetMember(&stock_number, sizeof(stock_number), 1)) {
      cout << "Could not set data member 1" << endl;
      return;
    }
    if(!infohog.SetMember(&price, sizeof(price), 2)) {
      cout << "Could not set data member 2" << endl;
      return;
    }
    if(!infohog.WriteObject()) {
      cout << "Could not add object number " << i << " to the database"
	   << endl;
      return;
    }
  }
}

void ListInOrder(const POD *DB, unsigned index_number)
// List the contents of the database using the index file.
{
  InfoHog<Name_t> infohog(DB);
  InfoHogKey<Name_t>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(!infohog.ReadObject(key.ObjectID())) {
      cout << "Error reading the object" << endl;
      return;
    }
    DisplayItem(infohog);
    
    while(btx->FindNext(key, compare_key)) {
      if(!infohog.ReadObject(key.ObjectID())) {
	cout << "Error reading the object" << endl;
	return;
      }
      DisplayItem(infohog);
    }
  }
}

void ListInOrder(const POD *DB)
// List contents of the database without using the index file.
{
  FAU addr = (FAU)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" << endl;
      return;
    }
    DisplayItem(infohog);
    count++;
    if(count == 2) {
      PausePrg();
      count = 0;
    }
  }
}

void AddItem(const POD *DB)
// Function used to add an object to the database.
{
  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 at address "
	 << infohog.ObjectAddress() << endl;
    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!" << endl;
    return;
  }
  if(cin) {
    Price_t p = price;
    infohog.SetMember(&p, sizeof(Price_t), 2);
  }
  else {
    cout << "Invalid entry. Object not added!" << endl;
    return;
  }
  
  ClearInputStream(cin);
  
  if(!infohog.WriteObject()) {
    cout << "Could not add object to the database" << endl;
  }
}

void DeleteItem(const POD *DB)
// Function used to delete an object from the database.
{
  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" << endl;
    return;
  }

  cout << "Deleted item: " << key << " at address "
       << infohog.ObjectAddress() << endl;
}

void FindItem(const POD *DB)
// Function used to find an object in the database.
{
  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" 
	 << endl;
    return;
  }

  cout << "Found item: " << key << " at address "
       << infohog.ObjectAddress() << endl;
  
  if(!infohog.ReadObject()) {
    cout << "Error reading the object" << endl;
    return;
  }
  DisplayItem(infohog); 
}

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

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

void ChangeItem(const 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" 
	 << endl;
    return;
  }
  
  cout << "Found item: " << key << " at address "
       << infohog.ObjectAddress() << endl;
  
  if(!infohog.ReadObject()) {
    cout << "Error reading the object" << endl;
    return;
  }
  DisplayItem(infohog); 
  
  ClearInputStream(cin);
  cout << "Entering changes to this object..." << endl;
  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!" << endl;
    return;
  }
  if(cin) {
    Price_t p = price;
    infohog_new.SetMember(&p, sizeof(Price_t), 2);
  }
  else {
    cout << "Invalid entry. Object not added!" << endl;
    return;
  }
  
  ClearInputStream(cin);
  int find = 0;

  if(change_object) { // Change the object
    if(!infohog.ChangeObject(infohog_new, find)) {
      cout << "Could not change this object" << endl;
      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" << endl;
    return;
  }
  if(!infohog.ChangeMember(infohog_new.GetMember(1), 
			   infohog_new.GetMemberLen(1), 1, find)) {
    cout << "Could not change member 1" << endl;
    return;
  }
  if(!infohog.ChangeMember(infohog_new.GetMember(2), 
			   infohog_new.GetMemberLen(2), 2, find)) {
    cout << "Could not change member 2" << endl;
    return;
  }
}

int main()
{
#ifdef __MSVC_DEBUG__
  InitLeakTest();
#endif

  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
  POD pod;
  gxDatabaseError err = pod.Open(data_file, index_file, key_type,
				 InfoHogNodeOrder, gxDBASE_READWRITE, 
				 InfoHogUseIndexFile, InfoHogStaticArea,
				 InfoHogNumTrees);
  // 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 '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 'x' : case 'X' : 
	Compare(&pod); break;
      case 'y' : case 'Y' : 
	Rebuild(&pod, "newindex.ihx"); break;
      default:
        cout << "Unrecognized command" << endl;
    }
  }

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