// Source file
// By: Nick Cash, 810:061-3
// Lab Project

// Parser by Nick Gammon
// You can find information about it at:
// http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=4649

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <list>
#include <map>
#include "main.h"
#include <bt/parser.h>  // include for the parser library

using namespace std;

// my lists
list<VAR *> var_list;   // list of variables in existence
list<LINE *>   lines;   // list of lines


// Main function
int main()
{
    // seed random num gen for possible use later
    srand(time(NULL));

    get_input();        // pull data from script into lines

    // fill in the rest of the line information
    if ( !parse_input() ) // error
    {
      cout << "\n\nFatal error, exiting...\n\n";
      return EXIT_FAILURE;
    }


    // run
    execute();

    // unalloc
    cleanup();

    // end
    return EXIT_SUCCESS;
}

// Alloc lines, fill in the "data" field
void get_input()
{
   LINE *line;
   bool blank;
   ifstream fin;
   string fname;
   
   cout << "\n\nSpecified file?\n> ";
   cin >> fname;
   
   fin.open(fname.c_str());
   
   while ( fin ) // while reading succeeded
   {
      blank = true;   // true until proved otherwise
      line = NULL;
      line = new LINE; // alloc
      
      getline(fin,line->data); // fill in the data field
      
      
      // check for blank lines
      for ( int i = 0; i < line->data.length(); i++ )
       if ( !isspace(line->data[i]) ) // something besides whitespace?
       {
         line->data = line->data + ' '; // add a trailing space to the line data

         // add to the list
         lines.push_back(line);
         
         blank = false;
         break;
       }
       
      // was it blank? if so, delete
      if ( blank == true )
        delete line;
   }
   
   //close file
   fin.close();
}

// unalloc data
void cleanup()
{
  LINE *ln = NULL;
  VAR  * v = NULL;

  // unalloc lines
  while( lines.size() > 0 )
  {
   ln = lines.front();
   
   // remove from list
   lines.pop_front();
   
   delete ln;  // delete
  }

  // unalloc variables
  while( var_list.size() > 0 )
  {
   v = var_list.front();

   // remove from list
   var_list.pop_front();

   delete v;  // delete
  }
}

// execute the script
void execute( void )
{
  list<LINE *>::const_iterator iter;
  LINE *ln = NULL;
  bool at_begin = false;

  cout << "\n\n\nExecuting script...\n\n\n";

  // go through lines
  for ( iter = lines.begin(); iter != lines.end(); iter++ )
  {
     if ( at_begin ) //prevent list overrun
     {
      iter = lines.begin();
      at_begin = false;
     }

     ln = (*iter);
     
     if ( ln )
     {
        // examine commands
        if ( ln->command == "stop" || ln->command == "STOP" )
         return;
        else if ( ln->command == "goto" || ln->command == "GOTO" )
        {
           list<LINE *>::const_iterator a;
           
           for ( a = lines.begin(); a != lines.end(); a++ )
            if ( (*a)->line_num == ln->operand )
             break;
             
             iter = a;
             
             if ( iter == lines.begin() )
              at_begin = true;
             else
              iter--;
        }
        else if ( ln->command == "set" || ln->command == "SET" )
        {
            parse_expression(ln->operand); //should modify values according to expression
        }
        else if ( ln->command == "if" || ln->command == "IF" )
        {
          // will creature/modify the variable named answer, return true or false
          if ( parse_expression(ln->operand) != 0 ) // return non-zero?
          {                                             // copied straight from GOTO, almost
           list<LINE *>::const_iterator a;

           for ( a = lines.begin(); a != lines.end(); a++ )
            if ( (*a)->line_num == ln->operand2 )
             break;

             //set the position
             iter = a;

             // fix for loop
             if ( iter == lines.begin() )
              at_begin = true;
             else
              iter--;
          }
        }
        else if ( ln->command == "read" || ln->command == "READ" )
        {
           string var, s = ln->operand + ','; //add a comma at the end for simplicity
           int x, j = ln->operand.length();
           
           // find var names
           for ( x = 0; x <= j; x++ )
           {
             if ( s[x] == ',' ) // found a comma
             {
              VAR *v = find_variable(var);
              int i = 0;

              if ( v == NULL )
               v = create_variable( var, 0 ); // create the variable if it doesnt exist

               cin >> i;

               v->value = i;

               var = "";
               cout << endl;
             }
             else
              var += s[x];
           }

           cout << endl;
        }
        else if ( ln->command == "write" || ln->command == "WRITE" )
        {
           string s, var_name;
           int x;
           bool in_constant = false;
           bool in_variable = false;
           bool print_variable = false;

           //do we start with a variable
           if ( ln->operand[0] != '"' && !isspace(ln->operand[0]) )
            in_variable = true;
           
           // seperate arguments
           for ( x = 0; x < ln->operand.length(); x++ )
           {
             if ( print_variable ) // find and print variable
             {
              bool found = false;
              list<VAR *>::iterator a;

              for ( a = var_list.begin(); a != var_list.end(); a++ )
              {
                if ( (*a)->name == var_name )
                {
                 found = true;
                 break;
                }
              } // end for

              if ( found == true )
               s += num_to_string((*a)->value);
              else
              {
#ifdef PRINT_DEBUG
                cout << "*** ERROR (Line #" << ln->ref << ") *** Could not find variable (" <<
                     var_name << ")!\n\n";
#endif
              }

               // reset
               print_variable = false;
               var_name = "";
             }

             if ( ln->operand[x] == '"' )
             {
              if ( in_constant == false )
               in_constant = true;
              else
               in_constant = false;
             }
             else if ( ln->operand[x] == ',' )
             {
              if ( in_variable ) // done reading variable name
              {
               in_variable = false;
               print_variable = true;
              }
              else if ( !in_constant && (ln->operand[x+1] != '"')
                        && !isspace(ln->operand[x])) // we are looking at a variable name
              {
               if ( print_variable == false )
                in_variable = true;
              }
             }
             else // we don't have a double quote or comma
             {
               if ( in_constant )
                s += ln->operand[x];
               else if ( in_variable )
               {
                 if ( isspace(ln->operand[x]) ) // encountered whitespace while reading var name
                 {
                   in_variable = false;
                   print_variable = true;

                   if ( x+1 >= ln->operand.length() ) // this variable is at the end of the string
                    x--;                              // subtract to prevent loop termination
                 }
                 else
                   var_name += ln->operand[x];
                }
#ifdef PRINT_DEBUG
               else if ( !isspace(ln->operand[x]) ) //catch any unknown non-white space
                cout << "\n\n\n*** ERROR *** Unsure what do do with character (" << ln->operand[x] <<
                     ") on line #" << ln->ref << " \n\n\n";
#endif
               
             }
           } // end for
           
           
           // print the product
           cout << s << endl;
        } // end else-if
#ifdef PRINT_DEBUG
        else
        {
          cout << "\n\n\n*** ERROR ***  Bad command (" << ln->command << ")! Exiting\n\n";
          return;
        }
#endif
     }
#ifdef PRINT_DEBUG
     else
     {
       cout << "\n\n*** ERROR *** NULL line! Exiting...\n\n";
       return;
     }
#endif
  } // end for
}

