// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: testprog.cpp 
// Compiler Used: MSVC40, DJGPP 2.7.2.1, GCC 2.7.2.1, HP CPP 10.24
// Produced By: Doug Gaer   
// File Creation Date: 02/12/1997 
// Date Last Modified: 03/17/1999
// Copyright (c) 1997 Dougas M. Gaer
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
This is a test program for the Balanced multi-way file-base
Tree classes. 

The VBD C++ classes are copyright (c) 1997, by Douglas M. Gaer.
All those who put this code or its derivatives in a commercial
product MUST mention this copyright in their documentation for
users of the products in which this code or its derivative
classes are used. Otherwise, you have the freedom to redistribute
verbatim copies of this source code, adapt it to your specific
needs, or improve the code and release your improvements to the
public provided that the modified files carry prominent notices
stating that you changed the files and the date of any change.

THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
THE ENTIRE RISK OF THE QUALITY AND PERFORMANCE OF THIS SOFTWARE
IS WITH YOU. SHOULD ANY ELEMENT OF THIS SOFTWARE PROVE DEFECTIVE,
YOU WILL ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR
CORRECTION.
*/
// ----------------------------------------------------------- // 
#include <iostream.h>
#include <string.h>
#include "btree.h"
#include "vbdstats.h"
#include "btwalk.h"
#include "btreeprt.h"
#include "btwalker.h"
#include "strutil.h"
#include "timer.h"

void SkipToEol(istream &s)
{
  char c;
  s.clear();
  while(s.get(c) && c != '\n') { ; }
}

void pause()
{
  cout << endl;
  cout << "Press enter to continue..." << endl;
  cin.get();
}

char *InputData()
{
  char buf[255];
  cout << "Input Data: ";
  cin.getline(buf, sizeof(buf));
  unsigned len = 0;
  for(unsigned i = 0; (buf[i] != ' ') && (buf[i] != '\0'); i++) len++;
  char *s = new char[len];
  s[len] = 0;
  memmove(s, buf, len);
  return s;
}

void Build(Btree &btx)
{
  EntryKey e;

  char *aa = "DOG";
  char *bb = "CAT";
  char *cc = "FISH";
  char *dd = "MOUSE";
  char *ee = "BIRD";
  char *ff = "PIG";
  char *gg = "HORSE";
  char *hh = "LION";
  char *ii = "SNAKE";
  char *jj = "COW";
  char *kk = "ARMADILLO";
  char *ll = "GROUPER";
  char *mm = "RAT";
  char *nn = "MONKEY";
  char *oo = "ZEBRA";
  char *pp = "STARFISH";
  char *qq = "LIZARD";
  char *rr = "CRAB";
  char *ss = "SNAIL";
  char *tt = "GORILLA";
  char *uu = "LOBSTER";
  char *vv = "TURKEY";
  char *ww = "BEETLE";
  char *xx = "SHARK";
  char *yy = "CLAM";
  char *zz = "OYSTER";
    
  const int NKEYS = 26;
  char *keys[NKEYS] = { aa, bb, cc, dd, ee, ff, gg, hh, ii, jj,
			kk, ll, mm, nn, oo, pp, qq, rr, ss, tt,
                        uu, vv, ww, xx, yy, zz};
  char exists[NKEYS];

  int status, i;
  
  for(i = 0; i < NKEYS; i++) {
    if(btx.Search(keys[i])) {
      cout << "An entry for " << keys[i] << " already exists" << endl;
    }
    else {
      status = btx.Add(keys[i]);
      if (status != 1) {
	cout << endl << "Problem adding " << keys[i] << " - " << i << endl;
	Error->SignalException(EHandler::AssertError);      
      }
      else {
	cout << "Adding " << keys[i] << endl;
      }
      exists[i] = 1;
    }
  }
}

int ImportTextFile(Btree &btx)
{
  cout << endl;
  cout << "Importing a dictionary file." << endl;
  cout << "NOTE: Words must be delimited by a space or forward slash (/)"
       << endl;
  
  int status, num, i, count = 0;
  char words[MAXWORDS][MAXWORDLENGTH];
  const int MaxLine = 255;
  char LineBuffer[MaxLine];
  const char dchar = '/';  // Text delimiter

  cout << endl;
  cout << "Enter the name of the text file to import" << endl;
  char *FileName = InputData();
  
  ifstream istream(FileName, ios::in|ios::nocreate);
  if(!istream) { // Could not open the istream
    cout << "Could not open file: " << FileName << endl;
    return 1;
  }
  
  cout << "Adding words..." << endl;

  // Get CPU clock cycles before entering loop
  clock_t Begin = Start();

  while(1) {
    for(i = 0; i < MaxLine; i++) LineBuffer[i] = 0; // Clear the line buffer

    // Read in file line by line
    istream.getline(LineBuffer, MaxLine);
    
    if(parse(LineBuffer, words, &num, dchar) == 1) 
      Error->SignalException(EHandler::ParseError);      
    
    if(istream.eof()) break; // Exit loop at EOF
    
    // Using the count variable as the object ID.
    // NOTE: The object ID is usually its file address
    // in the data file. Since no data file is used in
    // this example a dummy ID is assigned to the keys.
    FAU object_id = (__LWORD__)count;
    FAU class_id = 0;
    
    // Do not flush cache or test tree header during insertions
    status = btx.Add(LineBuffer, object_id, class_id, 0);
    
    if (status != 1) {
      cout << endl << "Problem adding " << words[0] << endl;
      Error->SignalException(EHandler::AssertError);      
    }
    else {
      count++;
    }
  }
  istream.close();
  
  // Get CPU clock cycles after loop is completed 
  clock_t End = Stop();
  
  cout.precision(3);
  cout << "Added " << count << " words in " << ElapsedTime(Begin, End)
       << " seconds" << endl;

  cout << "Exiting..." << endl;
  cout << endl;
  return 0; // Exit the program after building the dictionary
}

void Menu()
{
  cout << "(a)   Add element" << endl;
  cout << "(b)   Build test tree" << endl;
  cout << "(f)   Find element" << endl;
  cout << "(h)   Help" << endl;
  cout << "(i)   Import a text dictionary" << endl;
  cout << "(p)   Partial lookup" << endl;
  cout << "(q)   Quit" << endl;
  cout << "(r)   Remove element" << endl;
  cout << "(s)   Sort in alphabetical order" << endl;
  cout << "(t)   Tree Statistics" << endl;
  cout << "(v)   File Statistics" << endl;
  cout << "(w)   Walk the tree" << endl;
}

int Quit()
{
  return 0;
}

void Add(Btree &btx)
{
  char *s = InputData();
  EntryKey e(s);
  
  if(btx.Search(e)) {
    cout << "An entry for " << e.key << " already exists" << endl;
    return;
  }
  
  int status = btx.Add(e);
  if (status != 1) 
    cout << endl << "Problem adding " << s << endl;
}

void Remove(Btree &btx)
{
  char *s = InputData();
  EntryKey e(s);

  if(!btx.Search(e)) {
    cout << "Could not find an entry for " << s << endl;
    return;
  }
  
  if (btx.Remove(e)) 
    cout << "Removed " << e.key << endl;
  else 
    cout << "Problem removing " << e.key << endl;
}

void Walk(Btree &btx)
{
  char key;
  BtreeWalkOrder walk_order;
  
  if (!cin) { // Input is in fail state
    SkipToEol(cin); // Go to end of line
    if (!cin) {  // Can't fix
      cout << "Input stream is broken" << endl;
      return;
    }
  }
  cout << "(f) Flat, (i) In-Order, (p) Pre-Order, (t) Post-Order > ";
  cin >> key;
  switch(key) {
    case 'f' : case 'F' : walk_order = btLVLORDER; break;
    case 'i' : case 'I' : walk_order = btINORDER; break;
    case 'p' : case 'P' : walk_order = btPREORDER; break;
    case 't' : case 'T' : walk_order = btPOSTORDER; break;
    default:
      cout << "Unrecognized key" << endl;
      cout << endl;
      return;
  }

  CachePointer nxt(*btx.GetCache());
  BtreeWalkerb tw(&btx, walk_order);
  nxt = btx.GetRoot();

  int num_nodes = 0; int num_entries = 0;
  while((__LWORD__)nxt != 0) {
    nxt = tw.Next();
    if((__LWORD__)nxt) {
      num_nodes++;
      int n = nxt->cnt;
      for(int i = 0; i < n; i++) {
	cout << nxt->entry[i].key << " ";
	num_entries++;
      }
    }
  }
  cout << endl;
  cout << num_nodes << " nodes in this tree" << endl;
  cout << num_entries << " entries in this tree" << endl;
}

