// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: main.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: 09/18/1997  
// Date Last Modified: 03/18/1999
// Copyright (c) 1997 Douglas M. Gaer
// ----------------------------------------------------------- // 
// ------------- Program Description and Details ------------- // 
// ----------------------------------------------------------- // 
/*
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.

This is a test program used to test the (P)ersistent base class
in a practical database application.
*/
// ----------------------------------------------------------- // 

#include <fstream.h> // Using ofstream to print to a text file
#include <ctype.h>
#include "mtank.h"
#include "terminal.h"
#include "config.h"
#include "timer.h"
#include "strutil.h"
#include "asprint.h"
#include "pscript.h"
#include "version.h"
#include "mtank_sh.h"
#include "dbconfig.h"
#include "vbdstats.h"

static POD *DB;                // Global database pointer
const int KeyBuf = 25;         // Users input limit per string
const char *WildCard = "*";    // Used for wild card searches 
const int retrys = 3;          // Times to retry user input operations
const int sbuffer_len = 255;   // Format string buffer lenght

// Define configurable parameters
int CacheSize = 15;  // Memory cache size for the index file
int AdminRights = 1; // Define user privileges 

// Define file access modes used in the application
VBDFile::AccessMode RWMode = VBDFile::READWRITE;
VBDFile::AccessMode ROMode = VBDFile::READONLY;

// Menu menu setup
typedef void (*MMF) (); // Pointer to (M)ain (M)enu (F)unctions
const int MMSelections = 5; // Main menu selections

// Main menu functions
void Exit();
void ISRMenu();     // Insertion/Removal menu
void FindMenu();    // Query menu
void DisplayMenu(); // Display menu
void FileMenu();    // File operations menu

const char *mainmenu[MMSelections] = {
  "(0) - Exit this program",
  "(1) - Add, Remove, or Change items in the database",
  "(2) - Find items in the database",
  "(3) - Display items in the database",
  "(4) - File Operations",
};

MMF MMFunctions[MMSelections] = {
  &Exit,
  &ISRMenu,
  &FindMenu,
  &DisplayMenu,
  &FileMenu,
};

// Insertion/Removal menu setup
typedef void (*ISRMF) (); // Pointer to (A)dd (M)enu (F)unctions 
const char *ISRMTitle = "--------- DATABASE INSERTION/REMOVAL MENU ---------";
const int ISRMSelections = 2; // Insertion/Removal menu selections

// Insertion/Removal menu functions
void Return();
void Add();

const char *isrmenu[ISRMSelections] = {
  "(0) - Return to main menu",
  "(1) - Add items to the database",
};

ISRMF ISRMFunctions[ISRMSelections] = {
  &Return,
  &Add
};

// Find menu setup
typedef void (*FMF) (); // Pointer to (F)ind (M)enu (F)unctions 
const char *FMTitle = "---------- DATABASE QUERY MENU ----------";
const int FMSelections = 3; // Find menu selections

// Find menu functions
void Return();
void FindByTestDate();
void FindByComments();

const char *findmenu[FMSelections] = {
  "(0) - Return to main menu",
  "(1) - Find objects by Test Date",
  "(2) - Find objects by Comments",
};

FMF FMFunctions[FMSelections] = {
  &Return,
  &FindByTestDate,
  &FindByComments
};

// Display menu setup
typedef void (*DMF) (); // Pointer to (D)isplay (M)enu (F)unctions 
const char *DMTitle = "---------- DATABASE DISPLAY MENU ----------";
const int DMSelections = 2; // Display menu selections

// Display menu functions
void DisplayAll(); // Display all objects one at a time
  
const char *displaymenu[DMSelections] = {
  "(0) - Return to main menu",
  "(1) - Display all items one a time"
};

DMF DMFunctions[DMSelections] = {
  &Return,
  &DisplayAll
};

// File operations menu setup
typedef void (*FOMF) (); // Pointer to (F)ile (M)enu (F)unctions 
const char *FOMTitle = "---------- DATABASE FILE OPERATIONS MENU ----------";
const int FOMSelections = 2; // File operations menu selections

// File operations menu functions
void FileStats();

const char *filemenu[FOMSelections] = {
  "(0) - Return to main menu",
  "(1) - Display Variable Block Database statistics"
};

FOMF FOMFunctions[FOMSelections] = {
  &Return,
  &FileStats
};

