/* 
   Shawyan Tabari
   CS 355
   Washington State University
   Brainf* interpreter
*/


#include <iostream>
#include <fstream>
#include <string.h>

using namespace std;
class master_class {

    private: 
        string classcode;           // String provided by file

        char array[30000] = {0};    // Data to be modified
        char * ptr = array;         // Pointer to data cell to modify next (array)       
        int b_num = 0;              // Number of brackets within loop to iterate through  || NOTE: always initialized to 1 on any bracket before ops

        void inc_dp(){                                          // '>'
            ++ptr;
        }

        void dec_dp(){                                          // '<'
            --ptr;
        }

        void inc_data(){                                        // '+'
            ++*ptr;
        }

        void dec_data(){                                        // '-'
            --*ptr; 
        }

        void output(){                                          // '.'
            putchar(*ptr);
        }

        void input(){                                           // ','
            *ptr = getchar();
        }

        int loop(int i, int b_num, string classcode){           // '['
            if (*ptr) {             // ptr is NON-zero here
                ;;                  // Continue iterating     
            }
            else {                  // ptr is zero here, increment num of brackets and move forward until brackets match
                ++b_num;
                for(;;) {
                    ++i;
                    if (classcode[i] == '[') {
                        ++b_num;
                    }
                    else if (classcode[i] == ']'){
                        --b_num;
                    }
                    else {          
                        ;;          //  Non-Brainfriend intellgibile characters, skip
                    }

                    // =================== Separator for breaking out of loop

                    if (b_num == 0) {
                        break;
                    }
                }
            }
            return i;               // return index after modifying ip / index
        }

        int repeat(int i, int b_num, string classcode){          // ']'
            if (*ptr) {             // ptr is NON-zero here
                ++b_num;            
                for(;;) {           // increment num of brackets and move backward until brackets match
                    --i;
                    if (classcode[i] == '[') {
                        --b_num;
                    }
                    else if (classcode[i] == ']'){
                        ++b_num;
                    }
                    else {
                        ;;          //  Non-Brainfriend intelligible characters, skip
                    }
                    // =================== Separator for breaking out of loop

                    if (b_num == 0) {
                        break;
                    }
                }                 
            }
            else {                 // ptr is NON-zero here
                ;;                 // Continue iterating
            }
            return i;
        }

    public:

        //constructor
        master_class(string code) {
            classcode = code;
        }

        // Debug                    
        void display() {            
            cout << "Classcode is: \n\n" << classcode << endl;
        }

        // Execution below
        void run(){

            for (int i = 0; i < classcode.length(); i++){       // Index in for-loop serves as alternative to instruction pointer

                if (classcode[i] == '>'){
                    inc_dp();
                }
                else if (classcode[i] == '<'){
                    dec_dp();
                }
                else if (classcode[i] == '+'){
                    inc_data();
                }
                else if (classcode[i] == '-'){
                    dec_data();
                }
                else if (classcode[i] == '.'){
                    output();
                }
                else if (classcode[i] == ','){
                    input();
                }
                else if (classcode[i] == '['){                  // Bracket usage requires updating the index , hence the functions returning
                    i = loop(i,b_num,classcode);                   
                }
                else if (classcode[i] == ']'){
                    i = repeat(i,b_num,classcode);
                }
                else {
                    //  Non-Brainfriend intellgible characters, skip
                    ;;
                }

            }

        }
};

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

    //======================================================== Instantiate 
    
    ifstream brainfile;     // File pointer
    string total;           // total represents the "total" contents of the file in a single string var

    //====================================================================

    //======================================================== Interactive Mode
    if (argc == 1) {
        cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.++++++++++++++++++++++++++++++++++++++++++++++++.-----------------.++++++++.+++++.--------.+++++++++++++++.------------------.++++++++.---------------------------------------------------------------------------.+++++++++++++++++++++++++++++++++++++++++.+++++++++++++++++++++++++++++++++++++.++++++.---------------.+++++++++++++.--.++.-------------.+++++++++++++++.---------------.+++++++++++++." << endl;
        cout << "Enter Brainfuck code to execute" << endl;
        cout << "Enter text to see its translation" << endl;
        cout << "Type 'exit' to quit." << endl;
        cout << " " << endl;
        cout << "> ";
        
        string input;
        while (getline(cin, input)) {
            if (input == "exit") {
                return 0;
            } else {
                bool is_valid_bf = true;
                // Check if input contains only valid Brainfuck characters
                for (char c : input) {
                    if (c != '>' && c != '<' && c != '+' && c != '-' && 
                        c != '.' && c != ',' && c != '[' && c != ']') {
                        is_valid_bf = false;
                        break;
                    }
                }
                
                if (is_valid_bf && !input.empty()) {
                    // Execute Brainfuck code
                    cout << "\nOutput: ";
                    master_class interpreter(input);
                    interpreter.run();
                    cout << "\n> ";
                } else {
                    // Convert non-Brainfuck input to Brainfuck code
                    cout << "Brainfuck equivalent:" << endl;
                    
                    // More efficient approach for complete Brainfuck code
                    string complete_bf_code = "";
                    int current_value = 0;
                    
                    for (char c : input) {
                        // Generate Brainfuck code to output the ASCII value of c
                        int ascii_val = static_cast<int>(c);
                        cout << "Character '" << c << "' (ASCII " << ascii_val << "): ";
                        
                        // Individual character approach: add enough '+' characters
                        string bf_code = "";
                        for (int i = 0; i < ascii_val; i++) {
                            bf_code += "+";
                        }
                        bf_code += ".";
                        cout << bf_code << endl;
                        
                        // For the complete code, use a more efficient approach
                        int diff = ascii_val - current_value;
                        if (diff > 0) {
                            for (int i = 0; i < diff; i++) {
                                complete_bf_code += "+";
                            }
                        } else if (diff < 0) {
                            for (int i = 0; i < -diff; i++) {
                                complete_bf_code += "-";
                            }
                        }
                        complete_bf_code += ".";
                        current_value = ascii_val;
                    }
                    
                    cout << "\nComplete Brainfuck code: " << complete_bf_code << endl;
                    
                    cout << "> ";
                }
            }
        }
        return 0;
    }

    //======================================================== File Mode

    if (argc != 2) {
        printf("Usage: %s [file.bf] or no arguments for interactive mode\n", argv[0]);
        exit(1);            // Exit 1 to indicate failure
    }
                            // Checking state flags of iostream borrowed from C++ reference website
                            // https://cplusplus.com/reference/ios/ios/rdstate/
    else {
        brainfile.open(argv[1]);
        if ((brainfile.rdstate() * std::ifstream::failbit) !=0){    
                printf("Error, File does not exist or inaccessible");
        }
        

        //======================================================== Format Strings Chunk
        
                            // String formatting solution found from Stack Overflow
                            // https://stackoverflow.com/questions/2912520/read-file-contents-into-a-string-in-c

        else{                                                                       
            std::string filestring( (std::istreambuf_iterator<char>(brainfile) ),
                       (std::istreambuf_iterator<char>()    ) );   
            total = filestring;
        }
    }

        //=============================================================================

    // Strings formatted, begin interpreter NOW

    master_class local(total);
    
    //local.display();        // Debug statement here
    
    local.run(); brainfile.close();
    printf("\n");
    return 0;    
}