void Find(Btree &btx)
{
  char *s = InputData();
  EntryKey e(s);
  
  clock_t Begin = Start();
  if(btx.Search(e)) {
    clock_t End = Stop();
    cout.precision(3);
    cout << "Found: " << e.key << " in " << ElapsedTime(Begin, End)
	 << " seconds" << endl;
  }
  else {
    cout << "Did not find " << s << endl;    
  }
}

void PrintKey(EntryKey &e)
{
  cout << e.key << " ";
}

void Partiallookup(Btree &btx)
{
 BtreeWalker btw(&btx);
 
 cout << "Enter a few characters of the string to find" << endl;
 char *s = InputData();

 unsigned matches = btw.Partiallookup(s, PrintKey);

 cout << endl;
 
 if(matches == 0)
   cout << "No matches found in the tree" << endl;
 else
   cout << "Found " << matches << " matches" << endl;
}
  
void Sort(Btree &btx)
{
  BtreeWalker btw(&btx);
  unsigned num_objects = btw.Sort(PrintKey);
  cout << endl;
  cout << "Sorted " << num_objects << " objects" << endl;
}

int main(int argc, char **argv)
// If no arguments are given the program will open a default file
// and prompt you to enter a command. If a file name is given the
// program will open that file. If a program name and a single
// letter representing a command is given the program will open
// that file, process the command, and exit.
{
  char *fname;
  int cache_size = 15;
  VBDFilePtr f(new VBDFile);
  Btree btx(cache_size);
  
  if(argc < 2) 
    fname = "testfile.btx";
  else 
    fname = argv[1];
  
  if(!VBDFile::Exists(fname)) {
    f->Create(fname, sizeof(BtreeHeader));
    btx.Connect(f, 1);
  }
  else {
    f->Open(fname);
    btx.Connect(f, 0);
  }
  
  int rv;
  char key;

  if(argc <= 2) Menu(); // Not processing a command
  
  rv = 1;
  while(rv) {
    if(argc > 2) { // Process a single command and exit the loop
      key = *(argv[2]);
      rv = 0;
    }
    else {
      if (!cin) { // Input is in fail state
	SkipToEol(cin); // Go to end of line
	if (!cin) {  // Can't fix
          cout << "Input stream is broken" << endl;
          return 0;
	}
      }
      cout << endl;
      cout << '>';
      cin >> key;
    }
    if (!cin) continue; // Fix at top of loop
    switch(key) {
      case 'a' : case 'A' :
	if(argc <= 2) SkipToEol(cin);
	Add(btx);
	break;
      case 'b' : case 'B' :
	Build(btx);
	break;
      case 'f' : case 'F' :
	if(argc <= 2) SkipToEol(cin);
	Find(btx);
	break;
      case 'h' : case 'H' :
	Menu();
	break;
      case 'i' : case 'I' :
	if(argc <= 2) SkipToEol(cin);
	rv = ImportTextFile(btx);
	break;
      case '?' :
	Menu();
	break;
      case 'p' : case 'P' :
	if(argc <= 2) SkipToEol(cin);
	Partiallookup(btx);
	break;
      case 'q' : case 'Q' :
	rv = Quit();
	break;
      case 'r' : case 'R' :
	if(argc <= 2) SkipToEol(cin);
	Remove(btx);
	break;
      case 's' : case 'S' :
	Sort(btx);
	break;
      case 't' : case 'T' :
	BTreeStats(btx);
	break;
      case 'v' : case 'V' :
	VBDStats(f);
	break;
      case 'w' : case 'W' :
	Walk(btx);
	break;
      default:
        cout << "Unrecognized command" << endl;
	cout << endl;
    }
  }
  return 0;
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