// Function prototypes for functions use by all menu functions
void AnalyzeWaterTest(Coords *p, MarineTank &mtank);
void ChangeObjectDisplay(MarineTank &mtank, Coords *p);
void DisplayMultiText(const char *title, char *str, Coords *p);
char *MultiLineInput(const char *title, Coords *p);
void AddObject(MarineTank &mtank, Coords *p);
void ChangeObject(MarineTank &mtank, Coords *p);
void DisplayObject(MarineTank &mtank, Coords *p);

// Search functions
void FindBy(const char *MemberName, MTankDBItem item);
void MTankDBSearch(MarineTank &mtank, MTankDBItem item, UString &str,
		 Coords *p, const char *wildcard = 0);

void MainMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(MMTitle));
    p->SetY(terminal->ScreenCenter(MMSelections));
    terminal->ClearScreen();
    terminal->Write(MMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < MMSelections; i++)
      terminal->Write(mainmenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*MMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*MMFunctions[1]) (); // Call the appropriate menu function
	break;
      case '2':
	(*MMFunctions[2]) (); // Call the appropriate menu function
	break;
      case '3':
	(*MMFunctions[3]) (); // Call the appropriate menu function
	break;
      case '4':
	(*MMFunctions[4]) (); // Call the appropriate menu function
	break;
      default:
	break;
    }
  }
}

void ISRMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(ISRMTitle));
    p->SetY(terminal->ScreenCenter(ISRMSelections));
    terminal->ClearScreen();
    terminal->Write(ISRMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < ISRMSelections; i++)
      terminal->Write(isrmenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*ISRMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*ISRMFunctions[1]) (); // Call the appropriate menu function
	break;
      default:
	break;
    }
  }
}

void FindMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(FMTitle));
    p->SetY(terminal->ScreenCenter(FMSelections));
    terminal->ClearScreen();
    terminal->Write(FMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < FMSelections; i++)
      terminal->Write(findmenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*FMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*FMFunctions[1]) (); // Call the appropriate menu function
	break;
      case '2':
	(*FMFunctions[2]) (); // Call the appropriate menu function
	break;
      default:
	break;
    }
  }
}

void DisplayMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(DMTitle));
    p->SetY(terminal->ScreenCenter(DMSelections));
    terminal->ClearScreen();
    terminal->Write(DMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < DMSelections; i++)
      terminal->Write(displaymenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*DMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*DMFunctions[1]) (); // Call the appropriate menu function
	break;
	
      default:
	break;
    }
  }
}

void FileMenu()
{
  int c;
  Coords *p = new Coords(0, 0);

  while(1) {
    p->SetXY(0, 0);
    p->SetX(terminal->Center(FOMTitle));
    p->SetY(terminal->ScreenCenter(FOMSelections));
    terminal->ClearScreen();
    terminal->Write(FOMTitle, p->XPos(), p->YPrev());

    for(int i = 0; i < FOMSelections; i++)
      terminal->Write(filemenu[i], p->XPos(), p->YOffset(1));
    
    terminal->Write("Press the number of your selection >",
		    p->XPos(), p->YOffset(2));
    
    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	(*FOMFunctions[0]) (); // Call the appropriate menu function
	return;
      case '1':
	(*FOMFunctions[1]) (); // Call the appropriate menu function
	break;

      default:
	break;
    }
  }
}

void Exit()
{
  // Nothing to do
}

void Return()
{
  // Nothing to do
}

void DisplayAll()
{
  if(DB->RebuildIndex()) {
    terminal->Write("The index file needs to be rebuilt.", 0, 1);
    terminal->AnyKey(0, 2);
    return;
  }

  MarineTank mtank(DB);
  Coords *p = new Coords(0, 0);
  int c;

  terminal->ClearScreen();      
  dllist->Clear();
  
  // Load the index keys into memory
  CachePointer n = DB->Index()->GetRoot();
  rbtree->Clear();
  BtreeWalk(n, LoadKeys, DB->Index());

  BNodeBase *nxt; // BSTree base node pointer

  // Using iterator object to walk throught the rbtree
  TreeWalkerb tw(rbtree->GetRoot(), INORDER);
  
  // Walk through the rbtree using an iterator object
  while((nxt = tw.Next()) != 0) {
    RBNode<MTankInMemCopy> *ptr = (RBNode<MTankInMemCopy> *)nxt;
    dllist->StoreNode(ptr->Data);
  }

  rbtree->Clear(); // Clear the rbtree

  dllistptr = dllist->GetFront();
  while(!dllist->IsHeader(dllistptr)) {
    terminal->ClearScreen();      
    p->SetXY(0, 0); // Reset x and y positions
    mtank.ReadObject(dllistptr->Data.object_address);
    DisplayObject(mtank, p);
    terminal->Write("(A)-Analyze, (D)-Delete, (X)-Exit",
		    p->XPos(), p->YOffset(2));
    terminal->Write(", Any other key to continue");
    c = terminal->GetChar();
    switch(tolower(c)) {
      case 'a': 
	terminal->ClearScreen();
	p->SetXY(0, 0);
	AnalyzeWaterTest(p, mtank);
	break;
	
      case 'd': {
	int yn = terminal->YesNo("Are you sure you want to delete (y/n)",
			     p->XPos(), p->YOffset(2));
	if(!yn) break;
        mtank.DeleteObject();
	break;
      }
      
      case 'x':
	dllist->Clear();
	delete p;
	return;

      default:
	break;
    }
    dllistptr = dllistptr->GetNext(); 
  }
}

