// ---------------------------------------------------------------------------------- //
// Apple 1 Replica firmware for Arduino                                               //
//                                                                                    //
// Version 1.0                                                                        //
// By Ruud van Falier                                                                 //
//                                                                                    //
// Enables serial communication between an Arduino and the Apple 1 PIA.               //
// Also implements a blinking cursor and green text using VT100 escape codes          //
// (VT100 codes only tested with PuTTY client!)                                       //
//                                                                                    //
// Heavily based on Propeller source code from Briel Computers' Apple 1 replica.      //
// ---------------------------------------------------------------------------------- //

// Port definitions (Arduino pins connected to the PIA)
#define PIN_DA  A0
#define PIN_RDA A1
#define PIN_PB0 2
#define PIN_PB1 3
#define PIN_PB2 4
#define PIN_PB3 A5
#define PIN_PB4 A4
#define PIN_PB5 A3
#define PIN_PB6 A2
#define PIN_PA0 5
#define PIN_PA1 6
#define PIN_PA2 7
#define PIN_PA3 8
#define PIN_PA4 9
#define PIN_PA5 10
#define PIN_PA6 11
#define PIN_STROBE 12

#define DELAY_VIDEO 1
#define DELAY_ASCII 1

// VT100 codes (used for cursor and text color implementation)
#define VT100_OFF           "\033[0m"     // Disable formatting
#define VT100_DEFAULT       "\033[32m"    // Color green
#define VT100_BLINK         "\033[5;32m"  // Blink color green
#define VT100_BOLD          "\033[1;32m"  // Bold color green
#define VT100_CLS           "\033[2J"     // Clear screen
#define VT100_RESET_CURSOR  "\033[H"      // Position cursor in top left corner
#define VT100_CURSOR_LEFT   "\033[1D"     // Move cursor one column to the left
#define VT100_ERASE_EOL     "\033[K"      // Erase from cursor until end of line
#define CURSOR_CHAR   "@"

// Global variables
uint8_t video_data = 0;
uint8_t video_data_pins[] = { PIN_PB0, PIN_PB1, PIN_PB2, PIN_PB3, PIN_PB4, PIN_PB5, PIN_PB6 };
uint8_t serial_data;

bool cursor_visible = false;

/*
 * Program initialization
 */
void setup() 
{
  // Setup video data pins (output from the PIA)
  pinMode(PIN_RDA, OUTPUT);
  pinMode(PIN_DA, INPUT);
  pinMode(PIN_PB0, INPUT);
  pinMode(PIN_PB1, INPUT);
  pinMode(PIN_PB2, INPUT);
  pinMode(PIN_PB3, INPUT);
  pinMode(PIN_PB4, INPUT);
  pinMode(PIN_PB5, INPUT);
  pinMode(PIN_PB6, INPUT);

  // Setup ASCII data pins (input for the PIA)
  pinMode(PIN_STROBE, OUTPUT);
  pinMode(PIN_PA0, OUTPUT);
  pinMode(PIN_PA1, OUTPUT);
  pinMode(PIN_PA2, OUTPUT);
  pinMode(PIN_PA3, OUTPUT);
  pinMode(PIN_PA4, OUTPUT);
  pinMode(PIN_PA5, OUTPUT);
  pinMode(PIN_PA6, OUTPUT);

  Serial.begin(9600);
 
  display_boot_message();
}

/*
 * Program loop
 */
void loop() 
{
  process_video_data();
  process_serial_data(); 
}

/*
 * Displays the Apple 1 Replica firmware boot message and enables the blinking cursor
 */
