/*
    NeamerMon - A *Heavily* Modified Ben Eater's Monitor Program
    (The only original things left are his onClock() function and the names of his pin arrays)
    https://eater.net/6502

    I added the ability to drive the 6502 by supplying the clock signal
    *or* reacting to an external clock. I can also replicate much of the 
    functionality of "WozMon".

    I release this code under the CC-BY-NC-SA 4.0 license.
    https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en
    Copyright 2023 Jeremy Thurman (Neamerjell)
    
    Available Commands:
     r addr           reads data at address
     w addr dd        writes byte (dd) at address
     l addb adde      lists data between beginning and ending addresses
     e addr           execute starting from address
         *This prompts for the choice of single step or free run modes    
     reset            resets 6502 and this program, start from scratch
     h   display this list of available commands
    
    
    If CLK_EN pin is tied HIGH:
        Arduino-Controlled Clock (Interactive Mode)
    Else:
        Externally Controlled Clock; Basically Ben Eater's monitor program
 
    Arduino Mega Pins:
    ******************
    With the USB jack on the left, top edge:
    Serial Comms >> [D21, D20], AREF, GND, 
    D13, D12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2,    Serial Comms >> [D1, D0, D14-21] 
    
    Bottom edge:
    NC, IOREF, RESET, +3.3, +5, GND. GND. VIN, 
    D69, D68, D67, D66, D65, D64, D63, D62, D61, D60, D59, D58, D57, D56, D55, D54
    
    Right side, left column, top to bottom:
    +5, D22, D24, D26, D28, D30, D32, D34, D36, D38, D40, D42, D44, D46, D48, D50, D52, GND
    
    Right side, right column, top to bottom:
    +5, D23, D25, D27, D29, D31, D33, D35, D37, D39, D41, D43, D45, D47, D49, D51, D53, GND
    
    My Stackable Headers:
    *********************
    GND, +5, NC, NC, ACIA, VIA, PIA, RAM, ROM, RW, RST, CLK, D0, D1, D2, D3, D4, D5, D6, D7
    GND, +5, IRQ, NMI, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15
    
    Extra 6502 Pins Possibly Needed Later:
    VP, RDY, SYNC
 
*/

// Pins
const char ADDR[] = {23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53};
const char DATA[] = {38, 40, 42, 44, 46, 48, 50, 52}; // This lines up better with my stackable header
#define CLK_EN 22                               // Either +5 or GND (+5 = full functionality)
#define BE 24                                   // Goes directly to 6502
#define IRQ 26                                  
#define NMI 28                                  
#define RW  30 
#define RST 32
#define CLK 34


// Globals
#define PASSIVE 0
#define INTERACTIVE 1
#define READ HIGH
#define WRITE LOW
bool PGM_MODE = INTERACTIVE;                    // Defines Arduino's program mode
/*
    m: Monitor
    p: Execute mode, entered, awating run mode input from user
    s: Execute Step Mode
    f: Execute Free Run Mode
*/
char run_mode = 'm';

void InitInteractive() { // PASSED TEST
    pinMode(BE, OUTPUT);                        // Bus Enable controlled by Arduino
    digitalWrite(BE, HIGH);                     // Bus is disabled when low
    pinMode(RW, OUTPUT);                        // RW controlled by Arduino  
    digitalWrite(RW, READ);                     // Initialize to read mode
    ToggleAddrDir(INPUT);                       // Initialize address to input
    ToggleDataDir(INPUT);                       // Initialize data to input
    pinMode(IRQ, INPUT);                        // Externally controlled IRQ
    pinMode(NMI, INPUT);                        // Externally controlled MNI
    pinMode(CLK, OUTPUT);                       // Clock is controlled by Arduino
    pinMode(RST, OUTPUT);                       // Reset is controlled by Arduino
    digitalWrite(RST, HIGH);                    // Reset is active low
    digitalWrite(CLK, HIGH);                    // Initialize clock to high
}