void FileStats()
{
  VBDStats(terminal, DB->OpenDatabase());
  VBDStats(terminal, DB->OpenIndexFile());
}

void Add()
{
  if(!AdminRights) {
    terminal->ClearScreen();
    terminal->Write("You do not have Admin User Privileges", 0, 1);
    terminal->AnyKey(0, 2);
    return;
  }

  int r = retrys;
  
  Coords *p = new Coords(0, 0);
  char buf[KeyBuf];
  terminal->ClearScreen();

  unsigned short month, day, year;
  while(1) {
    terminal->Write("Month: ", p->XPos(), p->YPos());
    month = terminal->GetInt();
    if(month <= 0) r--; else break;
    if(!r) {
      delete p;
      return;
    }
  }
  
  while(1) {
    terminal->Write("Day: ", p->XPos(), p->YPos()+1);
    day = terminal->GetInt();
    if(day <= 0) r--; else break;
    if(!r) {
      delete p;
      return;
    }
  }

  while(1) {
    terminal->Write("Year: ", p->XPos(), p->YPos()+2);
    year = terminal->GetInt();
    if(year <= 0) r--; else break;
    if(!r) {
      delete p;
      return;
    }
  }

  MarineTank mtank(DB);
  mtank.SetDate(__UBYTE__(month), __UBYTE__(day), year);
  FAU addr = mtank.FindObject();
   
  if(addr) { 
    terminal->Write(buf, p->XPos(), p->YOffset(4));
    terminal->Write(" entry already exists."); 
    terminal->AnyKey(p->XPos(), p->YOffset(2));
    delete p; // Free Coords pointer
    return;
  }

  AddObject(mtank, p);
  delete p; // Free Coords pointer
}

void AddObject(MarineTank &mtank, Coords *p)
{
  double sg;
  double ph;
  double ammonia;
  int nitrate;
  double nitrite;
  UString comments;

  terminal->Write("Enter the ", p->XPos(), p->YOffset(4));
  terminal->Write(M2Name); terminal->Write("> ");
  sg = terminal->GetFloat();
  mtank.SetSG(sg);
  terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
  terminal->Write(M3Name); terminal->Write("> ");
  ph = terminal->GetFloat();
  mtank.SetPH(ph);
  terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
  terminal->Write(M4Name); terminal->Write("> ");
  ammonia = terminal->GetFloat();
  mtank.SetAmmonia(ammonia);
  terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
  terminal->Write(M5Name); terminal->Write("> ");
  nitrate = terminal->GetInt();
  mtank.SetNitrate(nitrate);
  terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
  terminal->Write(M6Name); terminal->Write("> ");
  nitrite = terminal->GetFloat();
  mtank.SetNitrite(nitrite);
  mtank.SetComments(MultiLineInput(M7Name, p));
 
  terminal->Write("(A)-Abort (C)-Change, Any other key to accept entry",
		  p->XPos(), p->YOffset(2));
  int c = terminal->GetChar();
  switch(c) {
    case 'c': case 'C': 
      terminal->ClearScreen();
      p->SetXY(0, 0);
      ChangeObject(mtank, p); 
      break;
    case 'a':
      return;
    default:
      break;
  }

  int find = 0;
  FAU addr = mtank.AddObject(find); // Write the object to the file
  if(!addr) {
    terminal->Write("Could not add item to database",
                    p->XPos(), p->YOffset(2));
    terminal->AnyKey(p->XPos(), p->YOffset(2));
    return;
  }
  
  terminal->Write("Item was added to the database",
                  p->XPos(), p->YOffset(2));

  terminal->Write("Add another (y/n)", p->XPos(), p->YOffset(2));
  if(terminal->GetYesNo())
    Add();
  else {
    terminal->ClearScreen();
    p->SetXY(0, 0);
    AnalyzeWaterTest(p, mtank);
    delete p;
    return;
  }
}

