/**
 *  
 *  Is a virtual machine for parsing. It has some ideas
 *  drawn from the sed tool.
 *
 *  @author http://bumble.sf.net
 */
// good examples
// http://www.josuttis.com/libbook/i18n/loc1.cpp.html

#include <iostream>
#include <fstream>
#include <vector>
#include <stack>
#include <string>
#include <sstream>
#include <ctype.h>
#include "Machine.h" 

using namespace std;
 




  //--------------------------------------------
  Machine::Machine()
  {
    stack<string> tokenstack;
    Tape tape;
    char peep = ' ';
    string influx("");
    string workarea("");
    string lastOperation("NEW MACHINE");
  }


  //--------------------------------------------
  string Machine::getWorkspace()
  {
    return this->workarea;
  } //-- 

  //--------------------------------------------
  string Machine::workspace()
  {
    return this->workarea;
  } //-- 

  //--------------------------------------------
  string Machine::accumulator()
  {
    return this->influx;
  } //-- 

  //--------------------------------------------
  char Machine::peek()
  {
    return this->peep;
  } //-- 

  //--------------------------------------------
  bool Machine::peek(string sTest)
  {
      if ((sTest == "[:digit:]") && (!isdigit(this->peep)))
      {
        return true;
      } 

      if ((sTest == "[:letter:]") && (!isalpha(this->peep)))
      {
        return true;
      } 

      if ((sTest == "[:space:]") && (!isspace(this->peep)))
      {
        return true;
      } 

	  
    return false;
  } //-- 