// Display Help
void DisplayHelp() { // PASSED TEST
    Serial.println("Available Commands:");
    Serial.println(" r addr...........reads data at address");
    Serial.println(" w addr dd........writes byte (dd) at address");
    Serial.println(" l addb adde......lists data between beginning and ending addresses");
    Serial.println();
    Serial.println(" e addr...........execute starting from address");
    Serial.println("        s: Stepped Clock, f: Free Run Clock, q: Quit Execute Mode");   
    Serial.println();
    // The complexity this feature would require far outweighs its benefits
    // (more trouble than it's worth)
    //Serial.println(" a................assembly mode; send commands and automatically");
    //Serial.println("                      single step on enter key, q to exit");
    Serial.println(" reset............resets 6502 and this program, start from scratch");
    Serial.println(" h................display this list of available commands");    
}

/* // Bus I/O - Adapted from onClock(), turned out to be unnecessary
unsigned int AddrIn() {
    // Bitwise operations can be performed on any integer data type;
    // in C++, the byte data type is actually an integer.
    unsigned int a = 0;
    for (int n = 0; n < 16; n += 1) {           // Read each pin
        // Initialization, assignment, and a shorthand if statement, all on one line
        int bit = digitalRead(ADDR[n]) ? 1 : 0;  
        a = (a << 1) + bit;                     // Concatinate each bit to address variable
    }
    return a;
} */

unsigned int DataIn() { // PASSED TEST
    unsigned int d = 0;
    for (int n = 0; n < 8; n += 1) {
        int bit = digitalRead(DATA[n]) ? 1 : 0;
        d = (d << 1) + bit;
    }
    return d;
}

void AddrOut(String hex) { // PASSED TEST
    // c_str converts string to old C character array, strtoul(char_array, ptr, base)
    uint16_t numerical = strtoul(hex.c_str(), NULL, 16); // convert hex substring to numerical value 
    bool bit = 0;
          
    // Is pin A0 on the 6502 the most or least significant bit?
          
    for (int x=0; x<16; x++) {
        bit = numerical & 32768;                // = 1000 0000 0000 0000 binary
        digitalWrite(ADDR[x], bit);
        numerical = numerical << 1;
    }
}

void DataOut(String hex) { // PASSED TEST
    uint8_t numerical = strtoul(hex.c_str(), NULL, 16); 
    bool bit = 0;
            
    for (int x=0; x<8; x++) {
        bit = numerical & 128;                  // = 1000 0000 binary
        digitalWrite(DATA[x], bit);
        numerical = numerical << 1;
    }
}

// Ben Eater's original monitor function - PASSED TEST
void onClock() {
    // Bitwise operations can be performed on any integer data type
    unsigned int address = 0;
    for (int n = 0; n < 16; n += 1) {           // Read each pin
        int bit = digitalRead(ADDR[n]) ? 1 : 0; // Initialization, assignment, and a shorthand if statement, all on one line 
        Serial.print(bit);                      // Output each bit
        address = (address << 1) + bit;         // Concatinate each bit to address variable
    }
  
    Serial.print("   ");                        // Serial.print() continues printing on the same line until a CRLF is sent
    unsigned int data = 0;
    for (int n = 0; n < 8; n += 1) {
        int bit = digitalRead(DATA[n]) ? 1 : 0;
        Serial.print(bit);
        data = (data << 1) + bit;
    }

    /* 
       sprintf "Composes a string with the same text that would be printed if format 
       was used on printf, but instead of being printed, the content is stored as a C string"
       - https://www.programiz.com/cpp-programming/library-function/cstdio/sprintf
       % begins special formatting, x converts unsigned integers to hexadecimal
       I assume %04x means add leading zeros, total length is 4 characters
       %c represents a single character
       The output char array is more than big enough:
       "___XXXX__X_XX" is 13 characters long 
    */
    char output[15];
    sprintf(output, "  %04x  %c %02x", address, digitalRead(RW) ? 'r' : 'W', data);
    Serial.println(output);                     // Serial.println() adds a CRLF to the end of the string automatically
}

