// file: $isip/class/io/Sof/sof_02.cc
// version: $Id: sof_02.cc 8121 2002-04-17 20:22:34Z huang $
//

// isip include files
//
#include "Sof.h"
#include <Console.h>

// method: diagnose
//
// arguments:
//  Integral::DEBUG level: (input) debug level for diagnostics
//
// return: a boolean value indicating status
//
boolean Sof::diagnose(Integral::DEBUG level_a) {

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

  // output the class name
  //
  if (level_a > Integral::NONE) {
    SysString output(L"diagnosing class ");
    output.concat(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 constructor
  //
  Sof sof_0;
  
  // test debug methods
  //
  sof_0.setDebug(Integral::BRIEF);
  
  if (level_a > Integral::BRIEF) {
    sof_0.debug(L"debug");
  }

  // open a file in write mode
  //
  SysString file0;
 
  Integral::makeTemp(file0);
  File::registerTemp(file0);
  
  if (!sof_0.open(file0, File::WRITE_ONLY, File::BINARY)) {
    return Error::handle(name(), L"open", Error::TEST, __FILE__, __LINE__);
  }
  
  // test clear methods, note clear is only used for writable Sof
  //
  sof_0.clear();

  if (sof_0.getNameCount() != 0) {
    return Error::handle(name(), L"clear", Error::TEST, __FILE__, __LINE__);
  }       

  // close file
  //
  sof_0.close();
  File::remove(file0);
    
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //--------------------------------------------------------------------------
  //
  // 2. class-specific public methods:
  //     extensions to required public methods
  //
  //--------------------------------------------------------------------------
  
  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: extensions to required methods...\n");
    Console::increaseIndention();
  }
  
  // get the memsize
  //
  long mem_size = sof_0.memSize();
  
  if (level_a > Integral::BRIEF) {
    SysString numeric;
    numeric.assign(mem_size);
    SysString output(L"memSize of sof_0: ");
    output.concat(numeric);
    Console::put(output);
  }
    
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }

  //--------------------------------------------------------------------------
  //
  // 3. class-specific public methods:
  //     file operation methods
  //
  //--------------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: file operation methods...\n");
    Console::increaseIndention();
  }
  
  Sof sof0;
  Sof sof1;
  Sof sof2;
  Sof sof3;
  
  // create some temporary filenames
  //
  SysString file1;
  SysString file2;
  SysString file3;
  
  SysString long_cname(L"Long");
  SysString short_cname(L"Short");
  SysString string_cname(L"String");
  
  Integral::makeTemp(file0);
  File::registerTemp(file0);
  
  // test a bin file
  Integral::makeTemp(file1);
  File::registerTemp(file1);
  

  // test a ascii file
  Integral::makeTemp(file2);
  File::registerTemp(file2);
    
  Integral::makeTemp(file3);
  File::registerTemp(file3);
  
  // open files for write (text & binary)
  //
  if (!sof0.open(file0, File::WRITE_ONLY)) {
    return Error::handle(name(), L"open", Error::TEST, __FILE__, __LINE__);
  }
  if (!sof1.open(file1, File::WRITE_ONLY, File::BINARY)) {
    return Error::handle(name(), L"open", Error::TEST, __FILE__, __LINE__);
  }

  if (sof0.getName().ne(file0)) {
    return Error::handle(name(), L"getName", Error::TEST, __FILE__, __LINE__);
  }

  if (sof0.getExpandedName().ne(file0)) {
    return Error::handle(name(), L"getExpandedName", Error::TEST, __FILE__,
			 __LINE__);
  }

  // add a long entry to the files
  //
  long j = 27;
  sof0.put(long_cname, Sof::ANY_SIZE);
  sof1.put(long_cname, 0, sizeof(long));
  
  // write the text value
  //
  sof0.increaseIndention();
  sof0.setLineWrap(100);
  sof0.decreaseIndention();
  
  SysString output;
  output.assign((long)j);
  output.insert(L"value = ", 0);
  output.concat(L"\n");
  sof0.puts(output);

  // write the binary value
  //
  sof1.write(&j, sizeof(j), 1);
  
  // add another long entry to the files
  //
  j = 277;
  sof0.put(long_cname, Sof::ANY_SIZE);
  sof1.put(long_cname, 1, sizeof(long));
  
  // write the text value
  //
  output.assign((long)j);
  output.insert(L"value = ", 0);
  output.concat(L"\n");
  sof0.puts(output);

  // write the binary value
  //
  sof1.write(&j, sizeof(j), 1);
  
  // print out debug information
  //
  if (level_a > Integral::DETAILED) {
    sof1.setDebug(Integral::NONE);
    sof1.debug(L"sof1");
  }

  // test set and get methods
  //
  sof0.setLockRetry((long)1);
  sof0.setDelimiter(DEF_DELIMITER);

  if (sof0.getDelimiter() != DEF_DELIMITER) {
    return Error::handle(name(), L"set/getDelimiter", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  // test file queries
  //
  if (!sof0.isSof() || !isSof(file0)) {
    return Error::handle(name(), L"isSof", Error::TEST, __FILE__, __LINE__);
  }
  
  if (!sof0.isText() || !sof1.isBinary()) {
    return Error::handle(name(), L"isText / isBinary", Error::TEST, __FILE__,
			 __LINE__);
  }

  if (!sof0.isOpen() || !sof1.isOpen()) {
    return Error::handle(name(), L"isOpen", Error::TEST, __FILE__, __LINE__);
  }
  
  if (sof0.isSameFile(sof1)) {
    return Error::handle(name(), L"isSameFile", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  // test clear the file
  //                    
  sof1.clear();

  if (sof1.getNameCount() != 0) {
    return Error::handle(name(), L"clear", Error::TEST, __FILE__, __LINE__);
  }       
  
  // close the files
  //
  sof0.close();
  sof1.close();
  
  if (sof0.isOpen() || sof1.isOpen()) {
    return Error::handle(name(), L"isOpen", Error::TEST, __FILE__, __LINE__);
  }
  
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }

  //--------------------------------------------------------------------------
  //
  // 4. class-specific public methods:
  //     object operation methods
  //
  //--------------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: object operation methods...\n");
    Console::increaseIndention();
  }
  
  // open files again in read plus
  //
  if (!sof2.open(file0, File::READ_PLUS)) {
    return Error::handle(name(), L"diagnose", Error::ARG, __FILE__, __LINE__);
  }
  
  if (!sof3.open(file1, File::READ_PLUS, File::BINARY)) {
    return Error::handle(name(), L"diagnose", Error::ARG, __FILE__, __LINE__);
  }
  
  // read all the text values
  //
  long tag;
  for (tag = sof2.first(long_cname); tag != Sof::NO_TAG;
       tag = sof2.next(long_cname, tag)) {
    
    // find the node
    //
    sof2.find(long_cname, tag);
    
    SysString input;

    // this gets should work
    //
    if (!sof2.gets(input)) {
      return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
    }
    
    if (input.firstStr(L"value = ") != 0) {
      return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
    }
    SysString numeric;
    input.substr(numeric, 8);
    if (!numeric.get(j)) {
      return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
    }
    
    // we have read all the data on the line, so this gets should fail
    //
    if (sof2.gets(input)) {
      return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
    }

    if (level_a > Integral::BRIEF) {
      output.assign((long)j);
      output.insert(L"reading from text: ", 0);
      Console::put(output);
    }
  }
  
  // read all the binary values
  //
  for (tag = sof3.last(long_cname); tag != Sof::NO_TAG;
       tag = sof3.prev(long_cname, tag)) {

    // find the node
    //
    sof3.find(long_cname, tag);

    if (sof3.read(&j, sizeof(j), 1) != 1) {
      return Error::handle(name(), L"read", Error::TEST, __FILE__, __LINE__);
    }
    
    if (level_a > Integral::BRIEF) {
      output.assign((long)j);
      output.insert(L"reading from Binary: ", 0);
      Console::put(output);
    }
  }
  
  long NUM_CLASSES = 10;               
  long NUM_TAGS = 3;
  
  // sof3.setDebug(Integral::DETAILED);
  // sof0.setDebug(Integral::DETAILED);
  
  // add a whole bunch of names to the binary file
  //
  for (long i = 0; i < NUM_CLASSES; i++) {

    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);

    // add multiple nodes
    //
    for (long j = 0; j < NUM_TAGS; j++) {

      // add the node
      //
      sof3.put(cname, j, sizeof(long));

      // create and write a value
      //
      long val = i * 10277 + j * 13;
      sof3.write(&val, sizeof(val), 1);
    }
  }
  
  // test copy methods
  //
  if (level_a > Integral::BRIEF) {
    Console::put(L"testing copy (text)");
  }

  SysString foo_copy;
  Integral::makeTemp(foo_copy);
  File::registerTemp(foo_copy);
  
  sof1.open(foo_copy, File::WRITE_ONLY, File::TEXT);
  tag = sof2.first(long_cname);
  
  if (tag != Sof::NO_TAG) {
    
    // copy the first Long number from sof2
    //
    sof1.copy(0, sof2, long_cname, tag);
    
    if (sof1.getCount(long_cname) != 1) {
      return Error::handle(name(), L"copy", Error::TEST, __FILE__, __LINE__);
    } 
    
    // remove this tag
    //
    sof1.remove(long_cname, 0);
    
    // copy all the Long numbers from sof2
    //
    sof1.copy(sof2, long_cname);
    if (sof1.getCount(long_cname) != sof2.getCount(long_cname)) {
      return Error::handle(name(), L"copy", Error::TEST, __FILE__, __LINE__);
    }
  }
  
  // close the files
  //
  sof1.close();
  sof2.close();
  sof3.close();

  // open the binary file again
  //
  sof0.open(file1, File::READ_PLUS, File::BINARY);

  long count = 0;
  
  // read all the dynamically named nodes
  //
  for (long i = 0; i < NUM_CLASSES; i++) {
    
    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);

    // read all the nodes of this name
    //
    for (tag = sof0.first(cname); tag != Sof::NO_TAG;
	 tag = sof0.next(cname, tag)) {
      
      // find the node
      //
      if (!sof0.find(cname, tag)) {
	return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
      }
      
      long val = i * 10277 + tag * 13;
      long j;
      count++;
      
      if (sof0.read(&j, sizeof(j), 1) != 1) {
	return Error::handle(name(), L"read", Error::TEST, __FILE__, __LINE__);
      }

      if (j != val) {
	return Error::handle(name(), L"mismatch", Error::TEST, __FILE__,
			     __LINE__);
      }
    }
  }

  // check count
  //
  if (count != NUM_CLASSES * NUM_TAGS) {
    return Error::handle(name(), L"not enough", Error::TEST, __FILE__,
			 __LINE__);
  }

  // test delete
  //
  if (level_a > Integral::BRIEF) {
    Console::put(L"testing remove (binary)");
  }
  
  // go back through, delete every other entry
  //
  for (long i = 0; i < NUM_CLASSES; i += 2) {
    
    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);
    
    if ((i % 4) == 0) {
      // read all the nodes of this name
      //
      for (tag = sof0.first(cname); tag != Sof::NO_TAG;
	   tag = sof0.next(cname, tag)) {
	
	// remove node
	//
	if (!sof0.remove(cname, tag)) {
	  return Error::handle(name(), L"remove1", Error::TEST,
			       __FILE__, __LINE__);
	}
      }
    }
    
    // delete all instances of this name
    //
    else if (!sof0.remove(cname)) {
      return Error::handle(name(),L"remove2",Error::TEST,__FILE__,__LINE__);
    }
    
    if (sof0.getCount(cname) != 0) {
      return Error::handle(name(), L"remove", Error::TEST, __FILE__, __LINE__);
    }
  }
  
  sof0.cleanUp();
  
  if (sof0.getCount() != 0) {
    return Error::handle(name(), L"remove", Error::TEST, __FILE__, __LINE__);
  }
  
  sof0.close();

  // testing read after delete
  //
  if (level_a > Integral::BRIEF) {
    SysString output(L"testing read after delete (binary)");
    Console::put(output);
  }

  // open the file again
  //
  sof0.open(file1);
  
  count = 0;
  
  // read all the dynamically named nodes
  //
  for (long i = 1; i < NUM_CLASSES; i += 2) {

    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);
    
    // read all the nodes of this name
    //
    for (tag = sof0.first(cname); tag != Sof::NO_TAG;
	 tag = sof0.next(cname, tag)) {
      
      // find the node
      //
      if (!sof0.find(cname, tag)) {
	return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
      }
      
      long val = i * 10277 + tag * 13;
      long j;
      count++;
      
      if (sof0.read(&j, sizeof(j), 1) != 1) {
	return Error::handle(name(), L"read", Error::TEST, __FILE__, __LINE__);
      }

      if (j != val) {
	return Error::handle(name(),L"mismatch",Error::TEST,__FILE__,__LINE__);
      }
    }
  }

  if (count != NUM_CLASSES * NUM_TAGS / 2) {
    return Error::handle(name(),L"not enough 2",Error::TEST,__FILE__,__LINE__);
  }
  
  // close the file
  //
  sof0.close();
  
  // add a whole bunch of names to the text file
  //
  NUM_CLASSES = 20;
  
  sof2.open(file2, File::WRITE_PLUS);
  
  for (long i = 0; i < NUM_CLASSES; i++) {
    
    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);
    
    // add multiple nodes
    //
    for (long j = 0; j < NUM_TAGS; j++) {

      // add the node
      //
      sof2.put(cname, j, ANY_SIZE);

      // create and write a value
      //
      long val = i * 10277 + j * 13;
      
      // write the text value
      //
      output.assign((long)val);
      output.insert(L"value = ", 0);
      output.concat(L"\n");
      sof2.puts(output);
    }
  }

  // close the files
  //
  sof2.close();
  
  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //--------------------------------------------------------------------------
  //
  // 5. class-specific public methods:
  //     input and output methods
  //
  //--------------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: i/o methods...\n");
    Console::increaseIndention();
  }
  
  // open the text file again
  //
  sof0.open(file2, File::READ_PLUS);

  count = 0;

  SysString input;
  
  // read all the dynamically named nodes
  //
  for (long i = 0; i < NUM_CLASSES; i++) {

    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);
    
    // read all the nodes of this name
    //
    for (tag = sof0.first(cname); tag != Sof::NO_TAG;
	 tag = sof0.next(cname, tag)) {
      
      // find the node
      //
      if (!sof0.find(cname, tag)) {
	return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
      }

      long val = i * 10277 + tag * 13;
      long j;
      count++;
      
      if ((!sof0.gets(input)) || (input.firstStr(L"value = ") != 0)) {
	return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
      }
      SysString numeric;
      input.substr(numeric, 8);

      if (!numeric.get(j)) {
	return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
      }
      
      if (j != val) {
	return Error::handle(name(),L"mismatch",Error::TEST,__FILE__,__LINE__);
      }
    }
  }

  // check count
  //
  if (count != NUM_CLASSES * NUM_TAGS) {
    return Error::handle(name(), L"not enough",Error::TEST,__FILE__,__LINE__);
  }

  // test delete
  //
  if (level_a > Integral::BRIEF) {
    SysString output(L"testing remove (text)");
    Console::put(output);
  }
  
  // go back through, delete every other entry
  //
  for (long i = 0; i < NUM_CLASSES; i += 2) {
    
    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);
    
    if ((i % 4) == 0) {
      // read all the nodes of this name
      //
      for (tag = sof0.first(cname); tag != Sof::NO_TAG;
	   tag = sof0.next(cname, tag)) {
	
	// remove node
	//
	if (!sof0.remove(cname, tag)) {
	  return Error::handle(name(), L"remove1", Error::TEST,
			       __FILE__, __LINE__);
	}
      }
    }
    
    // delete all instances of this name
    //
    else if (!sof0.remove(cname)) {
      return Error::handle(name(), L"remove2", Error::TEST, __FILE__,
			   __LINE__);
    }
  }
  
  sof0.close();
  
  // testing read after delete
  //
  if (level_a > Integral::BRIEF) {
    Console::put(L"testing read after delete (text)");
  }
  
  // open the file again
  //
  sof0.open(file2);

  count = 0;
  
  // read all the dynamically named nodes
  //
  for (long i = 1; i < NUM_CLASSES; i += 2) {
    
    // create a name
    //
    SysString cname;
    cname.assign((long)i);
    cname.insert(L"LongerNamedEntity", 0);
    
    // read all the nodes of this name
    //
    for (tag = sof0.first(cname); tag != Sof::NO_TAG;
	 tag = sof0.next(cname, tag)) {
      
      // find the node
      //
      if (!sof0.find(cname, tag)) {
	return Error::handle(name(), L"find", Error::TEST, __FILE__, __LINE__);
      }
      
      long val = i * 10277 + tag * 13;
      long j;
      count++;

            
      if ((!sof0.gets(input)) || (input.firstStr(L"value = ") != 0)) {
	return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
      }
      SysString numeric;
      input.substr(numeric, 8);

      if (!numeric.get(j)) {
	return Error::handle(name(), L"gets", Error::TEST, __FILE__, __LINE__);
      }
      
      if (j != val) {
	return Error::handle(name(), L"mismatch", Error::TEST,
			     __FILE__, __LINE__);
      }
    }
  }
  
  if (count != NUM_CLASSES * NUM_TAGS / 2) {
    return Error::handle(name(), L"not enough 2", Error::TEST, __FILE__,
			 __LINE__);
  }

  // test enumerate method
  //
  SofList sof0_index;
  SofSymbolTable sof0_table;
  if (!sof0.enumerate(sof0_index, sof0_table)) {
    return Error::handle(name(), L"enumerate", Error::TEST, __FILE__,
			 __LINE__);
  }
  
  // close the file
  //
  sof0.close();

  // test overwriting in binary w/resize
  //
  {
    SysString temp;
    Integral::makeTemp(temp);
    Sof sof;
    sof.open(temp, File::WRITE_ONLY, File::BINARY);
    SysString obj_name(L"MyObject");
    sof.put(obj_name, 0, sizeof(long));
    long a = 5;
    sof.write(&a, sizeof(long), 1);
    sof.close();

    
    sof.open(temp, File::READ_PLUS, File::BINARY);
    long pos = sof.end_of_data_d;
    sof.put(obj_name, 0, sizeof(long)*2);
    sof.write(&a, sizeof(long), 1);
    sof.write(&a, sizeof(long), 1);
    if (sof.end_of_data_d != (pos + (long)sizeof(long))) {
      return Error::handle(name(), L"diagnose", Error::TEST,
			   __FILE__, __LINE__);
    }
    sof.close();
  }

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //--------------------------------------------------------------------------
  //
  // 6. class-specific public methods:
  //     formatting methods
  //
  //--------------------------------------------------------------------------

  // set indentation
  //
  if (level_a > Integral::NONE) {
    Console::put(L"testing class-specific public methods: formatting methods...\n");
    Console::increaseIndention();
  }
  
  // open the file again
  //
  sof0.open(file0, File::WRITE_ONLY);
    
  // increase indentation
  //
  sof0.increaseIndention();
  sof0.setLineWrap(100);
  
  if (sof0.getLineWrap() != 100) {
    return Error::handle(name(), L"set/getLineWrap", Error::TEST,
			 __FILE__, __LINE__);
  }
  if (sof0.getLineLength() != 100 - (File::INDENT_STR).length()) {
    return Error::handle(name(), L"set/getLineLength", Error::TEST,
			 __FILE__, __LINE__);
  }
  
  // decrease indentation
  //
  sof0.decreaseIndention();
  
  if (sof0.getLineLength() != 100) {
    return Error::handle(name(), L"set/getLineLength", Error::TEST,
			 __FILE__, __LINE__);
  }
  
  sof0.close();
  
  // delete temporary files
  //
  //File::remove(file0);
  //File::remove(file1);
  //File::remove(L"/tmp/foo_asc.sof");
  //File::remove(L"/tmp/foo_copy.sof");

  // reset indentation
  //
  if (level_a > Integral::NONE) {
    Console::decreaseIndention();
  }
  
  //---------------------------------------------------------------------------
  //
  // 7. 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;
}