char *MultiLineInput(const char *title, Coords *p)
{
  char buf[KeyBuf];
  UString title_line(title);
  title_line += " LINE 1";
  terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
  terminal->Write(" -> ");
  terminal->GetString(buf);
  if(!*buf) return "\0";
  UString line_buf(buf);
  int offset = title_line.Find("1");
  title_line.DeleteAt(offset, 1);
  title_line.InsertAt(offset, "2");
  terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
  terminal->Write(" -> ");
  terminal->GetString(buf);
  if(!*buf) return line_buf.c_str(); else line_buf += "\n";
  line_buf += buf;
  offset = title_line.Find("2");
  title_line.DeleteAt(offset, 1);
  title_line.InsertAt(offset, "3");
  terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
  terminal->Write(" -> ");
  terminal->GetString(buf);
  if(!*buf) return line_buf.c_str(); else line_buf += "\n";
  line_buf += buf;
  offset = title_line.Find("3");
  title_line.DeleteAt(offset, 1);
  title_line.InsertAt(offset, "4");
  terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
  terminal->Write(" -> ");
  terminal->GetString(buf);
  if(!*buf) return line_buf.c_str(); else line_buf += "\n";
  line_buf += buf;
  return line_buf.c_str();
}

void FindBy(const char *MemberName, MTankDBItem item)
{
  Coords *p = new Coords(0, 0);
  char buf[KeyBuf]; 
  unsigned offset = 0;

  terminal->ClearScreen();
  
  MarineTank mtank(DB);

  terminal->Write("Enter complete string or use ", p->XPos(), p->YPos());
  terminal->Write(WildCard);
  terminal->Write(" for a wild card.");
  terminal->Write(MemberName, p->XPos(), p->YOffset(2));
  terminal->Write(" -> ");
  terminal->GetString(buf);
  UString str(buf);
  offset = str.Find(WildCard, offset); // Look for wild card character

  if(DB->RebuildIndex()) {
    terminal->Write("The index file needs to be rebuilt.",
		    p->XPos(), p->YOffset(2));
    terminal->AnyKey(p->XPos(), p->YOffset(1));
    delete p;
    return;
  }
  
  if(offset == UString::NoMatch) { // No wild cards found
    MTankDBSearch(mtank, item, str, p);
    delete p; // Free the Coord pointer
    return;
  }

  MTankDBSearch(mtank, item, str, p, WildCard);
  delete p; // Free the Coord pointer
}

void FindByTestDate()
{
  FindBy(KeyName, YEAR);
}

void FindByComments()
{
  FindBy(M7Name, COMMENTS);
}

void ChangeObject(MarineTank &mtank, Coords *p)
{
  // Buffer the object's data to allow changes to be canceled
  MarineTank ob;
  ob.Copy(mtank);
  FAU addr;
  double sg;
  double ph;
  double ammonia;
  int c, nitrate;
  double nitrite;
  unsigned short month, day, year;
  int r = retrys;
  char *prompt = "Press the number of your selection >";
  
  while(1) {
    ChangeObjectDisplay(mtank, p);    
    terminal->Write(prompt, p->XPos(), p->YOffset(2));

    c = terminal->GetChar();
    switch(tolower(c)) {
      case '0': 
	mtank.Copy(ob);
	return;

      case '1':
	terminal->ClearScreen();
	p->SetXY(0, 0);
	terminal->Write("Month: ", p->XPos(), p->YPos());
	month = terminal->GetInt();
	while(1) {
	  if(month <= 0) r--; else break;
	  if(!r) {
	    break;
	  }
	}
  
	while(1) {
	  terminal->Write("Day: ", p->XPos(), p->YPos()+1);
	  day = terminal->GetInt();
	  if(day <= 0) r--; else break;
	  if(!r) {
	    break;
	  }
	}

	while(1) {
	  terminal->Write("Year: ", p->XPos(), p->YPos()+2);
	  year = terminal->GetInt();
	  if(year <= 0) r--; else break;
	  if(!r) {
	    break;
	  }
	}
	
	mtank.SetDate(__UBYTE__(month), __UBYTE__(day), year);
        if(mtank.FindObject()) {
	  terminal->Write("Entry already exists", p->XPos(), p->YOffset(4));
	  terminal->AnyKey();
	  break;
	}
	break;

      case '2':
	terminal->ClearScreen();
	p->SetXY(0, 0);
	terminal->Write("Enter the ", p->XPos(), p->YOffset(4));
	terminal->Write(M2Name); terminal->Write("> ");
	sg = terminal->GetFloat();
	mtank.SetSG(sg);
	break;

      case '3':
	terminal->ClearScreen();
	p->SetXY(0, 0);
	terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
	terminal->Write(M3Name); terminal->Write("> ");
	ph = terminal->GetFloat();
	mtank.SetPH(ph);
	break;

      case '4':
	terminal->ClearScreen();
	p->SetXY(0, 0);
	terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
	terminal->Write(M4Name); terminal->Write("> ");
	ammonia = terminal->GetFloat();
	mtank.SetAmmonia(ammonia);
	break;

      case '5':
	terminal->ClearScreen();
	p->SetXY(0, 0);
	terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
	terminal->Write(M5Name); terminal->Write("> ");
	nitrate = terminal->GetInt();
	mtank.SetNitrate(nitrate);
	break;

      case '6':
	terminal->ClearScreen();
	p->SetXY(0, 0);
	terminal->Write("Enter the ", p->XPos(), p->YOffset(1));
	terminal->Write(M6Name); terminal->Write("> ");
	nitrite = terminal->GetFloat();
	mtank.SetNitrite(nitrite);
	break;

      case '7':
	terminal->ClearScreen();
	p->SetXY(0, 0);
	mtank.SetComments(MultiLineInput(M7Name, p));
	break;
	
      case 'A': case 'a':
	return;

      default:
	break;
    }
  }
}

void ChangeObjectDisplay(MarineTank &mtank, Coords *p)
{
  UString title_buf;  
  terminal->ClearScreen();
  p->SetXY(0, 0);

  terminal->Write("(0) Cancel change", p->XPos(), p->YOffset(2));
  terminal->Write("(1) ", p->XPos(), p->YOffset(1));
  terminal->Write(KeyName); terminal->Write(" = ");
  terminal->Write(mtank.GetDateStr());
  terminal->ClearLine();
  terminal->Write("(2) ", p->XPos(), p->YOffset(1));
  terminal->Write(M2Name); terminal->Write(" = ");
  terminal->Write(mtank.GetSG());
  terminal->ClearLine();
  terminal->Write("(3) ", p->XPos(), p->YOffset(1));
  terminal->Write(M3Name); terminal->Write(" = ");
  terminal->Write(mtank.GetPH());
  terminal->ClearLine();
  terminal->Write("(4) ", p->XPos(), p->YOffset(1));
  terminal->Write(M4Name); terminal->Write(" = ");
  terminal->Write(mtank.GetAmmonia());
  terminal->ClearLine();
  terminal->Write("(5) ", p->XPos(), p->YOffset(1));
  terminal->Write(M5Name); terminal->Write(" = ");
  terminal->Write(mtank.GetNitrate());
  terminal->ClearLine();
  terminal->Write("(6) ", p->XPos(), p->YOffset(1));
  terminal->Write(M6Name); terminal->Write(" = ");
  terminal->Write(mtank.GetNitrite());
  terminal->ClearLine();
  title_buf = "(7) "; title_buf += M7Name;
  DisplayMultiText(title_buf.c_str(), mtank.GetComments(), p);
  terminal->Write("(A) Accept changes", p->XPos(), p->YOffset(1));
}

void DisplayMultiText(const char *title, char *str, Coords *p)
{
  char words[MAXWORDS][MAXWORDLENGTH];
  int num, offset, i, j;
  const char dchar = '\n';  // Text delimiter
  UString line_buf;

  UString title_line(title);
  title_line += " LINE 1 = ";
  terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
  
  for(i = 0; i < MAXWORDS; i++) {
    for(j = 0; j < MAXWORDLENGTH; j++)
      words[i][j] = '\0';
  }
  
  parse(str, words, &num, dchar);

  if(words[0][0] != '\0') {
    line_buf = words[0];
    offset = line_buf.Find("\r");
    if(offset != UString::NoMatch) {
      line_buf.DeleteAt(offset, 1);
    }
    terminal->Write(words[0]);
  }
  else
    return;

  if(words[1][0] != '\0') {
    line_buf = words[1];
    offset = line_buf.Find("\r");
    if(offset != UString::NoMatch) {
      line_buf.DeleteAt(offset, 1);
    }
    offset = title_line.Find("1");
    title_line.DeleteAt(offset, 1);
    title_line.InsertAt(offset, "2");
    terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
    terminal->Write(words[1]);
  }
  else
    return;
    
  if(words[2][0] != '\0') {
    line_buf = words[2];
    offset = line_buf.Find("\r");
    if(offset != UString::NoMatch) {
      line_buf.DeleteAt(offset, 1);
    }
    offset = title_line.Find("2");
    title_line.DeleteAt(offset, 1);
    title_line.InsertAt(offset, "3");
    terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
    terminal->Write(words[2]);
  }
  else
    return;
  
  if(words[3][0] != '\0') {
    line_buf = words[3];
    offset = line_buf.Find("\r");
    if(offset != UString::NoMatch) {
      line_buf.DeleteAt(offset, 1);
    }
    offset = title_line.Find("3");
    title_line.DeleteAt(offset, 1);
    title_line.InsertAt(offset, "4");
    terminal->Write(title_line.c_str(), p->XPos(), p->YOffset(1));
    terminal->Write(words[3]);
  }
}

void DisplayObject(MarineTank &mtank, Coords *p)
// Display's the complete object with all details.
{
  terminal->Write(KeyName, p->XPos(), p->YPos());
  terminal->Write(" = ");
  terminal->Write(mtank.GetDateStr());

  terminal->Write(M2Name, p->XPos(), p->YOffset(1));
  terminal->Write(" = ");
  terminal->Write(mtank.GetSG());

  terminal->Write(M3Name, p->XPos(), p->YOffset(1));
  terminal->Write(" = ");
  terminal->Write(mtank.GetPH());

  terminal->Write(M4Name, p->XPos(), p->YOffset(1));
  terminal->Write(" = ");
  terminal->Write(mtank.GetAmmonia());

  terminal->Write(M5Name, p->XPos(), p->YOffset(1));
  terminal->Write(" = ");
  terminal->Write(mtank.GetNitrate());

  terminal->Write(M6Name, p->XPos(), p->YOffset(1));
  terminal->Write(" = ");
  terminal->Write(mtank.GetNitrite());

  DisplayMultiText(M7Name, mtank.GetComments(), p);
}

void Version()
{
  printf("\n%s program version %.3f\n", ProgramName, VersionNumber);
  exit(0);
}

void MTankDBSearch(MarineTank &mtank, MTankDBItem item, UString &str,
		  Coords *p, const char *wildcard)
{
  terminal->Write("Searching for ", p->XPos(), p->YOffset(2));
  terminal->Write(str.c_str());

  int offset;
  offset = ObjectsFound = 0;

  if(wildcard != 0) {
    while(1) { // Remove the wild card characters from the string
      offset = str.Find(wildcard, offset);
      if (offset == UString::NoMatch) break;
      str.DeleteAt(offset, strlen(wildcard));
      offset++;
    }
  }

  // Clear the data structures
  dllist->Clear();
  rbtree->Clear();

  CachePointer n = DB->Index()->GetRoot();

  // Ensure that the memory buffers and the file data
  // stay in sync during multiple file access.
  DB->Index()->TestTree();

  if(wildcard != 0)
    BtreeSearch(n, BtreeNodeSearch, item, mtank, str, 1);
  else
    BtreeSearch(n, BtreeNodeSearch, item, mtank, str);

  if(ObjectsFound == 0) {
    terminal->Write("No matches found.", p->XPos(), p->YOffset(2));
    terminal->AnyKey(p->XPos(), p->YOffset(1));
    return;
  }

  if(ObjectsFound > 1) { // Found multiple matches
    terminal->Write("Found ", p->XPos(), p->YOffset(2));
    terminal->Write(ObjectsFound);
    terminal->Write(" matching.");
    terminal->AnyKey(p->XPos(), p->YOffset(1));
  }
  else {
    terminal->Write("Found matching entry for:  ", p->XPos(), p->YOffset(2));
    terminal->Write(str.c_str());
    terminal->AnyKey(p->XPos(), p->YOffset(1));
  }

  BNodeBase *nxt; // BSTree base node pointer

  // Using iterator object to walk throught the rbtree
  TreeWalkerb tw(rbtree->GetRoot(), INORDER);
  
  // Walk through the rbtree using an iterator object
  while((nxt = tw.Next()) != 0) {
    RBNode<MTankInMemCopy> *ptr = (RBNode<MTankInMemCopy> *)nxt;
    dllist->StoreNode(ptr->Data);
  }

  rbtree->Clear(); // Clear the rbtree

  // Display all the matches
  dllistptr = dllist->GetFront();
  while(!dllist->IsHeader(dllistptr)) {
    terminal->ClearScreen();
    p->SetXY(0, 0);
    mtank.ReadObject(dllistptr->Data.object_address);
    DisplayObject(mtank, p);
    terminal->Write("(A)-Analyze, (D)-Delete, (X)-Exit",
		    p->XPos(), p->YOffset(2));
    terminal->Write(", Any other key to continue");
    int c = terminal->GetChar();
    switch(c) {
      case 'a': case 'A': 
	terminal->ClearScreen();
	p->SetXY(0, 0);
        mtank.ReadObject(dllistptr->Data.object_address);
        AnalyzeWaterTest(p, mtank);
	break;
	
      case 'd': case 'D': {
	int yn = terminal->YesNo("Are you sure you want to delete (y/n)",
				 p->XPos(), p->YOffset(2));
	if(!yn) break;
        mtank.ReadObject(dllistptr->Data.object_address);
        mtank.DeleteObject();
	break;
      }
      
      case 'x': case 'X':
	dllist->Clear(); // Free dllist memory
	return;

      default:
	break;
    }
    dllistptr = dllistptr->GetNext();
  }
  dllist->Clear(); // Free dllist memory
}

void AnalyzeWaterTest(Coords *p, MarineTank &mtank)
{
  terminal->Write("Analyzing Salt Water Test: ", p->XPos(), p->YPos());
  terminal->Write(mtank.GetDateStr());
  char sbuffer[sbuffer_len];
  int i;

  sprintf(sbuffer, "%.3f", (double)mtank.GetSG());
  terminal->Write("Specific Gravity reads: ", p->XPos(), p->YOffset(2));
  terminal->Write(sbuffer);
  if(mtank.GetSG() < sg_lower_limit) {
    terminal->Write("The salinity is to low.", p->XPos(), p->YOffset(1));
    for(i = 0; i < sbuffer_len; i++) sbuffer[i] = '\0';
    sprintf(sbuffer, "%.3f", sg_lower_limit);
    terminal->Write( "Acceptable range = ", p->XPos(), p->YOffset(1));
    terminal->Write(sbuffer);
    for(i = 0; i < sbuffer_len; i++) sbuffer[i] = '\0';
    sprintf(sbuffer, "%.3f", sg_upper_limit);
    terminal->Write(" to ");
    terminal->Write(sbuffer);
  }
  else if(mtank.GetSG() > sg_upper_limit) {
    for(i = 0; i < sbuffer_len; i++) sbuffer[i] = '\0';
    sprintf(sbuffer, "%.3f", sg_lower_limit);
    terminal->Write( "The salinity is to high.", p->XPos(), p->YOffset(1));
    terminal->Write( "Acceptable range = ", p->XPos(), p->YOffset(1));
    terminal->Write(sbuffer);
    for(i = 0; i < sbuffer_len; i++) sbuffer[i] = '\0';
    sprintf(sbuffer, "%.3f", sg_upper_limit);
    terminal->Write(" to ");
    terminal->Write(sbuffer); 
  }
  else {
    terminal->Write( "The salinity tests good.", p->XPos(), p->YOffset(1));
  }

  terminal->Write("The pH reads: ", p->XPos(), p->YOffset(2));
  terminal->Write((double)mtank.GetPH());
  if(mtank.GetPH() < ph_lower_limit) {
    terminal->Write("The pH is to low.", p->XPos(), p->YOffset(1));
    terminal->Write("Acceptable range = ", p->XPos(), p->YOffset(1));
    terminal->Write(ph_lower_limit);
    terminal->Write(" to ");
    terminal->Write(ph_upper_limit);
  }
  else if(mtank.GetPH() > ph_upper_limit) {
    terminal->Write("The pH is to high.", p->XPos(), p->YOffset(1));
    terminal->Write("Acceptable range = ", p->XPos(), p->YOffset(1));
    terminal->Write(ph_lower_limit);
    terminal->Write(" to ");
    terminal->Write(ph_upper_limit);
  }
  else {
    terminal->Write("The pH tests good.", p->XPos(), p->YOffset(1));
  }

  terminal->Write("Ammonia level reads: ", p->XPos(), p->YOffset(2));
  terminal->Write(mtank.GetAmmonia());
  terminal->Write(" mg/liter");
  if(mtank.GetAmmonia() > ammonia_limit) {
    terminal->Write("Ammonia level is to high.", p->XPos(), p->YOffset(1));
    terminal->Write("Acceptable range is below ", p->XPos(), p->YOffset(1));
    terminal->Write(ammonia_limit);
    terminal->Write(" mg/liter");
  }
  else {
    terminal->Write("Ammonia level tests good.", p->XPos(), p->YOffset(1));
  }

  terminal->Write("Nitrate level reads: ", p->XPos(), p->YOffset(2));
  terminal->Write(mtank.GetNitrate());
  terminal->Write(" mg/liter");
  if(mtank.GetNitrate() >= nitrate_limit_IV &&
     mtank.GetNitrate() < nitrate_limit_fish) {
    terminal->Write("Nitrate level is deadly to invertebrates.",
		    p->XPos(), p->YOffset(1)); 
    terminal->Write("Acceptable range is below ", p->XPos(), p->YOffset(1));
    terminal->Write(nitrate_limit_IV);
    terminal->Write(" for invertebrates.");
    terminal->Write("Acceptable range for fish is below ",
		    p->XPos(), p->YOffset(1));
    terminal->Write(nitrate_limit_fish);
    terminal->Write(" mg/liter");
  }
  else if(mtank.GetNitrate() >= nitrate_limit_fish) {
    terminal->Write("Nitrate level is to high.", p->XPos(), p->YOffset(1));
    terminal->Write("Acceptable range is below ", p->XPos(), p->YOffset(1));
    terminal->Write(nitrate_limit_fish);
    terminal->Write(" mg/liter");
    terminal->Write("This indicates poor water quality.",
		    p->XPos(), p->YOffset(1));
  }
  else {
    terminal->Write("Nitrate level tests good.", p->XPos(), p->YOffset(1));
  }

  terminal->Write("Nitrite level reads: ", p->XPos(), p->YOffset(2));
  terminal->Write(mtank.GetNitrite());
  terminal->Write(" mg/liter");

  if(mtank.GetNitrite() > nitrite_limit) {
    terminal->Write("Nitrite level is to high.", p->XPos(), p->YOffset(1));
    terminal->Write( "Acceptable range is below ", p->XPos(), p->YOffset(1));
    terminal->Write(nitrite_limit);
    terminal->Write(" mg/liter");
  }
  else {
    terminal->Write("Nitrite level tests good.", p->XPos(), p->YOffset(1));
  }

  terminal->Write("Water Test Complete.", p->XPos(), p->YOffset(2)); 
  terminal->AnyKey(p->XPos(), p->YOffset(1)); 
}

