// @topic W130210 state machine implementing desktop calculator
// @brief state machine implementing desktop calculator 

// main_sm_calc.cpp

#include <cstdlib>
#include <stack>
#include <iostream>
#include <string>
#include <sstream>

using std::stack;
using std::cout;
using std::cin;
using std::string;
using std::stringstream;

enum {
    STATE_OPERATOR_BINARY_PLUS = '+',
    STATE_OPERATOR_BINARY_MINUS = '-',
    STATE_OPERATOR_MULTIPLY = '*',
    STATE_OPERATOR_DIVIDE = '/',
    STATE_OPERATOR_EXPR_TERMINATOR = ';',
    STATE_CLEAR = 1000,
    STATE_GET_NEXT_TOKEN,
    STATE_NUMBER,
    STATE_CIN_CLOSED,
    STATE_ERROR_BAD_NUMBER,
    STATE_EXIT
};

void calculate_and_display_the_result( stack< int >& operators, stack< int >& operands )
{
    int op1 = 0;
    int op2 = 0;
    cout << "\t result = ";
    while ( operators.size() ) {
        switch ( operators.top() ) {
        case STATE_OPERATOR_BINARY_PLUS:
            if ( operands.size() < 2 ) {
                cout << "ERROR: Binary operator \'" << operators.top() << "\' requires two operands\n";
                return;
            }
            op2 = operands.top(); operands.pop();
            op1 = operands.top(); operands.pop();
            operands.push( op2 + op1 );
            cout << " " << op2 << char( operators.top() ) << op1 << " = ";
            break;

        case STATE_OPERATOR_BINARY_MINUS:
            if ( operands.size() < 2 ) {
                cout << "ERROR: Binary operator \'" << operators.top() << "\' requires two operands\n";
                return;
            }
            op2 = operands.top(); operands.pop();
            op1 = operands.top(); operands.pop();
            operands.push( op2 - op1 );
            cout << " " << op2 << char( operators.top() ) << op1 << " = ";
            break;

        case STATE_OPERATOR_MULTIPLY:
            if ( operands.size() < 2 ) {
                cout << "ERROR: Binary operator \'" << operators.top() << "\' requires two operands\n";
                return;
            }
            op2 = operands.top(); operands.pop();
            op1 = operands.top(); operands.pop();
            operands.push( op2 * op1 );
            cout << " " << op2 << char( operators.top() ) << op1 << " = ";
            break;

        case STATE_OPERATOR_DIVIDE:
            if ( operands.size() < 2 ) {
                cout << "ERROR: Binary operator \'" << operators.top() << "\' requires two operands\n";
                return;
            }
            op2 = operands.top(); operands.pop();
            op1 = operands.top(); operands.pop();
            if ( op2 == 0 ) {
                cout << "ERROR: Division by zero is illegal\n";
                return;
            }
            operands.push( op2 / op1 );
            cout << " " << op2 << char( operators.top() ) << op1 << " = ";
            break;

        default:
            cout << "ERROR: Unexpected operator \'"<< char( operators.top() ) << "\'\n";
            return;
        }
        operators.pop();
    }
    if ( operands.size() != 1 ) {
        cout << "ERROR: bad arithmetic expression syntax\n";
        return;
    }
    cout << "\t" << operands.top() << "\n";

}

int main()
{
    //int result = 0;
    int number = 0;
    string input;
    stack< int > states;
    stack< int > operands;
    stack< int > operators;

    states.push( STATE_CLEAR );

    while ( states.size() > 0 ) {
        int state = states.top(); // get next state
        if ( state == STATE_EXIT ) {
            break;
        }

        states.pop(); // remove current state from the stack
        switch ( state ) {

        case STATE_CLEAR:
            while ( operands.size() ) operands.pop();
            while ( operators.size() ) operators.pop();
            states.push( STATE_GET_NEXT_TOKEN );
            break;

        case STATE_CIN_CLOSED:
            cout << "Input stream has closed, exiting...\n";
            states.push( STATE_EXIT );
            break;

        case STATE_GET_NEXT_TOKEN:
            if ( cin >> input ) {
                // parse and process input token:
                if ( input == "q" || input == "Q" || input == "quit" ) states.push( STATE_EXIT );
                else if ( isdigit( input[ 0 ] ) ) states.push( STATE_NUMBER );
                else if ( input.length() > 1 ) states.push( STATE_NUMBER ); // assume any multi-character token is a number
                else if ( ispunct( input[ 0 ] ) ) states.push( input[ 0 ] ); // assume it's an operator
            }
            else {
                states.push( STATE_CIN_CLOSED );
            }
            break;

        case STATE_NUMBER:
        {
            // convert input to a numeric value
            stringstream buffer( input );
            if ( buffer >> number ) {
                states.push( STATE_GET_NEXT_TOKEN );
                operands.push( number );
            } else {
                states.push( STATE_ERROR_BAD_NUMBER );
            }
            break;
        }

        case STATE_OPERATOR_BINARY_PLUS:
        case STATE_OPERATOR_BINARY_MINUS:
        case STATE_OPERATOR_MULTIPLY:
        case STATE_OPERATOR_DIVIDE:
            operators.push( state );
            states.push( STATE_GET_NEXT_TOKEN );
            break;

        case STATE_OPERATOR_EXPR_TERMINATOR:
            calculate_and_display_the_result( operators, operands );
            states.push( STATE_CLEAR );
            break;

        default:
            if ( ( 0 < state ) && ( state <= 255 ) && isprint( state ) ) {
                cout << "Unexpected token \'" << char( state ) << "\'\n";
            } else {
                cout << "Unexpected internal state " << state << "\n";
            }
            states.push( STATE_CLEAR );
            break;

        }//switch
    }//while

    system( "pause" );
    return 0;
}