// file: $isip/class/dstr/HashTable/HashTableDiagnose.h
// version: $Id: HashTableDiagnose.h 10636 2007-01-26 22:18:09Z tm334 $
//

// make sure definitions are only made once
//
#ifndef ISIP_HASH_TABLE_DIAGNOSE
#define ISIP_HASH_TABLE_DIAGNOSE

// isip include files
//
#ifndef ISIP_HASH_TABLE
#include <HashTable.h>
#endif

// HashTableDiagnose: a class that contains the diagnose method of
// HashTable class.
//
template<class THashable, class TObject>
class HashTableDiagnose : public HashTable<THashable, TObject> {

  //---------------------------------------------------------------------------
  //
  // public constants
  //
  //---------------------------------------------------------------------------
public:
  
  // define the class name
  //
  
  //----------------------------------------
  //
  // i/o related constants
  //
  //----------------------------------------  
  
  //----------------------------------------
  //
  // default values and arguments
  //
  //----------------------------------------

  // default values
  //
    
  // default arguments to methods
  //
  
  //----------------------------------------
  //
  // error codes
  //
  //----------------------------------------  
  
  //---------------------------------------------------------------------------
  //
  // protected data
  //
  //---------------------------------------------------------------------------
protected:

  //---------------------------------------------------------------------------
  //
  // required public methods
  //
  //---------------------------------------------------------------------------
public:

  // method: name
  //
  static const String& name() {
    return HashTable<THashable, TObject>::name();
  }

  // other static methods
  //
  static boolean diagnose(Integral::DEBUG debug_level);

  // debug methods
  //  these methods are omitted since this class does not have data
  //  members and operations  
  //
  
  // destructor/constructor(s):
  //  these methods are omitted since this class does not have data
  //  members and operations
  //

  // assign methods:
  //  these methods are omitted since this class does not have data
  //  members and operations
  //

  // operator= methods:
  //  these methods are omitted since this class does not have data
  //  members and operations
  //  

  // i/o methods:
  //  these methods are omitted since this class does not have data
  //  members and operations
  //

  // equality methods:
  //  these methods are omitted since this class does not have data
  //  members and operations
  //

  // memory-management methods:
  //  these methods are omitted since this class does not have data
  //  members and operations
  //
  
  //---------------------------------------------------------------------------
  //
  // class-specific public methods
  //
  //---------------------------------------------------------------------------

  //  these methods are omitted since this class does not have data
  //  members and operations
  //
  
  //---------------------------------------------------------------------------
  //
  // private methods
  //
  //---------------------------------------------------------------------------
private:

}; 

// below are all the methods for the HashTableDiagnose template class
//

//-----------------------------------------------------------------------------
//
// required static methods
//
//-----------------------------------------------------------------------------

// method: diagnose
//
// arguments:
//  Integral::DEBUG level: (input) debug level for diagnostics
//
// return: a boolean value indicating status
//
template<class THashable, class TObject>
boolean HashTableDiagnose<THashable, TObject>::diagnose(Integral::DEBUG level_a) {

  //----------------------------------------------------------------------
  //
  // 0. preliminaries
  //
  //----------------------------------------------------------------------

  // output the class name
  //
  if (level_a > Integral::NONE) {
    SysString output(L"diagnosing class ");
    output.concat(HashTable<THashable, TObject>::CLASS_NAME);
    output.concat(L": ");
    Console::put(output);
    Console::increaseIndention();
  }

  //---------------------------------------------------------------------
  //
  // 1. required public methods
  //
  //---------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing required public methods...\n");
    Console::increaseIndention();
  }

  // test the debug methods
  //
  HashTable<THashable, TObject>::setDebug(HashTable<THashable, TObject>::debug_level_d);
  
  if (level_a > Integral::BRIEF) {
    Integral::debug(L"debug");
  }

  // prepare items and keys for all the hash tables used in this diagnose 
  //
  long num_elem = 20;
  Char** items = new Char*[num_elem];
  String* keys = new String[num_elem];
  Char key_char;
  
  for (long i = 0; i < num_elem; i++) {
    items[i] = new Char((unichar)((int)'a' + i));
    key_char.assign((unichar)((int)'a' + i));
    keys[i].assign(key_char);
    keys[i].concat(L"-key");
  }
  
  // we change some keys in order to create some collisions
  //
  keys[5].assign(L"ab-key");  // hashes to 17 from 49
  keys[9].assign(L"az-key");  // hashes to 17 from 49
  keys[13].assign(L"ad-key");  // hashes to 17 from 49
  keys[15].assign(L"ah-key");  // hashes to 1 from 49
  keys[19].assign(L"aa-key");  // hashes to 1 from 49

  // test constructors
  //
  HashTable<String, Char> def_htable(DstrBase::SYSTEM, (long)50);
  
  if (def_htable.getCapacity() != (long)50) {
    Long cap = def_htable.getCapacity();
    cap.debug(L"capacity");
    return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  for (long i = 0; i < num_elem; i++) {
    def_htable.insert(keys[i], items[i]);
  }

  // copy constructor
  //
  HashTable<String, Char> copy_htable(def_htable);     
  
  // the two constructed hash tables should be the same now
  //
  if (def_htable.ne(copy_htable)) {
    def_htable.debug(L"def_htable");
    return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			 __LINE__);
  }

  {
    // test constructors
    //
    HashTable<String, Char> def_htable;
    
    if (def_htable.getCapacity() != (long)128) {
      return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
    
    for (long i = 0; i < num_elem; i++) {
      def_htable.insert(keys[i], items[i]);
    }
    
    // copy constructor
    //
    HashTable<String, Char> copy_htable(def_htable);     
    
    // the two constructed hash tables should be the same now
    //
    if (def_htable.ne(copy_htable)) {
      def_htable.debug(L"def_htable");
      return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
  }
  
  // check the constructors and destructors for allocating on the dynamic
  // memory heap
  //
  HashTable<String, Char>* def_dyn_htable =
    new HashTable<String, Char>(DstrBase::SYSTEM, (long)50);

  if (def_dyn_htable->getCapacity() != (long)50) {
    return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  for (long i = 0; i < num_elem; i++) {
    def_dyn_htable->insert(keys[i], items[i]);
  }
  
  HashTable<String, Char>* copy_dyn_htable =
    new HashTable<String, Char>(*def_dyn_htable);
  
  // the two constructed hash tables should be the same now
  //
  if (def_dyn_htable->ne(*copy_dyn_htable)) {    
    return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			 __LINE__);
  }

  // see if we can dynamically delete
  //
  delete def_dyn_htable;
  delete copy_dyn_htable;
  {
    // check the constructors and destructors for allocating on the dynamic
    // memory heap
    //
    HashTable<String, Char>* def_dyn_htable =
      new HashTable<String, Char>();

    if (def_dyn_htable->getCapacity() != (long)128) {
      return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
  
    for (long i = 0; i < num_elem; i++) {
      def_dyn_htable->insert(keys[i], items[i]);
    }
    
    HashTable<String, Char>* copy_dyn_htable =
      new HashTable<String, Char>(*def_dyn_htable);
    
    // the two constructed hash tables should be the same now
    //
    if (def_dyn_htable->ne(*copy_dyn_htable)) {    
      return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
    
    // see if we can dynamically delete
    //
    delete def_dyn_htable;
    delete copy_dyn_htable;
  }
  
  // check the constructors for allocating on the stack
  //
  HashTable<String, Char> def_htable_2(DstrBase::USER, (long)50);

  if (def_htable_2.getCapacity() != (long)50) {
    return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  for (long i = 0; i < num_elem; i++) {
    def_htable_2.insert(keys[i], items[i]);
  }

  // copy constructor
  //  
  HashTable<String, Char> copy_htable_2(def_htable_2);

  // the two constructed hash tables should be the same now
  //
  if (def_htable_2.ne(copy_htable_2)) {
    return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			 __LINE__);
  }

  {
    // check the constructors for allocating on the stack
    //
    HashTable<String, Char> def_htable_2(DstrBase::USER);

    if (def_htable_2.getCapacity() != (long)128) {
      return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
    
    for (long i = 0; i < num_elem; i++) {
      def_htable_2.insert(keys[i], items[i]);
    }
    
    // copy constructor
    //  
    HashTable<String, Char> copy_htable_2(def_htable_2);

    // the two constructed hash tables should be the same now
    //
    if (def_htable_2.ne(copy_htable_2)) {
      return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
  }

  // check the constructors and destructors for allocating on the dynamic
  // memory heap
  //
  HashTable<String, Char>* def_dyn_htable_2 =
    new HashTable<String, Char>(DstrBase::USER, (long)50);

  if (def_dyn_htable_2->getCapacity() != (long)50) {
    return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			 __LINE__);
  }

  for (long i = 0; i < num_elem; i++) {
    def_dyn_htable_2->insert(keys[i], items[i]);
  }
  
  HashTable<String, Char>* copy_dyn_htable_2 = new HashTable<String, Char>(*def_dyn_htable_2);
  
  // the two constructed hash tables should be the same now
  //
  if (def_dyn_htable_2->ne(*copy_dyn_htable_2)) {
    return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  // see if we can dynamically delete
  //
  delete def_dyn_htable_2;
  delete copy_dyn_htable_2;

  {
    // check the constructors and destructors for allocating on the dynamic
    // memory heap
    //
    HashTable<String, Char>* def_dyn_htable_2 =
      new HashTable<String, Char>(DstrBase::USER);
    
    if (def_dyn_htable_2->getCapacity() != (long)128) {
      return Error::handle(name(), L"constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
    
    for (long i = 0; i < num_elem; i++) {
      def_dyn_htable_2->insert(keys[i], items[i]);
    }
    
    HashTable<String, Char>* copy_dyn_htable_2 =
      new HashTable<String, Char>(*def_dyn_htable_2);
    
    // the two constructed hash tables should be the same now
    //
    if (def_dyn_htable_2->ne(*copy_dyn_htable_2)) {
      return Error::handle(name(), L"copy constructor", Error::TEST, __FILE__,
			   __LINE__);
    }
    
    // see if we can dynamically delete
    //
    delete def_dyn_htable_2;
    delete copy_dyn_htable_2;    
  }

  // test large allocation construction and deletion
  //
  if (level_a >= Integral::ALL) {
    
    // output an informative message
    //
    Console::put(L"\ntesting large chunk memory allocation and deletion:\n");
    
    // set the memory to a strange block size so we can hopefully catch any
    // frame overrun errors
    //
    HashTable<THashable, TObject>::setGrowSize((long)13);
    
    // loop for a large number of times creating and deleting a large number
    // of htables at each loop
    //
    for (long j = 1; j <= 10; j++) {
      
      HashTable<String, Char>** htables = new HashTable<String, Char>*[j * 50];      
      HashTable<String, Char>** system_htables = new HashTable<String, Char>*[j * 50];
      
      // create the items
      //
      for (long i = 0; i < j * 50; i++) {
	system_htables[i] =  new HashTable<String, Char>();
	htables[i] =  new HashTable<String, Char>(DstrBase::USER);
      }
      
      // delete hash tables
      //
      for (long i = (j * 50) - 1; i >= 0; i--) {
	delete htables[i];
	delete system_htables[i];
      }
      
      // clean up memory
      //
      delete [] htables;
      delete [] system_htables;
    }
  }
  
  // test assign methods
  //
  HashTable<String, Char> tmp_htable;
  HashTable<String, Char>* tmp_dyn_htable = new HashTable<String, Char>();

  // USER-allocated
  //
  HashTable<String, Char> tmp_htable_1(DstrBase::USER);
  HashTable<String, Char>* tmp_dyn_htable_1 = new HashTable<String, Char>(DstrBase::USER);
  
  // add some items into the htable
  //
  for (long i = 0; i < num_elem; i++) {
    tmp_htable.insert(keys[i], items[i]);
  }
  
  // try the htable assign method
  //
  tmp_dyn_htable->assign(tmp_htable);
  tmp_htable_1.assign(*tmp_dyn_htable_1);
  
  if ((tmp_htable_1.ne(*tmp_dyn_htable_1)) ||
      (tmp_dyn_htable_1->ne(tmp_htable_1))) {
    return Error::handle(name(), L"assign", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  if ((tmp_dyn_htable->ne(tmp_htable)) ||
      (tmp_htable.ne(*tmp_dyn_htable))) {
    return Error::handle(name(), L"assign", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  // clean up
  //
  delete tmp_dyn_htable;
  delete tmp_dyn_htable_1;

  // testing i/o methods
  //
  String text_filename;
  Integral::makeTemp(text_filename);
  String bin_filename;
  Integral::makeTemp(bin_filename);

  // open files in write mode
  //
  Sof text_file;
  text_file.open(text_filename, File::WRITE_ONLY, File::TEXT);
  Sof bin_file;
  bin_file.open(bin_filename, File::WRITE_ONLY, File::BINARY);
  
  // prepare items for the htables
  //
  Char** write_chars = new Char*[10];
  String** write_strings = new String*[10];
  Vector<Char>* vec_chars = new Vector<Char>[10];
  unichar tmp_char = L'a';
  
  for (long i = 0; i < 10; i++) {
    write_chars[i] = new Char(tmp_char);
    write_strings[i] = new String();
    vec_chars[i].setLength(5);
    vec_chars[i].assign(*write_chars[i]);
    tmp_char++;
  }
  
  write_strings[0]->assign(L"this ");
  write_strings[1]->assign(L"is");
  write_strings[2]->assign(L"a");
  write_strings[3]->assign(L"HashTable");
  write_strings[4]->assign(L"String");
  write_strings[5]->assign(L"this ");
  write_strings[6]->assign(L"is");
  write_strings[7]->assign(L"in");
  write_strings[8]->assign(L"the");
  write_strings[9]->assign(L"diagnose");
  
  // create hash tables to write, we set a small capacity on purpose
  //
  HashTable<String, Char> write_char_htable(DstrBase::USER, (long)20);
  HashTable<String, String> write_str_htable(DstrBase::USER, (long)20);

  HashTable<String,  Vector<Char> > write_vec_char_htable(DstrBase::USER, (long)20);

  HashTable<String, Long> write_empty_hash;
  HashTable<String, Long> write_nested_hash;
  
  Long l;
  l = 8;
  String object_name(L"Object");
  write_nested_hash.insert(object_name, &l);

  for ( long i = 0; i < 10; i++) {
    write_char_htable.insert(keys[i], write_chars[i]);
    write_str_htable.insert(keys[i], write_strings[i]);
    write_vec_char_htable.insert(keys[i], &vec_chars[i]);
  }
  
  // print our some hash tables
  //
  if (level_a >= Integral::ALL) {
    write_str_htable.debug(L"write_str_htable");
  }
  
  // write the values
  //
  write_char_htable.write(text_file, (long)0);
  write_char_htable.write(bin_file, (long)0);  
  write_str_htable.write(text_file, (long)0);
  write_str_htable.write(bin_file, (long)0);
  write_vec_char_htable.write(text_file, (long)0);
  write_vec_char_htable.write(bin_file, (long)0);

  String hash_name(L"hash");

  text_file.put(object_name, 0, -1);
  write_empty_hash.writeData(text_file, hash_name);
  l.writeData(text_file);

  bin_file.put(object_name, 0, write_empty_hash.sofSize() + l.sofSize());
  write_empty_hash.writeData(bin_file, hash_name);
  l.writeData(bin_file);
  
  text_file.put(object_name, 1, -1);
  write_nested_hash.writeData(text_file, hash_name);
  l.writeData(text_file);

  bin_file.put(object_name, 1, write_empty_hash.sofSize() + l.sofSize());
  write_nested_hash.writeData(bin_file, hash_name);
  l.writeData(bin_file);

  // close the files
  //
  text_file.close();
  bin_file.close();

  // open the files in read mode
  //
  text_file.open(text_filename);
  bin_file.open(bin_filename);
  
  // create objects for reading in
  //
  HashTable<String, Char> read_char_htable_text;
  HashTable<String, Char> read_char_htable_bin;
  HashTable<String, String> read_str_htable_text;
  HashTable<String, String> read_str_htable_bin;
  HashTable< String, Vector<Char> > read_vec_char_htable_text;
  HashTable< String, Vector<Char> > read_vec_char_htable_bin;

  HashTable<String, Long> read_empty_text;
  HashTable<String, Long> read_empty_bin;
  
  // read in the hash tables and test for equivalence
  // if there is error, print out the hash tables
  //
  if (!read_char_htable_text.read(text_file, (long)0) ||
      (read_char_htable_text.ne(write_char_htable))) {

    write_char_htable.debug(L"write_char_htable");
    read_char_htable_text.debug(L"read_char_htable_text");
    return Error::handle(name(), L"read char text", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  if (!read_char_htable_bin.read(bin_file, (long)0) ||
      (read_char_htable_bin.ne(write_char_htable))) {

    write_char_htable.debug(L"write_char_htable");
    read_char_htable_bin.debug(L"read_char_htable_bin");
    return Error::handle(name(), L"read char bin", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  if (!read_str_htable_text.read(text_file, (long)0) ||
      (read_str_htable_text.ne(write_str_htable))) {

    write_str_htable.debug(L"write_str_htable");
    read_str_htable_text.debug(L"read_str_htable_text");
    return Error::handle(name(), L"read str text", Error::TEST, __FILE__, 
			 __LINE__);
  }
  
  if (!read_str_htable_bin.read(bin_file, (long)0) ||
      (read_str_htable_bin.ne(write_str_htable))) {

    write_str_htable.debug(L"write_str_htable");
    read_str_htable_bin.debug(L"read_str_htable_bin");
    return Error::handle(name(), L"read str bin", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  if (!read_vec_char_htable_text.read(text_file, (long)0) ||
      (read_vec_char_htable_text.ne(write_vec_char_htable))) {

    write_vec_char_htable.debug(L"write_vec_char_htable");
    read_vec_char_htable_text.debug(L"read_vec_char_htable_text");
    return Error::handle(name(), L"read vec_char text", Error::TEST, __FILE__,
			 __LINE__);
  }

  if (!read_vec_char_htable_bin.read(bin_file, (long)0) ||
      (read_vec_char_htable_bin.ne(write_vec_char_htable))) {

    write_vec_char_htable.debug(L"write_vec_char_htable");
    read_vec_char_htable_bin.debug(L"read_vec_char_htable_bin");
    return Error::handle(name(), L"read vec_char bin", Error::TEST, __FILE__,
			 __LINE__);
  }
  SofParser parser;

  if (!text_file.find(object_name, 0)) {
    return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
  }

  parser.load(text_file);
  if ((!read_empty_text.readData(text_file, hash_name,
				parser.getEntry(text_file, hash_name),
				false, true)) ||
      (read_empty_text.ne(write_empty_hash))) {

    read_empty_text.debug(L"read_empty_text");
    write_empty_hash.debug(L"write_empty_hash");
    return Error::handle(name(), L"read empty text", Error::TEST,
			 __FILE__, __LINE__);
  }
  Long read_l;
  if (!read_l.readData(text_file, Long::DEF_PARAM,
		       parser.getEntry(text_file, Long::DEF_PARAM)) ||
      read_l.ne(l)) {
    return Error::handle(name(), L"read empty text", Error::TEST,
			 __FILE__, __LINE__);
  }    

  
  if (!text_file.find(object_name, 1)) {
    return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
  }
  parser.reset();
  parser.load(text_file);
  if ((!read_empty_text.readData(text_file, hash_name,
				 parser.getEntry(text_file, hash_name),
				 false, true)) ||
      (read_empty_text.ne(write_nested_hash))) {

    read_empty_text.debug(L"read_nested_text");
    write_nested_hash.debug(L"write_nested_hash");
    return Error::handle(name(), L"read empty text", Error::TEST,
			 __FILE__, __LINE__);
  }
  if (!read_l.readData(text_file, Long::DEF_PARAM,
		       parser.getEntry(text_file, Long::DEF_PARAM)) ||
      read_l.ne(l)) {
    return Error::handle(name(), L"read empty text", Error::TEST,
			 __FILE__, __LINE__);
  }    

  if (!bin_file.find(object_name, 0)) {
    return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
  }

  if ((!read_empty_bin.readData(bin_file, hash_name)) ||
      (read_empty_bin.ne(write_empty_hash))) {

    read_empty_bin.debug(L"read_empty_bin");
    write_empty_hash.debug(L"write_empty_hash");
    return Error::handle(name(), L"read empty bin", Error::TEST,
			 __FILE__, __LINE__);
  }

  if (!read_l.readData(bin_file) ||
      read_l.ne(l)) {
    return Error::handle(name(), L"read empty bin ", Error::TEST,
			 __FILE__, __LINE__);
  }    

  
  if (!bin_file.find(object_name, 1)) {
    return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
  }

  if ((!read_empty_bin.readData(bin_file, hash_name)) ||
      (read_empty_bin.ne(write_nested_hash))) {

    read_empty_bin.debug(L"read_nested_bin");
    write_nested_hash.debug(L"write_nested_hash");
    return Error::handle(name(), L"read empty bin", Error::TEST,
			 __FILE__, __LINE__);
  }
  if (!read_l.readData(bin_file, Long::DEF_PARAM) ||
      read_l.ne(l)) {
    return Error::handle(name(), L"read empty bin", Error::TEST,
			 __FILE__, __LINE__);
  }    
  
  // close and delete the temporary files
  //
  text_file.close();
  bin_file.close();

  // cleanup the memory that was created by the HashTable read
  //
  for ( long i = 0; i < 10; i++) {
    delete write_chars[i];
    delete write_strings[i];
  }
  delete [] write_chars;
  delete [] write_strings;
  delete [] vec_chars;

  // test equality methods
  //
  HashTable<String, Char> char_ht_0(DstrBase::SYSTEM, (long)50);
  
  // initialize the characters and store them in the htables
  //
  for (long i = 0; i < num_elem; i++) {
    
    // add the character to the hash table
    //
    char_ht_0.insert(keys[i], items[i]);
  }

  // create another hash table the same as this
  //
  HashTable<String, Char> char_ht_1;
  char_ht_1.assign(char_ht_0);
  
  if (char_ht_0.getAllocationMode() != char_ht_1.getAllocationMode()) {
    return Error::handle(name(), L"diagnose", Error::TEST, __FILE__, __LINE__);
  }

  if (!char_ht_0.eq(char_ht_1)) {
    char_ht_0.debug(L"char_ht_0");
    char_ht_1.debug(L"char_ht_1");
    return Error::handle(name(), L"eq", Error::TEST, __FILE__, __LINE__);
  }
  
  if (char_ht_0.ne(char_ht_1)) {
    return Error::handle(name(), L"ne", Error::TEST, __FILE__, __LINE__);
  }

  // test equality methods
  //
  HashTable<String, Char> char_ht_2(DstrBase::USER, (long)30);
  
  // initialize the characters and store them in the htables
  //
  for (long i = 0; i < num_elem; i++) {
    
    // add the character to the hash table
    //
    char_ht_2.insert(keys[i], items[i]);
  }
  
  // create another hash table the same as this
  //
  HashTable<String, Char> char_ht_3;
  char_ht_3.assign(char_ht_2);

  if (char_ht_3.getAllocationMode() != char_ht_2.getAllocationMode()) {
    return Error::handle(name(), L"diagnose", Error::TEST, __FILE__, __LINE__);
  }
  
  if (!char_ht_2.eq(char_ht_3)) {
    char_ht_2.debug(L"char_ht_2");
    char_ht_3.debug(L"char_ht_3");
    return Error::handle(name(), L"eq", Error::TEST, __FILE__, __LINE__);
  }
  
  if (char_ht_2.ne(char_ht_3)) {
    return Error::handle(name(), L"ne", Error::TEST, __FILE__, __LINE__);
  }

    // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }

    
  // --------------------------------------------------------------------
  //
  // 2. testing class-specific public methods
  //     hash table manipulation methods
  //
  // --------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: hash table manipulation methods...\n");
    Console::increaseIndention(); 
  }

  if (char_ht_0.getNumItems() != num_elem) {
    return Error::handle(name(), L"put", Error::TEST, __FILE__, __LINE__);
  }
  
  // get the character by its key
  //
  for (long i = 0; i < num_elem; i++) {
    Char* tmp_char = char_ht_0.get(keys[i]);
    if (tmp_char->ne(*items[i])) {
      
      tmp_char->debug(L"get char");
      items[i]->debug(L"items[i]");
      return Error::handle(name(), L"get", Error::TEST, __FILE__, __LINE__);
    }
  }

  if (char_ht_2.getNumItems() != num_elem) {
    return Error::handle(name(), L"put", Error::TEST, __FILE__, __LINE__);
  }
  
  // get the character by its key
  //
  for (long i = 0; i < num_elem; i++) {
    Char* tmp_char = char_ht_2.get(keys[i]);
    if (tmp_char->ne(*items[i])) {
      
      tmp_char->debug(L"get char");
      items[i]->debug(L"items[i]");
      return Error::handle(name(), L"get", Error::TEST, __FILE__, __LINE__);
    }
  }
  

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }

  // --------------------------------------------------------------------
  //
  // 3. testing class-specific public methods
  //     hash table data access methods
  //
  // --------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: hash table data access methods...\n");
    Console::increaseIndention(); 
  }
  
  // get all the keys (SYSTEM mode)
  //
  Vector<String> keys_list;
  char_ht_0.keys(keys_list);
  long index;
  
  if (keys_list.length() != num_elem) {
    return Error::handle(name(), L"keys", Error::TEST, __FILE__, __LINE__);
  }

  // check if we have got all the keys
  //
  for (long i = 0; i < num_elem; i++) {
    if (!keys_list.contains(index, &keys[i])) {
      return Error::handle(name(), L"keys", Error::TEST, __FILE__, __LINE__);
    }
  }
  
  // get all the keys (USER mode)
  //
  char_ht_2.keys(keys_list);
  
  if (keys_list.length() != num_elem) {
    return Error::handle(name(), L"keys", Error::TEST, __FILE__, __LINE__);
  }

  // check if we have got all the keys
  //
  for (long i = 0; i < num_elem; i++) {
    if (!keys_list.contains(index, &keys[i])) {
      return Error::handle(name(), L"keys", Error::TEST, __FILE__, __LINE__);
    }
  }

  // get all the values (SYSTEM mode)
  //
  Vector<Char> chars_list;
  char_ht_0.values(chars_list);
  
  if (chars_list.length() != num_elem) {
    return Error::handle(name(), L"values", Error::TEST, __FILE__, __LINE__);
  }
  
  // check if we have get all the values
  // 
  for (long i = 0; i < num_elem; i++) {
    if (!chars_list.contains(index, items[i])) {
      return Error::handle(name(), L"values", Error::TEST, __FILE__, __LINE__);
    }
  } 

  // get all the values (USER mode)
  //
  char_ht_2.values(chars_list);
  
  if (chars_list.length() != num_elem) {
    return Error::handle(name(), L"values", Error::TEST, __FILE__, __LINE__);
  }
  
  // check if we have get all the values
  // 
  for (long i = 0; i < num_elem; i++) {
    if (!chars_list.contains(index, items[i])) {
      return Error::handle(name(), L"values", Error::TEST, __FILE__, __LINE__);
    }
  } 
  
  // test contains (SYSTEM)
  //
  for (long i = 0; i < num_elem; i++) {
    if (!char_ht_0.containsKey(keys[i])) {
      keys[i].debug(L"this key is not found");
      return Error::handle(name(), L"containsKey", Error::TEST, __FILE__,
			   __LINE__);
    }
    
    if (!char_ht_0.containsValue(items[i])) {
      items[i]->debug(L"this value is not found");
      return Error::handle(name(), L"containsValue", Error::TEST, __FILE__,
			   __LINE__);
    }
  }

  // test contains (USER)
  //
  for (long i = 0; i < num_elem; i++) {
    if (!char_ht_2.containsKey(keys[i])) {
      keys[i].debug(L"this key is not found");
      return Error::handle(name(), L"containsKey", Error::TEST, __FILE__,
			   __LINE__);
    }
    
    if (!char_ht_2.containsValue(items[i])) {
      items[i]->debug(L"this value is not found");
      return Error::handle(name(), L"containsValue", Error::TEST, __FILE__,
			   __LINE__);
    }
  }

  // test remove (SYSTEM)
  //
  Char* char_ptr = new Char();
  char_ht_0.remove(keys[0], char_ptr);
  
  if (char_ptr->ne(*items[0])) {
    return Error::handle(name(), L"remove", Error::TEST, __FILE__, __LINE__);
  }
  if (char_ht_0.containsKey(keys[0])) {
    return Error::handle(name(), L"remove / containsKey", Error::TEST,
			 __FILE__, __LINE__);
  }
  if (char_ht_0.containsValue(items[0])) {
    return Error::handle(name(), L"remove / containsValue", Error::TEST,
			 __FILE__, __LINE__);
  }
  delete char_ptr;
  
  char_ht_0.remove(keys[1]);
  if (char_ht_0.containsKey(keys[1])) {
    return Error::handle(name(), L"remove / containsKey", Error::TEST,
			 __FILE__, __LINE__);
  }
  if (char_ht_0.containsValue(items[1])) {
    return Error::handle(name(), L"remove / containsValue", Error::TEST,
			 __FILE__, __LINE__);
  }
  
  // test remove (USER)
  //
  char_ptr = (Char*)NULL;
  char_ht_2.remove(keys[0], char_ptr);
  
  if ((char_ptr == (Char*)NULL) || (char_ptr->ne(*items[0]))) {
    return Error::handle(name(), L"remove", Error::TEST, __FILE__, __LINE__);
  }
  if (char_ht_2.containsKey(keys[0])) {
    return Error::handle(name(), L"remove / containsKey", Error::TEST,
			 __FILE__, __LINE__);
  }
  if (char_ht_2.containsValue(items[0])) {
    return Error::handle(name(), L"remove / containsValue", Error::TEST,
			 __FILE__, __LINE__);
  }
  
  char_ht_2.remove(keys[1]);
  if (char_ht_2.containsKey(keys[1])) {
    return Error::handle(name(), L"remove / containsKey", Error::TEST,
			 __FILE__, __LINE__);
  }
  if (char_ht_2.containsValue(items[1])) {
    return Error::handle(name(), L"remove / containsValue", Error::TEST,
			 __FILE__, __LINE__);
  }

  // determine the occupancy of the htable (SYSTEM)
  //
  if (char_ht_0.isEmpty()) {
    return Error::handle(name(), L"isEmpty", Error::TEST, __FILE__, __LINE__);
  }

  // determine the occupancy of the htable (USER)
  //
  if (char_ht_2.isEmpty()) {
    return Error::handle(name(), L"isEmpty", Error::TEST, __FILE__, __LINE__);
  }
  
  // test clear (USER) -- first make a copy of char_ht_0 in system mode
  //
  char_ht_0.setAllocationMode(DstrBase::USER);
  char_ht_2.assign(char_ht_0);
  char_ht_0.setAllocationMode(DstrBase::SYSTEM);

  char_ht_2.clear();
  if (!char_ht_2.isEmpty()) {
    return Error::handle(name(), L"clear", Error::TEST, __FILE__, __LINE__);
  }
  
  // test clear (SYSTEM)
  //
  char_ht_0.clear();

  // create a hash table whose capacity* load_factor is less than 20 (SYSTEM)
  //
  HashTable<String, Char> tmp_htable_4(DstrBase::SYSTEM, (long)20);
  
  // put characters in the hash table
  //
  for (long i = 0; i < num_elem; i++) {
    
    // add the character to the htable
    //
    tmp_htable_4.insert(keys[i], items[i]);
  }

  // create a hash table whose capacity* load_factor is less than 20 (USER)
  //
  HashTable<String, Char> tmp_htable_7(DstrBase::USER, (long)20);

  tmp_htable_7.setLoadFactor(0.5);
  
  // put characters in the hash table
  //
  for (long i = 0; i < num_elem; i++) {
    
    // add the character to the htable
    //
    tmp_htable_7.insert(keys[i], items[i]);
  }

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  // --------------------------------------------------------------------
  //
  // 4. testing class-specific public methods
  //     hash table property methods
  //
  // --------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: hash table property methods...\n");
    Console::increaseIndention(); 
  }
  
  // the capacity should have be increased automatically
  //
  if (tmp_htable_4.getCapacity() != 40) {
    tmp_htable_4.debug(L"rehash tmp_htable_4");
    return Error::handle(name(), L"rehash", Error::TEST, __FILE__, __LINE__);
  }
  
  // check if all the keys are in the rehashed table
  //
  for (long i = 0; i < num_elem; i++) {
    if (!tmp_htable_4.containsKey(keys[i])) {
      return Error::handle(name(), L"rehash", Error::TEST, __FILE__,
			   __LINE__);
    }
  }
  
  // clear the table
  //
  tmp_htable_4.clear(); 
  
  if (tmp_htable_4.getNumItems() != 0) {
    return Error::handle(name(), L"clear", Error::TEST, __FILE__, __LINE__);
  }

  // the capacity should have be increased automatically
  //
  if (tmp_htable_7.getCapacity() != 40) {
    tmp_htable_7.debug(L"rehash tmp_htable_7");
    return Error::handle(name(), L"rehash", Error::TEST, __FILE__, __LINE__);
  }
  
  // check if all the keys are in the rehashed table
  //
  for (long i = 0; i < num_elem; i++) {
    if (!tmp_htable_7.containsKey(keys[i])) {
      return Error::handle(name(), L"rehash", Error::TEST, __FILE__,
			   __LINE__);
    }
  }
  
  // clear the table
  //
  tmp_htable_7.clear();
  
  if (tmp_htable_7.getNumItems() != 0) {
    return Error::handle(name(), L"clear", Error::TEST, __FILE__, __LINE__);
  }

  // preform some stress test on the hash table in SYSTEM mode
  //
  HashTable<String, Long> int_hash;
  long int_hash_size = 1000;
  
  for (long i = 0; i < int_hash_size; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);
    
    Long int_obj(i * 3);
    
    int_hash.insert(key, &int_obj);
  }

  // find out the maximum number of elements in one hash
  //
  double max = 0;
  for (long i = 0; i < int_hash.getCapacity(); i++) {
    max = Integral::max(max, (double)int_hash.table_d(i).length());
  }

  // make sure that the maximum search length is two orders of
  // magnitude less than the number of elements
  //
  if (max > ((double)int_hash_size * 0.01)) {
    return Error::handle(name(), L"hash", Error::TEST, __FILE__, __LINE__);
  }
  
  // read the values back out of the hash
  //
  for (long i = 0; i < int_hash_size; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);

    if (!int_hash.get(key)->eq(i * 3)) {
      return Error::handle(name(), L"get", Error::TEST,
			   __FILE__, __LINE__);
    }
  }

  // remove things from the hash and check that the size decreases
  //
  if (int_hash.getCapacity() != 2048) {
    return Error::handle(name(), L"rehash", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  for (long i = 0; i < 850; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);

    if (!int_hash.remove(key)) {
      return Error::handle(name(), L"get", Error::TEST,
			   __FILE__, __LINE__);
    }
  }

  if (int_hash.getCapacity() != 1024) {
    return Error::handle(name(), L"rehash", Error::TEST, __FILE__, __LINE__);
  }

  for (long i = 850; i < 900; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);

    if (!int_hash.remove(key)) {
      return Error::handle(name(), L"get", Error::TEST,
			   __FILE__, __LINE__);
    }
  }

  if (int_hash.getCapacity() != 512) {
    return Error::handle(name(), L"rehash", Error::TEST, __FILE__, __LINE__);
  }

  for (long i = 900; i < 975; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);

    if (!int_hash.remove(key)) {
      return Error::handle(name(), L"get", Error::TEST,
			   __FILE__, __LINE__);
    }
  }

  if (int_hash.getCapacity() != 128) {
    return Error::handle(name(), L"rehash", Error::TEST, __FILE__, __LINE__);
  }

  for (long i = 975; i < 999; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);

    if (!int_hash.remove(key)) {
      return Error::handle(name(), L"get", Error::TEST,
			   __FILE__, __LINE__);
    }
  }

  if (int_hash.getCapacity() != 128) {
    return Error::handle(name(), L"rehash", Error::TEST, __FILE__, __LINE__);
  }

  // preform some stress test on the hash table in USER mode
  //
  int_hash.clear();
  int_hash.setCapacity(16);
  int_hash.setAllocationMode(DstrBase::USER);
    
  for (long i = 0; i < int_hash_size; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);
    
    Long* int_obj = new Long(i * 3);
    int_hash.insert(key, int_obj);
  }
  
  // read the values back out of the hash
  //
  for (long i = 0; i < int_hash_size; i++) {
    String key;
    key.assign(i);
    key.insert(L"key of ", 0);
    
    if (!int_hash.get(key)->eq(i * 3)) {
      return Error::handle(name(), L"get", Error::TEST,
			   __FILE__, __LINE__);
    }
  }

  // cleanup the USER mode hashtable
  //
  int_hash.clear(Integral::FREE);
  
  // cleanUp
  //
  for (long i = 0; i < num_elem; i++) {
    delete items[i];
  }

  delete [] items;
  delete [] keys;
  
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //---------------------------------------------------------------------
  //
  // 5. print completion message
  //
  //---------------------------------------------------------------------

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  if (level_a > Integral::NONE) {
    SysString output(L"diagnostics passed for class ");
    output.concat(name());
    output.concat(L"\n");
    Console::put(output);
  }
  
  // exit gracefully
  //
  return true;
}

// end of include file
//
#endif
