#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include "Token.h"
#include "ExpressionTree.h"
#include "VariousExceptions.h"

using namespace std;

istream *isr;
bool echoinput;
bool endOfInput=false;

string GeneralQuestion(const string& prompt) {
  string response;
  bool done = false;

  cout << prompt;
  while (!done) {
    getline(*isr, response);
    if (isr->eof()) {
      // must be EOF
      if (echoinput) {
	// switch to keyboard input
	isr = &cin;
	echoinput=false;
      } else {
	cout << "unexpected end of input reached.\n";
	endOfInput = true;
	done=true;
      }
    } else {
      // successful so far
      if (echoinput) cout << response << "\n";
      if ((response).length()>0) done=true;
    }
  }

  return(response);
}


int tokenize(const string s, Token *tokens[]) {
  vector<Token> v;

  string digit = "0123456789";
  string other = "()+-*/";
  string valid = digit+other;

  string::size_type pos = s.find_first_of(valid);
  string::size_type end;

  while (pos != string::npos) {
    if (s.find_first_of(digit,pos)==pos) {
      // pos is a digit
      end = s.find_first_not_of(digit,pos);
      v.push_back(Token(NUMERIC,atoi(s.substr(pos,(end-pos)).c_str())));
    } else {
      char c = s[pos];
      Token t;
      switch (c) {
	case '(':
	  t = Token(L_PAREN);
	  break;
	case ')':
	  t = Token(R_PAREN);
	  break;
	case '+':
	  t = Token(PLUS);
	  break;
	case '-':
	  t = Token(MINUS);
	  break;
	case '*':
	  t = Token(TIMES);
	  break;
	case '/':
	  t = Token(DIVIDE);
	  break;
      }
      v.push_back(t);
      end = pos+1;
    }

    pos = s.find_first_of(valid,end);
  }


  // convert vector to an array
  (*tokens) = new Token[v.size()];
  for (unsigned int i=0; i<v.size(); i++)
    (*tokens)[i] = v[i];

  return v.size();
}


int main(int argc, const char* argv[]) {

  cout << "Welcome to ExpressionDriver.\n";

  filebuf fb;
  bool quit = false;
  bool useExtra = false;
  string response;
  Token *tokens;
  int numTok;
  ExpressionTree *T = NULL;


  // choose between keyboard input or mock-keyboard file input
  echoinput = false;
  isr = &cin;
  if (argc>1) {
    fb.open(argv[1],ios::in);
    if (fb.is_open()) {
      isr = new istream(&fb);
      echoinput= true;
    } else {
      cout << "Input file '" << argv[1] << "' not found.\n";
      cout << "Defaulting to keyboard input.\n\n";
    }
  }


  // shall we use extra credit?
  if (argc>2) {
    useExtra = true;
    cout << "Will be using the extra credit constructor.\n";
  }


  string prompt = "\nPlease enter an arithmetic expression (Q to quit):\n";

  do {
    response = GeneralQuestion(prompt);

    if (response.length()==0 || (response[0]=='q') || (response[0]=='Q')) {
      quit=true;
    } else {

      numTok = tokenize(response,&tokens);

      if (numTok>0) {
	try {
	  T = (!useExtra ?
	       new ExpressionTree(numTok,tokens) :
	       new ExpressionTree(tokens,numTok)
	       );
	} catch (const InvalidExpressionException& e1) {
	  cout << "constructor threw unexpected exception\n" << e1 << "\n";
	} catch (const RuntimeException& e2) {
	  cout << "constructor threw unexpected exception\n" << e2 << "\n";
	} catch (...) {
	  cout << "constructor threw unexpected exception.\n";
	}

	delete [] tokens;
      }

      if (T) {
	try {
	  cout << "Printing the newly constructed tree results in: " << *T << endl;
	} catch (const RuntimeException& e2) {
	  cout << "output operator threw unexpected exception\n" << e2 << "\n";
	} catch (...) {
	  cout << "output operator threw unexpected exception.\n";
	}

	try {
	  int value = T->evaluate();
	  cout << "Evaluating the newly constructed tree results in value: " << value << endl;
	  int value2 = T->evaluate();
	  if (value2 != value)
	    cout << "However repeated call to evaluate returned " << value << endl;
	} catch (const RuntimeException& e2) {
	  cout << "evaluate method threw unexpected exception" << e2 << "\n";
	} catch (...) {
	  cout << "evaluate method threw unexpected exception.\n";
	}


	delete T;
	T = NULL;
      }
    }
  } while (!quit);


  cout << "\n";
  if (isr != &cin)  delete isr;
  

  return(0);
}





