// ------------------------------- //
// -------- 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: 10/15/1998
// Copyright (c) 1997 Douglas M. Gaer
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
This is a test program for the balanced multi-way file-base
tree class. This program is used to create an index file,
which contains key indexes.

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 <fstream.h>
#include <stdlib.h>
#include <string.h>
#include "timer.hpp"
#include "btree.hpp"
#include "btreeprt.hpp"
#include "strutil.hpp"

// Default cache size
const int cache_size = 1024;

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

char *InputData()
{
  char buf[255];
  cout << "Enter word: ";
  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 Dump(CachePointer t)
{
  int i = 0;
  
  if ((__LWORD__)t) {
    while (i < t->cnt) {
      cout << t->entry[i].key << endl;
      i++;
    }

    int n = t->cnt;
    CachePointer p(t);
    for(i = -1; i < n; i++) { 
      if((__LWORD__)p) 
	p = t->Branch(i);
      t.Release(); // Prevent cache from filling up during recursion
      Dump(p);  
    }
  }
}

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

int Quit()
{
  cout << "Exiting..." << endl;
  return 0;
}

int Find(Btree &t, char *s)
{
  if(!s) return 0;
  EntryKey e = s;
  int status = t.FullSearch(e);
  return status;
}

void FindWord(Btree &t)
{
  char *buf = InputData();

  clock_t Begin = Start();
  int exists = Find(t, buf);
  clock_t End = Stop();
  
  if(!exists) {
    cout << "Could not find: " << buf << endl;
    return;
  }
  
  cout.precision(3);
  cout << "Found: " << buf << " in " << ElapsedTime(Begin, End)
       << " seconds" << endl;
  cout << endl;
}

void RemoveWord(Btree &t)
{
  char *buf = InputData();

  int exists = Find(t, buf);
  
  if(exists != 1) {
    cout << "Could not find: " << buf << endl;
    return;
  }

  cout << "Deleting: " << buf << endl;
  if(t.Remove(buf) != 1) {
      cout << "Problem removing " << buf << endl;
      Error->SignalException(EHandler::AssertError);
    }
}

void AddWord(Btree &t)
{
  char *buf = InputData();

  int exists = Find(t, buf);

  if(exists) {
    cout << buf << " already int the dictionary!" << endl;
    return;
  }
  
  int status = t.Add(buf, 0, 0, 0);
  if (status != 1) {
    cout << endl << "Problem adding " << buf << endl;
    Error->SignalException(EHandler::AssertError);      
  }
  else 
    cout << "Added " << buf << endl;
}

void Menu(void)
{
  cout << "(A)   Add a word to the dictionary" << endl;
  cout << "(F)   Find a word in the dictionary" << endl;
  cout << "(L)   List the entire contents of the dictionary" << endl; 
  cout << "(H)   Help (prints this menu)" << endl;
  cout << "(P)   Print the tree node by node" << endl;
  cout << "(R)   Remove a word from the dictionary" << endl;
  cout << "(T)   Display Btree stats" << endl;
  cout << "(V)   Display file stats" << endl;
  cout << "(Q)   Quit" << endl;
  cout << "(X)   Dump the entire dictionary" << endl;
  cout << endl;
}

void DisplayStats(const VBDFilePtr &f)
{
  cout << endl;
  cout << "VBDFile Free Space:     " << f->GetFreeSpace() << endl;
  cout << "VBDFile End of File:    " << f->GetEOF() << endl;
  cout << "VBDFile Heap Start:     " << f->GetHeapStart() << endl;
  cout << "VBDFile Highest VB:     " << f->GetHighestVB() << endl;

  clock_t Begin = Start();
  cout << "Total VBs in file:      " << f->VBTotal() << endl;
  clock_t End = Stop();
  cout.precision(3);
  cout << "Found in " << ElapsedTime(Begin, End)
       << " seconds" << endl;

  cout << "Total deleted VBs:      " << f->VBDeleted() << endl;
  cout << endl;
}

void BuildDictionary(const char *fname)
{
  cout << endl;
  cout << "Building a new dictionary." << endl;
  
  VBDFilePtr f(new VBDFile);
  Btree t(cache_size);
  f->Create(fname, sizeof(BtreeHeader));
  t.Connect(f, 1);
  
  int status, num, i;
  int count = 0;
  char words[MAXWORDS][MAXWORDLENGTH];
  const int MaxLine = 255;
  char LineBuffer[MaxLine];
  
#ifdef __DOS__
  char *FileName = "..\\..\\diction\\amerdict.txt";
#endif

#ifdef __UNIX__
  char *FileName = "../../diction/amerdict.txt";
#endif
  
  const char dchar = '/';  // Text delimiter

  ifstream istream(FileName, ios::in|ios::nocreate);
  if(!istream) { // Could not open the istream
    cout << "Could not open file: " << FileName << endl;
    Error->SignalException(EHandler::FileOpenError);      
  }
  
  cout << endl;
  cout << "WARNING: This will require 5 Meg of disk space." << endl;
  cout << "Press Enter to continue or Ctrl-C to abort..." << endl;
  cin.get();
  cout << endl;
  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
    
    status = t.Add(LineBuffer, 0, 0, 0);
    if (status != 1) {
      cout << endl << "Problem adding " << words[0] << endl;
      Error->SignalException(EHandler::AssertError);      
    }
    else {
      // Echo each word added to the console
      // cout << "Adding " << words[0] << endl;
      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 << endl;
}

int main()
{
  // Default dictionary name
  char *fname = "amerdict.btx";
  
  if(!VBDFile::Exists(fname))  BuildDictionary(fname);

  VBDFilePtr f(new VBDFile);
  Btree t(cache_size);
  f->Open(fname);
  t.Connect(f, 0);
  
  // Main menu
  char key;
  Menu();
  int rv = 1;
  while(rv) {
    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 << '>';
    cin >> key;
    if (!cin) continue; // Fix at top of loop
    switch(key) {
      case 'a' : case 'A' : SkipToEol(cin); AddWord(t); break;
      case 'f' : case 'F' : SkipToEol(cin); FindWord(t); break;	
      case 'l' : case 'L' :
	SkipToEol(cin); ListInOrder(t.GetRoot(), &t); break;
      case 'h' : case 'H' : Menu(); break;
      case '?' : Menu(); break;
      case 'p' : case 'P' :
	SkipToEol(cin); PrintTree(t.GetRoot(), &t); break;
      case 'q' : case 'Q' : rv = Quit(); break;
      case 'r' : case 'R' : SkipToEol(cin); RemoveWord(t); break;
      case 't' : case 'T' : SkipToEol(cin); BTreeStats(t); break;	
      case 'v' : case 'V' : SkipToEol(cin); DisplayStats(f); break;
      case 'x' : case 'X' : SkipToEol(cin); Dump(t.GetRoot()); break;
      default:
        cout << "Unrecognized command" << endl;
    }
  }
  return 0;
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
//