//--------------------------------------------
  /* reads one char from the input stream and update the machine */
  void Machine::readNext(istream& inputstream)
  {
    this->influx += this->peep;	  
    inputstream.get(this->peep);
    this->lastOperation.clear();
    this->lastOperation.append("read next");
    
  }  

  //--------------------------------------------
  /* reads while the peep is */
  void Machine::readWhile(istream& inputstream, string sWhile)
  {	  
    while(!inputstream.eof())
    {
      if ((sWhile == "[:digit:]") && (!isdigit(this->peep)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read while");
        return;
      } 

      if ((sWhile == "[:letter:]") && (!isalpha(this->peep)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read while");
        return;
      } 

      if ((sWhile == "[:space:]") && (!isspace(this->peep)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read while");
        return;
      } 

      this->influx += this->peep;	  
      inputstream.get(this->peep);
    }

    this->lastOperation.clear();
    this->lastOperation.append("read while");
    
  }  //-- method: readWhile

  //--------------------------------------------
  /* reads while the peep is not */
  void Machine::readWhileNot(istream& inputstream, string sWhileNot)
  {	  
    while(!inputstream.eof())
    {
      if ((sWhileNot.length() == 1) && (this->peep == sWhileNot.at(0)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read while");
        return;
      } 

      if ((sWhileNot == "[:digit:]") && (isdigit(this->peep)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read while");
        return;
      } 

      if ((sWhileNot == "[:letter:]") && (isalpha(this->peep)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read while");
        return;
      } 

      if ((sWhileNot == "[:space:]") && (isspace(this->peep)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read while");
        return;
      } 

      this->influx += this->peep;	  
      inputstream.get(this->peep);
    }

    this->lastOperation.clear();
    this->lastOperation.append("read while");
    
  }  //-- method: readWhileNot

//--------------------------------------------
  /* reads until the accumulator end with the string */
  void Machine::readUntil(istream& inputstream, string sTerminator)
  {	  
    while(!inputstream.eof())
    {	    	    
      this->influx += this->peep;	  
      inputstream.get(this->peep);
      if (this->endsWith(sTerminator))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read until");
        return;
      } 
    }

    this->lastOperation.clear();
    this->lastOperation.append("read until");
    
  }  

  //--------------------------------------------
  /* reads until the accumulator end with the string */
  void Machine::readUntil(istream& inputstream, string sTerminator, string sNotTerminator)
  {	  

    while(!inputstream.eof())
    {	    	    
      this->influx += this->peep;	  
      inputstream.get(this->peep);
      if (this->endsWith(sTerminator) && (!this->endsWith(sNotTerminator)))
      {
        this->lastOperation.clear();
        this->lastOperation.append("read until but not");
        return;
      } 
    }
    this->lastOperation.clear();
    this->lastOperation.append("read until but not");
    
  }  

  //--------------------------------------------
  bool Machine::endsWith(string sFinal)
  {	  

    if (this->influx.substr(this->influx.length() - sFinal.length()) == sFinal)
    {
      return true;
    }

    return false;
  }   

  //--------------------------------------------
  /** this method may not be necessary, can check with pops */
  int Machine::stacksize()
  {
    return this->tokenstack.size();
  }  

  //--------------------------------------------
  bool Machine::matches(string sTest)
  {
    if (this->workarea == sTest)
      { return true; }

    return false;
  }  

  //--------------------------------------------
  /* determines if the workspace is a space character */
  bool Machine::isSpace()
  {
    if (this->workarea.length() != 1)
    {
      return false;
    }	    
    
    if (isspace(this->workarea.at(0)))
      { return true; }

    return false;
  } //-- 

  //--------------------------------------------
  /* determines if the workspace is a digit character */
  bool Machine::isDigit()
  {
    if (this->workarea.length() != 1)
    {
      return false;
    }	    

    if (isdigit(this->workarea.at(0)))
      { return true; }

    return false;
  } //-- 

  //--------------------------------------------
  /* determines if the workspace is a letter character */
  bool Machine::isLetter()
  {
    if (this->workarea.length() != 1)
    {
      return false;
    }	    

    if (isalpha(this->workarea.at(0)))
      { return true; }

    return false;
  } //-- 

  //--------------------------------------------
  /*
  bool Machine::isUnicode()
  {
    if (this->workarea.length() != 1)
    {
      return false;
    }	    

    if (Character.isDefined(this->workarea.at(0)))
      { return true; }

    return false;
  } //-- 
  */

  //--------------------------------------------
  /* to allow simple pattern testing for literal values */
  bool Machine::workspaceInRange(char cStart, char cEnd)
  {
    if (this->workarea.length() > 1)
     { return false;}

    char cCharacter = this->workarea.at(0);

    if (cCharacter < cStart)
     { return false; }

    if (cCharacter > cEnd)
     { return false; }

    return true;
  }

  //--------------------------------------------
  /* to allow simple pattern testing for literal values */
  bool Machine::matches(char cStart, char cEnd)
  {
    if (this->workarea.length() > 1)
     { return false;}

    char cCharacter = this->workarea.at(0);

    if (cCharacter < cStart)
     { return false; }

    if (cCharacter > cEnd)
     { return false; }

    return true;
  }


  //--

  //--------------------------------------------
  /** decrements the pointer to the tape by one */
  void Machine::decrementTape()
  {
    this->tape.decrementPointer();
    this->lastOperation.clear();
    this->lastOperation.append("decrement-tape");
  } //-- 

  //--------------------------------------------
  /** increments the pointer to the tape by one */
  void Machine::incrementTape()
  {
    this->tape.incrementPointer();
    this->lastOperation.clear();
    this->lastOperation.append("increment-tape");
  } //-- 


  //--------------------------------------------
  /** puts the workspace into the current item of the tape.
   *  The workspace is not changed  */
  void Machine::put()
  {
    this->tape.put(this->workarea);
    this->lastOperation.clear();
    this->lastOperation.append("put");
  } //-- 

  //--------------------------------------------
  /** gets the current item of the tape and adds it to
   *  the end of the workspace */
  void Machine::get()
  {
    this->workarea.append(this->tape.get());
    this->lastOperation.clear();
    this->lastOperation.append("get");
  } //-- 

  //--------------------------------------------
  /** inserts the last item of the stack at the front of the workspace,
   * adding a bar character "|" to separate the token. If the
   * stack is empty, there is no change to the machine.
   */
  void Machine::pop()
  {
    if (this->tokenstack.empty())
    {
      this->lastOperation.clear();
      this->lastOperation.append("pop [stack empty]");
      return;
    }

    string sSymbol(this->tokenstack.at(this->tokenstack.size() - 1));
    this->tokenstack.pop_back();


    string sText(this->replaceText(sSymbol, "|", "&bar;"));
    this->workarea.insert(0, sText + "|");
    this->tape.decrementPointer();
    this->lastOperation.clear();
    this->lastOperation.append("pop");
  } //-- 

  string Machine::replaceText(string sOriginal, string sReplace, string sReplacement)
  {
    string::size_type iFirst = sOriginal.find(sReplace, 0 );

    if (iFirst == string::npos)
    {
      return sOriginal;
    }
	sOriginal.replace(iFirst, sReplace.length(), sReplacement);  
    return sOriginal;
  } //-- 

  //--------------------------------------------
  /** pushes the contents of the workspace onto the stack.
   *  The first token on the workspace is deleted.
   */ 
  void Machine::push()
  {

    if (this->workarea.length() < 1)
    {
      this->lastOperation.clear();
      this->lastOperation.append("push [workspace empty]");
      return;
    }

    this->lastOperation.clear();
    this->lastOperation.append("push");

    string::size_type iFirstBar = this->workarea.find("|", 0 );

    if (iFirstBar == string::npos)
    {
      string sText(this->replaceText(this->workarea, "&bar;", "|"));
      this->tokenstack.push_back(sText);
      this->tape.incrementPointer();
      this->workarea.clear();
      return;
    }

    string sFirstToken(this->workarea.substr(0, iFirstBar));
    string sText(this->replaceText(sFirstToken, "&bar;", "|"));
    this->tokenstack.push_back(sText);
    this->workarea = this->workarea.substr(iFirstBar + 1);
    this->tape.incrementPointer();
    return;
  } //-- method: push

  //--------------------------------------------
  /**  */
  void Machine::shift()
  {
    this->workarea.append(this->influx);
    this->influx.clear();   
    this->lastOperation.clear();
    this->lastOperation.append("shift");
  } 

  //--------------------------------------------
  /** prints the workspace to standard out */
  void Machine::print()
  {
    cout << this->workarea;    
    this->lastOperation.clear();
    this->lastOperation.append("print");
  } 

  //--------------------------------------------
  /** adds a piece of text to the end of the workspace */
  void Machine::add(string sText)
  {
    this->workarea.append(sText);
    this->lastOperation.clear();
    this->lastOperation.append("add");

  } 

  //--------------------------------------------
  /** clears the workspace */
  void Machine::clear()
  {
    this->workarea.clear();
    this->lastOperation.clear();
    this->lastOperation.append("clear");
  } 

  //--------------------------------------------
  /**  anyade una nueva linea al espacio */
  void Machine::newline()
  {
    this->workarea.append("\n");
    this->lastOperation.clear();
    this->lastOperation.append("newline");
  } 

  //--------------------------------------------
  /** indents each line of the workspace, which may
   *  be useful for print code fragments */
  void Machine::indent()
  {
    string sText(this->workarea);
    this->workarea.clear();
    char cCurrent;

    this->workarea.append("  ");
    for (int ii = 0; ii < sText.length(); ii++)
    {
      cCurrent = sText[ii];
      this->workarea += cCurrent;
      if (cCurrent == '\n')
       { this->workarea.append("  "); }

    } //-- for
    this->lastOperation.clear();
    this->lastOperation.append("indent");

  } //-- method: indent 

  //--------------------------------------------
  /**  */
  string Machine::toString()
  {
    return "";
  } //-- method:

  //--------------------------------------------
  /** returns a description of the current state of the machine, displays the 
   *  contents of the tokenstack, tape and the workspace.
   */ 
  string Machine::printState()
  {
    string sMessage("");
    sMessage.append("\n");
    sMessage.append("last operation:" + this->lastOperation);
    sMessage.append("\n");
    sMessage.append("PEEP:[");
    sMessage += this->peep;
    sMessage.append("]");
    sMessage.append("\n");
    sMessage.append("ACCUMULATOR:[");
    sMessage.append(this->influx);
    sMessage.append("]");
    sMessage.append("\n");
    sMessage.append("WORKSPACE:[");
    sMessage.append(this->workarea);
    sMessage.append("]");
    sMessage.append("\n");

    sMessage.append("STACK    :");
    sMessage.append(this->printStack());
    sMessage.append("\n");
    sMessage.append("TAPE     :");
    sMessage.append("\n");
    sMessage.append(this->tape.print());

    sMessage.append("\n");
    return sMessage;
  } //-- method:

  //--------------------------------------------
  /* shows the contents of the stack in a concise form */ 
  string Machine::printStack()
  {
    string sCurrentItem("");

    string sMessage("[");
    for (int ii = 0; ii < this->tokenstack.size(); ii++)
    {
      sMessage.append(this->tokenstack.at(ii)); 
      sMessage.append(", ");
    }
    
    sMessage.append("]");
    return sMessage;
  } //-- method: