/* This file contains the definition for class DecisionTree.

---------------------------------------------------------------*/

#include "DecisionTree.h"
#include <fstream>            // ifstream, ofstream, ...
#include <cstdlib>            // exit()
using namespace std;

/* read tree-data from fileName
 * receive: fileName, a string.
 * input(fileName): a sequence of Node values.
 * POST: I am a DecisionTree containing the data from fileName.
 * Relies on: buildTree()
 * Called by: DecisionTree constructor
 */
void DecisionTree::read(const string& fileName)
{
  ifstream fin( fileName.data() );
  
  if (!fin.is_open())
  {
    cerr << "\nDecisionTree::load(fileName) unable to open fileName: '"
         << fileName << "'; exiting...\n";
    exit (-1);
  }

  buildTree(fin, myRoot);

  fin.close();
}

/* utility to recursively build the tree by reading from an istream
 * Receive: fin, an istream'
 *           nPtr, the 'root' of a DecisionTree.
 * POST: the tree has been built from nPtr 'down' using values from fin.
 * Called by: read()
 */

void DecisionTree::buildTree(istream& fin, DecisionTree::NodePtr& nPtr)
{
                                       // trivial case: fin is empty, do nothing
  if (!fin.eof() && fin.good())        // nontrivial case: 
  {
    string question;                   // a. read 1 node's data
    getline(fin, question);
    int leftSubtreeExists,
        rightSubtreeExists;
    fin >> leftSubtreeExists >> rightSubtreeExists;
    char separator;
    fin.get(separator); fin.get(separator);
                                       // b. build a new node for that data
    nPtr = new DecisionTree::Node(question);

    if (leftSubtreeExists)             // c. if necessary
      buildTree(fin, nPtr->myYesChild);  //      build 'yes' node recursively

    if (rightSubtreeExists)            // d. if necessary
      buildTree(fin, nPtr->myNoChild);   //      build 'no' node recursively
  }
}


/* recursively 'walk' a tree from its root to a leaf
 * return: 1 if the answer to the leaf-node's question was 'yes'
 *         0 if the answer at the leaf-node's question was 'no'
 * Called by: descend()
 */

int DecisionTree::descendTree(NodePtr& nPtr)
{
  char response;
  int result;
  
  if (nPtr != 0)                          // validate parameter
  {
      do                                  // get a y or n response
      {                                   //  to this node's question
        cout << "\n" + nPtr->myQuestion;
        cin >> response;
      }
      while ((response != 'y') && (response != 'n'));

    // if this is a leaf node
    if ((nPtr->myYesChild == 0) && (nPtr->myNoChild == 0))
      if (response == 'y')                //  and we guessed correctly
        result = 1;                       //   we won!
      else                                //  otherwise, we lost, so
      {
        learnSomething(nPtr);             // learn about the new animal
        result = 0;
      }
    else                                  // otherwise: it's a non-leaf node
      if (response == 'y')                //  so follow the appropriate link
        result = descendTree(nPtr->myYesChild);
      else
        result = descendTree(nPtr->myNoChild);
  }
    
  return result;
}

/* utility to expand my 'knowledge base'; 
 *  invoked when I reach a leaf node and the answer is 'n'.
 * Receive: nPtr, the handle of the leaf Node.
 * POST: nPtr now points to a new interior node
 *       && nPtr->myNoChild points to the old leaf (animal)
 *       && nPtr->myYesChild points to a new leaf (animal)
 */
void DecisionTree::learnSomething(NodePtr& nPtr)
{
                                                  // get new animal name
  cout << "\nWhat animal are you thinking of? ";
  char separator;
  cin.get(separator);
  string newAnimal;
  getline(cin, newAnimal);
                                                  // extract old animal name
  int end = nPtr->myQuestion.find(" (y or n)?", 0);
  if (end == string::npos)
  {
    cerr << "DecisionTree::learnSomething(): ill-formatted question: '"
         << nPtr->myQuestion << "'; exiting ...\n" << endl;
    exit(1);
  }
  string oldAnimal = nPtr->myQuestion.substr(11, end-10);
                                                  // get question to distinguish them
  cout << "\nPlease enter a question, such that the answer is\n"
       << "\tyes - for a(n) " << newAnimal
       << ", and\n\tno - for a(n) " << oldAnimal
       << "\n--> ";
  string newQuestion;
  getline(cin, newQuestion);

  NodePtr tempPtr = nPtr;                         // save node containing oldAnimal
  nPtr = new Node(newQuestion + " (y or n)? ");   // make node for new question
                                                  // make node for newAnimal
  NodePtr newAnimalPtr = new Node("\nIs it a(n) " + newAnimal + " (y or n)? ");
  nPtr->myYesChild = newAnimalPtr;                // arrange the 3 nodes correctly
  nPtr->myNoChild = tempPtr;
}

/* utility to write a DecisionTree's data to a data file.
 *  (mostly just a wrapper for the recursive writeTree() method).
 * Receive: fileName, a string.
 * POST: All of my data has been written to fileName.
 * Called by: DecisionTree destructor.
 */
void DecisionTree::write(const string& fileName)
{
  ofstream fout( fileName.data() );

  if ( !fout.is_open() ) 
  {
    cerr << "\nDecisonTree::write(fileName): unable to open fileName: '"
         << fileName << "'; exiting...\n";
    exit (-1);
  }

  writeTree(fout, myRoot);

  fout.close();
}

/* Utility to recursively write a node and its subtrees to a file via an ostream
 * Receive: fout, the stream to which data should be written;
            nPtr, the root of a DecisionTree.
 * POST: fout contains the data of nPtr and its subtrees.
 * Called by: write()
 */ 
void DecisionTree::writeTree(ostream& fout, DecisionTree::NodePtr nPtr)
{
  if (nPtr != 0)
  {
    fout << nPtr->myQuestion << endl
         << (nPtr->myYesChild != 0) << ' '
         << (nPtr->myNoChild != 0) << endl;

    writeTree(fout, nPtr->myYesChild);
    writeTree(fout, nPtr->myNoChild);
  }
}