int main(int argc, char **argv)
{
  // Display the program version information and exit the program
  if(argc >= 2) {
    if(strcmp(argv[1], "version") == 0) Version();
  }

  char *FileName;
  char *CurrentCfgFile;
  char *CfgFile;

  Config *CfgData = new Config;
  char *AdminUser = 0;
  
  // Look for CfgFile name in environment
  if((CurrentCfgFile = getenv("GDBCFG")) == 0)
    CfgFile = (char *)DefaultCfgFile;
  else
    CfgFile = CurrentCfgFile;

  // Use default file of CFG file name if no file is specifed on 
  if(argc < 2) {
    int FileStatus = CfgData->Load((char *)CfgFile);
    if(!FileStatus)
      FileName = (char *)DefaultDBFileName;
    else {
      FileName = CfgData->GetStrValue("DBFileName");
      AdminUser = CfgData->GetStrValue("AdminUser");
      if(!FileName) {
	printf("\nDBFileName section missing in config file: %s\n", CfgFile);
	exit(0);
      }
    }
  }
  else {
    FileName = argv[1];
  }

  if(!AdminUser) {
    AdminRights = 1;
  }
  else {
    int result = strcmp(AdminUser, "FALSE");
    if(result == 0) {
      AdminRights = 0;
    }
    else {
      AdminRights = 1;
    }
  }

  CfgData->UnLoad(); // Unload the Config file from memory

  // Initialize global database pointer
  // NOTE: Index file and data file will share the same file name
  // with different file extensions. The data file will have a 
  // .pod extension and the index file will have a .btx extension.
  if(AdminRights == 1) 
    DB = new POD(FileName, RWMode, 1, CacheSize);
  else
    DB = new POD(FileName, ROMode, 1, CacheSize);

  terminal->init();
  MainMenu();
  terminal->finish();
  delete DB;
  return 0;
}
// ----------------------------------------------------------- //
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