// parse the input
bool parse_input( void )
{
  list<LINE *>::iterator iter;
  int x;
  int num_lines = 0;
  bool in_string_constant = false;


  // some strings
  string data;
  string line_num;
  string command;
  string operand;
  string operand2 = "None";
  
  // look through strings one by one
  for ( iter = lines.begin(); iter != lines.end(); iter++ )
  {
    // reset variables
    data = line_num = command = operand = "";
    x = 0;

    if ( (*iter) && (*iter)->data.size() > 0 )
     data = (*iter)->data;
    else
     return false;
     
    num_lines++;  //used to count lines

    // find the optional line num
    if ( !isspace(data[0]) && isdigit(data[0])) // this isn't a space, we have a line num
     for ( x = 0; x < data.length(); x++ )
     {
       if ( isspace(data[x]) ) // we hit white space
       {
          line_num = data.substr(0,x);  // grab from the beginning till first whitespace
          break;
       }
     }
    else
     line_num = "None";

    // skip white space
    while( isspace(data[x]) )
     x++;

    // locate the command
    for ( int y = x; x < data.length(); x++ )
    {
      if ( isspace(data[x]) ) // found the end
      {
       command = data.substr(y,x-y); // pull out the command
       break;
      }
    }

    // minor error checking
    if ( command.length() <= 0 )
    {
#ifdef PRINT_DEBUG
       cout << "\n\n*** ERROR (Line #" << num_lines << ") ***  No command found!\n\n";
       cout << "\n\nCommand = " << command << ". Line_num = " << line_num << ".\n\n";
       cout << "x = " << x << ".  Data.length() = " << data.length() << ".\n\n";
#endif
       return false;
    }


    // skip white space
    while( isspace(data[x]) )
     x++;

    // pull off operand
    for ( int k = x; x < data.length(); x++ )
    {
      // spot string constants
      if ( data[x] == '"' )
      {
       if ( in_string_constant == true )
        in_string_constant = false;
       else
        in_string_constant = true;
        
//#ifdef PRINT_DEBUG
//       cout << "\n\nFound a double quote (\"). in_string constant = " << in_string_constant << ".\n\n";
//#endif
        continue;
      }

      if ( isspace(data[x]) && !in_string_constant) // found the end
      {
       operand = data.substr(k,x-k); // pull out the opperand
       break;
      }
    }
    
    if ( command == "if" || command == "IF" ) // we need a second opperand
    {
        // skip white space
        while( isspace(data[x]) )
         x++;

        // pull off operand
        for ( int k = x; x < data.length(); x++ )
        {
          if ( isspace(data[x]) ) // found the end
          {
           operand2 = data.substr(k,x-k); // pull out the opperand
           break;
          }
        }
    }
    
    if ( (command == "write" || command == "WRITE") ) //add a space to the end, fixes a problem for
     operand += ' ';                                  //certain cases of WRITE's

    // minor error checking
    if ( (operand.length() <= 0) &&
       ( (command != "STOP") && (command != "stop") ) )      // stop is the only cmd
    {                                                        // without an operand
#ifdef PRINT_DEBUG
       cout << "\n\n*** ERROR (Line #" << num_lines << ") ***  No operand found!\n\n";
       cout << "\n\nCommand = " << command << ". Line_num = " << line_num << ".\n\n";
       cout << "x = " << x << ".  Data.length() = " << data.length() << ".\n\n";
#endif
       return false;
    }

    // check op2
    if ( operand2 == "None" && (command == "if" || command == "IF" ) )
    {
#ifdef PRINT_DEBUG
      cout << "\n\n*** ERROR (Line #" << num_lines << ") *** No operand2 for an IF!\n\n";
      cout << "\n\nCommand = " << command << ". Line_num = " << line_num << ".\n\n";
      cout << "x = " << x << ". Data.length() = " << data.length() << ".\n\n";
#endif
      return false;
    }

//#ifdef PRINT_DEBUG
//    cout << "\n\nCommand = " << command << ". Line_num = " << line_num << ".\n\n";
//    cout << "x = " << x << ".  Data.length() = " << data.length() << ".\n\n";
//#endif
    
    // data looks good
    (*iter)->operand  = operand;
    (*iter)->operand2 = operand2;
    (*iter)->command  = command;
    (*iter)->line_num = line_num;
    (*iter)->ref      = num_lines;  // store the current line
    
  } // end for
  
  
  return true; // success!
}

// return a string with the numerics of an integer
// I dont know any other way to do this
string num_to_string( int i )
{
   string s = "";
   char buf[16];
   
   //initialize
   s = "";
   buf[0] = '\0';
   
   //make the string equivalent
   sprintf( buf, "%d", i );
   
   s = buf;
   
   return s;
}

// find a variable by name
VAR *find_variable( string name )
{
  list<VAR *>::iterator iter;
  VAR *v = NULL;

  //iterate
  for ( iter = var_list.begin(); iter != var_list.end(); iter++ )
  {
     v = (*iter);
     
     if ( v->name == name )
      return v;
  }

  return NULL;  //return the variable, if found, otherwise return null
}

// will create and link a variable
VAR *create_variable( string name, int value )
{
  VAR *v = NULL;
  
  if ( (v = find_variable(name)) != NULL )
  {
     v->value = value;
     return v;
  }
  else
   v = new VAR;
  
  v->name = (name[0] != '\0') ? name : "";
  v->value = value;
  
  var_list.push_back(v);
  
  return v;
}


// parse an expression
int parse_expression( string z )
{
   list<VAR *>::iterator iter;
   map<std::string, double>::iterator m_iter;
   int y = 0;
   string s;
   VAR *v = NULL;
   
   Parser p(z);
   
   //preload all variables into the parser
   for ( iter = var_list.begin(); iter != var_list.end(); iter++ )
    p[(*iter)->name] = (*iter)->value;

   y = (int)p.Evaluate();
   
   //copy values back, they may have changed, or variables may need to be created
   for ( m_iter = p.symbols_.begin(); m_iter != p.symbols_.end(); m_iter++ )
   {
     v = NULL;
     s = "";

     // assign the string to s
     s = m_iter->first;
     
     // skip parser-premade variables
     if ( s == "e" || s == "pi" )
      continue;

     // does it exist?
     if ( (v = find_variable(s)) == NULL )
       v = create_variable(s, (int)m_iter->second ); // create the variable if it doesnt exist
     else
       v->value = (int)m_iter->second;  // otherwise set the value
   }

#ifdef PRINT_COMP_DEBUG
   for ( iter = var_list.begin(); iter != var_list.end(); iter++ )
    cout << "\nVariable! Name = " << (*iter)->name << " , Value = " << (*iter)->value;
#endif
   
   return y;
}