// Toggle Bus Data I/O Mode - PASSED TEST
void ToggleAddrDir(uint8_t mode) {              // This takes the constants INPUT, OUTPUT, same as pinMode.
    for (int n = 0; n < 16; n += 1) {
        pinMode(ADDR[n], mode);
    }    
}

void ToggleDataDir(uint8_t mode) { //PASSED TEST
    for (int n = 0; n < 8; n += 1) {
        pinMode(DATA[n], mode);
    }    
}

void PulseClock(int n) { // PASSED TEST
    for ( ; n > 0; n--) {
        digitalWrite(CLK, LOW);
        delay(100);
        digitalWrite(CLK, HIGH);
    }
}

void Reset() { // PASSED TEST
    /*
        "The RESB signal must be held low for at least two clock cycles after VDD 
        reaches operating voltage." - 6502 Datasheet, pg 10
        Delay is one second so it shows up for debugging purposes on the simulator
    */
    digitalWrite(RST, LOW);             // Trigger Reset
    PulseClock(2);                      // Send 2 clock pulses
    digitalWrite(RST, HIGH);            // Release reset button
    PulseClock(7);                      // Complete the 7-cycle reset routine
    Serial.println("NeamerMon");
    Serial.println("Use 'h' to display available commands.");
}

// Main Loop for Interactive Mode - PASSED TEST
void GetInput() {
    while (Serial.available()) {
        bool valid_cmd = false;
        String cmd = Serial.readStringUntil('\n');    
        cmd.toLowerCase(); // toLowerCase() and trim() modify the string in place rather than returning a new one
        cmd.trim(); 
        Serial.println(cmd);                    // Echo the command back to the user
        
        // "r xxxx", read data at address - PASSED TEST
        if (cmd.length() == 6 && cmd.substring(0, 1) == "r" && cmd.substring(1, 2) == " ") { 
            valid_cmd = true;
            digitalWrite(BE, LOW);              // Disable CPU
            ToggleAddrDir(OUTPUT);              // Make sure all address pins are output...
            ToggleDataDir(INPUT);               // ...and data pins are input
            digitalWrite(RW, READ);             // Set R/W to read (high)
            AddrOut(cmd.substring(2, 6));       // Put the address on the bus
            digitalWrite(CLK, LOW);             // Clock pulse required for VIA and others to output data to the bus
            delay(1000);                           // This delay is apparently required for the simulator to work correctly...
            unsigned int d = DataIn();
            digitalWrite(CLK, HIGH);
            digitalWrite(BE, HIGH);             // Reenable CPU
            char o[8] = {};
            unsigned int a = strtoul(cmd.substring(2, 6).c_str(), NULL, 16);
            sprintf(o, "%04x: %02x", a, d);
            Serial.println(o);
        }
        
        
        // "w xxxx xx",write data at address - PASSED TEST
        if (cmd.length() == 9 && cmd.substring(0, 1) == "w" && cmd.substring(1, 2) == " " && cmd.substring(6, 7) == " ") { 
            valid_cmd = true;
            digitalWrite(BE, LOW);              // Disable CPU
            ToggleAddrDir(OUTPUT);              // Make sure all address pins are output...
            ToggleDataDir(OUTPUT);              // ...and data pins are output
            digitalWrite(RW, WRITE);            // Set R/W to write (low)
            DataOut(cmd.substring(7));          // Put the data on the bus FIRST
            AddrOut(cmd.substring(2, 6));       // THEN send the address to make sure correct data is written
            PulseClock(1);                      // Clock pulse required for VIA and others to recieve data from the bus
            digitalWrite(BE, HIGH);             // Reenable CPU
        }
        
        // "l addb adde", list data between addb and adde - PASSED TEST 
        if (cmd.length() == 11 && cmd.substring(0, 1) == "l" && cmd.substring(1, 2) == " " && cmd.substring(6, 7) == " ") { 
            valid_cmd = true;
            digitalWrite(BE, LOW);                              // Disable CPU
            ToggleAddrDir(OUTPUT);                              // Make sure all address pins are output...
            ToggleDataDir(INPUT);                               // ...and data pins are input
            String addb = cmd.substring(2, 6);                  // Substring beginning address
            String adde = cmd.substring(7, 11);                 // Substring ending address
            uint16_t i_addb = strtoul(addb.c_str(), NULL, 16);  // Numerical beginning address
            uint16_t i_adde = strtoul(adde.c_str(), NULL, 16);  // Numerical ending address
            bool b = 0;                                         // Shortened bit variable (avoid possible name conflicts)
            unsigned int d = 0;                                 // Shortened data variable
            String out = addb + ":";                            // Initialize output to first address
            char build[4] = "";                                 // To hold string version of data before concatination to out
            uint16_t s = i_addb;                                // Temporary variable to do bit shifting on
            
            /*
                        addr: b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be| bf
                INDEX   01234567890123456789012345678901234567890123456789|012   0-52
                                  1         2         3         4         |5     Remaining data < 50
                LENGTH  12345678901234567890123456789012345678901234567890|123   1-53
                                 1         2         3         4         5|      Remaining data < 51
            */
            while (i_addb <= i_adde) {
                /*
                    Duplicated and modified part of AddrOut() here because it takes String as 
                    input and sprintf produces a c_str, which is really just a char array
                    and I need to be able to increment the address with each loop
                */                
                for (int x=0; x<16; x++) {
                    b = s & 32768;
                    digitalWrite(ADDR[x], b);
                    s = s << 1;
                }
                
                digitalWrite(CLK, LOW);                         // Clock pulse required for VIA and others to output data to the bus
                delay(1);
                d = DataIn();                                   // Read data at address
                digitalWrite(CLK, HIGH);
                sprintf(build, " %02x", d);                     // Convert int data to string with leading space
                out += build;                                   // Concatinate the last byte

                
                if (out.length() == 53) {                       // If line break is required
                    Serial.println(out);                        // Print the line
                    sprintf(build, "%04x:", i_addb + 1);        // Reset output with next address
                    out = build;
                    build[0] = '\0';                            // Reset temporary variable
                    build[1] = '\0';
                    build[2] = '\0';
                    build[3] = '\0';
                    build[4] = '\0';
                }

                if (i_addb == 65535) {                          // Adding 1 to 65535 in a uint16_t variable causes it to roll over to zero
                  break;
                }
                i_addb++;                                       // Get next address
                s = i_addb;                                     // Save copy to bit shifting temporary variable
            }
            
            
            if (out.length() < 51) {                            // Print remaining data if necessary
                Serial.println(out);
            }
            
            digitalWrite(BE, HIGH);                             // Reenable CPU
            // list data from start to end addresses
            // Loop reads, adding 1 to hex addr_start until addr_end
        }
         
         
        // "e xxxx", execute at address - PASSED TEST
        if (cmd.length() == 6 && cmd.substring(0, 1) == "e" && cmd.substring(1, 2) == " ") { 
            valid_cmd = true;
            ToggleAddrDir(INPUT);                               // 6502 needs control of the address bus
            ToggleDataDir(OUTPUT);                              // Address to start execution needs to be manually written
            pinMode(RW, INPUT);                                 // RW controlled by 6502
            String addr_h = cmd.substring(2, 4);                // Get address high byte
            String addr_l = cmd.substring(4);                   // Get address low byte
            
            /*
                Reset routine simulates pressing a button and holding for 
                two clock cycles, then pulsing the clock 7 times to 
                complete the routine. The first two cycles after that,
                the 6502 reads the little endian two byte address and 
                loads it into the program counter.
                https://www.youtube.com/watch?v=yl8vPW5hydQ&t=906s
            */
            Reset();                                            // Trigger reset routine
            
            DataOut(addr_l);                                    // Put the low byte first (Little Endian)
            PulseClock(1);                                      // Send single clock pulse
            DataOut(addr_h);                                    // Put the high byte last
            PulseClock(1);                                      // Send single clock pulse
            InitInteractive();                                  // Make sure all pins are reset to defaults
            Serial.print("Run clock in (s)tepped or (f)ree run mode? ");
            run_mode = 'p';                                     // Set mode to prompt (handled below)
        }
        
        // Alert user that while command is valid, it is only applicable to certian modes
        if (((cmd == "s") || (cmd == "f") || (cmd == "q")) && (run_mode == 'm'))  {
            valid_cmd = true;                   // Supress "Invalid input..."
            Serial.println("This command is only available while in Execute mode.");
        }
        
        // Get run mode from user - PASSED TEST
        if (cmd.length() == 1 && run_mode == 'p' && cmd == "s") {
            valid_cmd = true;
            run_mode = 's';                                     // GetInput() checks the mode and handles this
        }
        if (cmd.length() == 1 && run_mode == 'p' && cmd == "f") {
            valid_cmd = true;
            run_mode = 'f';                                     // This mode is handled in loop()
        }        
        
        // Trigger single step clock pulse - PASSED TEST
        if (cmd.length() == 0 && (run_mode == 's' || run_mode == 'a') ) { 
            valid_cmd = true;
            PulseClock(1);
            onClock();
        }
        
        // Quit execute or assembly mode - PASSED TEST
        if (cmd.length() == 1 && (run_mode == 'f' || run_mode == 's' || run_mode == 'a') && cmd == "q") {
            valid_cmd = true;
            run_mode = 'm';
        }
        
        
        /* if (cmd.length() == 1 && cmd == "q" && (run_mode != 's' || run_mode != 'f'))  {
            valid_cmd = true;                   // Supress "Invalid input..."
            Serial.println("This command is only available while in Execute mode.");
        }
        if (cmd.length() == 0 && run_mode != 's') { 
            valid_cmd = true;                   // Supress "Invalid input..."
            Serial.println("This command is only available while in Execute mode.");
        } */
        
        /* // Assembly mode
        if (cmd.length() == 1 && cmd == "a") { 
            valid_cmd = true;
            Serial.println("Not yet implemented");
        } */
        

        if (cmd.length() == 5 && cmd == "reset") { // PASSED TEST
            valid_cmd = true;
            InitInteractive();
            Reset();
        }
        
        if (cmd.length() == 1 && cmd == "h") { // PASSED TEST
            valid_cmd = true;
            DisplayHelp();
        }
        
        if (valid_cmd == false) { // PASSED TEST
            Serial.println("Invalid input. Use 'h' to display available commands.");
        }
    } // End While
} // End GetInput


// This runs only once at the beginning
void setup() { // PASSED TEST
    Serial.begin(57600);
    InitInteractive();                          // Set pin numbers and directions
    pinMode(CLK_EN, INPUT);                     // Tie high to enable Arduino-controlled clock
    PGM_MODE = digitalRead(CLK_EN);             // Determine passive or interactive mode
    
    if (PGM_MODE == PASSIVE) {                  // PGM_MODE is initialized to INTERACTIVE
        pinMode(CLK, INPUT);                    // Clock is controlled externally
        pinMode(RW, INPUT);                     // RW controlled by 6502
        pinMode(RST, INPUT);                    // Reset is controlled externally
        attachInterrupt(digitalPinToInterrupt(CLK), onClock, RISING);
    }
    Reset();
}

// This runs no matter what happens in setup()
void loop() { // PASSED TEST
    if (PGM_MODE == INTERACTIVE) {
        GetInput();
        if (run_mode == 'f') {
            PulseClock(1);
            onClock();
        }
    }
    // Else: Just sit in a loop doing nothing but handling interrupts
}
$abcdeabcde151015202530fghijfghij
$abcdeabcde151015202530354045505560fghijfghij