void display_boot_message()
{
  cursor_visible = false;

  Serial.print(VT100_CLS);
  Serial.print(VT100_RESET_CURSOR);
  Serial.print(VT100_OFF);
  Serial.print(VT100_DEFAULT);
  Serial.println("+-----------------------+");
  Serial.print("|    ");
  Serial.print(VT100_OFF);
  Serial.print(VT100_BOLD);
  Serial.print("APPLE 1 REPLICA");
  Serial.print(VT100_OFF);
  Serial.print(VT100_DEFAULT);
  Serial.println("    |");
  Serial.print(VT100_OFF);
  Serial.print(VT100_DEFAULT);
  Serial.println("|-----------------------|");
  Serial.println("| FIRMWARE VERSION 1.0  |");
  Serial.println("| RUUD VAN FALIER, 2017 |");
  Serial.println("+-----------------------+");
  Serial.println();
  Serial.println("READY...");
  Serial.println();
  Serial.print(VT100_OFF);

  show_cursor();
}

/*
 * Retrieves video (character) data from the PIA and sends it to the user over the serial connection.
 */
void process_video_data()
{
  // Tell the PIA we are ready to receive data.
  digitalWrite(PIN_RDA, HIGH);
  delay(DELAY_VIDEO);

  while (digitalRead(PIN_DA))
  {
    // Data is available.
    video_data = 0;

    // Decode the data bits to a byte.
    for (int i = 0; i < 7; i++)
    { 
      if (digitalRead(video_data_pins[i]))
      {
        video_data |= (1 << i);
      }
    }

    // Send video data over serial connection.
    if (video_data == 13)
    {
      // Carrage return.
      hide_cursor();
      Serial.println();
    }
    else if (video_data > 31)
    {
      // Display-compatible character.
      hide_cursor();
      Serial.print(VT100_DEFAULT);
      Serial.print((char)video_data);
      Serial.print(VT100_OFF);
    }

    // Done receiving this byte.
    digitalWrite(PIN_RDA, LOW);
    delay(DELAY_VIDEO);

    // Tell the PIA we are ready to receive more data.
    digitalWrite(PIN_RDA, HIGH);
    delay(DELAY_VIDEO);
  }

  show_cursor();
}

/*
 * Processes data received from the serial connection.
 * Puts the received data on to the PIA where the CPU will read it from.
 */
void process_serial_data()
{
  // Wait for serial data coming in.
  while (Serial.available() > 0)
  {
    serial_data = Serial.read();

    if (serial_data == 203) 
    {
      // Translate uppercase ESC to normal ESC.
      serial_data = 27;
    }

    if (serial_data >= 97 && serial_data <= 122)
    {
      // Translate lowercase characters to uppercase as the Apple 1 only supports uppercase.
      serial_data -= 32;
    }

    if (serial_data < 96)   
    {
      // Encode any Apple 1 compatible character to data bits.
      digitalWrite(PIN_PA6, bitRead(serial_data, 6));
      digitalWrite(PIN_PA5, bitRead(serial_data, 5));
      digitalWrite(PIN_PA4, bitRead(serial_data, 4));
      digitalWrite(PIN_PA3, bitRead(serial_data, 3));
      digitalWrite(PIN_PA2, bitRead(serial_data, 2));
      digitalWrite(PIN_PA1, bitRead(serial_data, 1));
      digitalWrite(PIN_PA0, bitRead(serial_data, 0));

      // Pulse the STROBE bit so the PIA will process the input.
      digitalWrite(PIN_STROBE, HIGH);
      delay(DELAY_ASCII);
      digitalWrite(PIN_STROBE, LOW);
    }

    process_video_data();
  }
}

/*
 * Remove the cursor character from the screen by sending a backspace.
 */
void hide_cursor()
{
  if (cursor_visible)
  {
    Serial.print(VT100_CURSOR_LEFT);
    Serial.print(VT100_ERASE_EOL);
    cursor_visible = false;
  }
}

/*
 * Display blinking cursor character.
 */
void show_cursor()
{
  if (!cursor_visible)
  {
    Serial.print(VT100_BLINK);
    Serial.print(CURSOR_CHAR);
    Serial.print(VT100_OFF);
    cursor_visible = true;
  }
}