/*To display a text, enter the text in the serial monitor. This code displays
  alphabets, numbers and space. The data displayed can be changed any time while
  running. Press the button shortly to change the direction of the text scroll.
  Press the button for >= four seconds to change the speed. The default speed
  is slow.  */


#include <EEPROM.h>

//Constants and variables for the matrix
#define row 8
#define column 5
#define BUTTON_PIN 19
#define SPEED_CHANGE_TIME 4000 //Button press time to change speeds is four seconds.

int requiredLength;
bool matrix;
#define allowedMatrixLength 255 //Since EEPROM has storage from 0 to 255, this is the maximum allowed length for the text.
int length;

//Variables and constants for debouncing button and check for long and short presses.
#define debounceTime 15
unsigned long buttonPressed;
unsigned long buttonReleased;
unsigned long buttonChange = 0;

//Matrices for all alphabets and numbers. Also includes space.
const bool A[row][column] = {
  {0, 0, 0, 0, 0},
  {0, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool B[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool C[row][column] = {
  {0, 0, 0, 0, 0},
  {0, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {0, 1, 1, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool D[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool E[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool F[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool G[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 0, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool H[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool I[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool J[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {1, 0, 1, 0, 0},
  {1, 1, 1, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool K[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 1, 0},
  {1, 1, 1, 0, 0},
  {1, 0, 0, 1, 0},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool L[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 0},
  {1, 0, 0, 0, 0},
  {1, 0, 0, 0, 0},
  {1, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool M[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 1, 0, 1, 1},
  {1, 0, 1, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool N[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 1, 0, 0, 1},
  {1, 0, 1, 0, 1},
  {1, 0, 0, 1, 1},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool O[row][column] = {
  {0, 0, 0, 0, 0},
  {0, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {0, 1, 1, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool P[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 0},
  {1, 0, 0, 0, 0},
  {1, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool Q[row][column] = {
  {0, 0, 0, 0, 0},
  {0, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 1, 0, 1},
  {1, 0, 0, 1, 0},
  {0, 1, 1, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool R[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 0},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 0},
  {1, 0, 0, 1, 0},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool S[row][column] = {
  {0, 0, 0, 0, 0},
  {0, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {0, 1, 1, 1, 0},
  {0, 0, 0, 0, 1},
  {1, 1, 1, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool T[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool U[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {0, 1, 1, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool V[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {0, 1, 0, 1, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool W[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 1, 0, 1},
  {1, 1, 0, 1, 1},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool X[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {0, 1, 0, 1, 0},
  {0, 0, 1, 0, 0},
  {0, 1, 0, 1, 0},
  {1, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool Y[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {0, 1, 0, 1, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool Z[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 1, 0},
  {0, 0, 1, 0, 0},
  {0, 1, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool SPACE[row][column] = {
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool ZERO[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool ONE[row][column] = {
  {0, 0, 0, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 1, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {0, 0, 1, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool TWO[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool THREE[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 1},
  {0, 1, 1, 1, 1},
  {0, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool FOUR[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 0, 0, 0, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 1},
  {0, 0, 0, 0, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool FIVE[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool SIX[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool SEVEN[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 1, 0},
  {0, 0, 1, 0, 0},
  {0, 1, 0, 0, 0},
  {1, 0, 0, 0, 0},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool EIGHT[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};

const bool NINE[row][column] = {
  {0, 0, 0, 0, 0},
  {1, 1, 1, 1, 1},
  {1, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 1},
  {1, 1, 1, 1, 1},
  {0, 0, 0, 0, 0},
  {0, 0, 0, 0, 0}
};


bool finalMatrix[row][allowedMatrixLength];

unsigned long time;
int timeDelay = 180; //Time for shifting of matrix to give scrolling effect.

//States of scrolling
enum state {
  LEFT,
  RIGHT
};

state mode = LEFT;

//States of the speed
enum speedState {
  SLOW,
  MEDIUM,
  FAST
};

speedState speed = SLOW; //Default speed set to SLOW.


void setup() {

  Serial.begin(9600);

  pinMode(BUTTON_PIN, INPUT_PULLUP);

  for (int i = 2; i < 18; i++) {
    pinMode(i, OUTPUT);
  }

  //Resetting the main matrix and setting it according to the input text.
  resetmatrix();
  setMatrix();

  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), pressed, LOW);
}



void loop() {

  //Lighting up all the LEDs
  for (int j = 0; j < 8; j++) {
    digitalWrite(10, (finalMatrix[j][0]));
    digitalWrite(11, (finalMatrix[j][1]));
    digitalWrite(12, (finalMatrix[j][2]));
    digitalWrite(13, (finalMatrix[j][3]));
    digitalWrite(14, (finalMatrix[j][4]));
    digitalWrite(15, (finalMatrix[j][5]));
    digitalWrite(16, (finalMatrix[j][6]));
    digitalWrite(17, (finalMatrix[j][7]));

    digitalWrite((j + 2), LOW);
    delay(1);
    digitalWrite((j + 2), HIGH);

  }

  //Assigns different time delays for shifting matrix based on different speeds.
  switch (speed) {
    case SLOW:
      timeDelay = 250;
      break;

    case MEDIUM:
      timeDelay = 180;
      break;

    case FAST:
      timeDelay = 90;
      break;
  }

  if (time + timeDelay < millis()) { //Checks if it is time to shift the matrix.
    shiftMatrix(); //Calls the shifting matrix function if yes.
    time = millis();
  }
}

//Function for restting the LEDs and the matrix.
void resetmatrix() {

  for (int k = 2; k < 10; k++) {
    digitalWrite(k, HIGH);
  }

  for (int x = 10; x < 18; x++) {
    digitalWrite(x, LOW);
  }


  for (int i = 0; i < row; i++) {
    for (int j = 0; j < allowedMatrixLength; j++) {
      finalMatrix[i][j] = 0;
    }
  }
}


//This function is called at the end of the loop() if there is data available in Serial.
//Detects the new input and sets the matrix again.
void serialEvent() {

  String data = Serial.readString();
  data.trim(); //Removing the garbage value.
  length = data.length();

  //Showing error if the length of text is greater than EEPROM storage
  if (length > 254) {
    Serial.println("Cannot display! Text too long");
    return;
  }

  Serial.println("Text being displayed: " + data);

  //clearing eeprom to store next data
  for (int i = 0; i < 255; i++) {
    EEPROM.write(i, 255);
  }

  //saving the new data to the EEPROM
  for (int j = 0; j < length; j++) {
    EEPROM.write(j, data[j]);
  }

  resetmatrix();
  setMatrix();
}


//Function to set the main matrix with text to display.
void setMatrix() {

  String data;
  for (int i = 0; i < length; i++) {
    data += (char)EEPROM.read(i);
  }

  Serial.println("Enter the string to display on the matrix: ");

  //The required length of the matrix to display the text.
  requiredLength = (6 * (length + 1));

  //Starting column to store each character in whole matrix.
  int startPosition = 0;

  //Getting every character and its defined matrix to transfer to the final Matrix.
  for (int i = 0; i < length; i++) {
    boolean (*matrix)[row][column];
    char character = data[i];

    switch (character) {
      case 'A':
      case 'a':
        matrix = &A;
        break;

      case 'B':
      case 'b':
        matrix = &B;
        break;

      case 'C':
      case 'c':
        matrix = &C;
        break;

      case 'D':
      case 'd':
        matrix = &D;
        break;

      case 'E':
      case 'e':
        matrix = &E;
        break;

      case 'F':
      case 'f':
        matrix = &F;
        break;

      case 'G':
      case 'g':
        matrix = &G;
        break;

      case 'H':
      case 'h':
        matrix = &H;
        break;

      case 'I':
      case 'i':
        matrix = &I;
        break;

      case 'J':
      case 'j':
        matrix = &J;
        break;

      case 'K':
      case 'k':
        matrix = &K;
        break;

      case 'L':
      case 'l':
        matrix = &L;
        break;

      case 'M':
      case 'm':
        matrix = &M;
        break;

      case 'N':
      case 'n':
        matrix = &N;
        break;

      case 'O':
      case 'o':
        matrix = &O;
        break;

      case 'P':
      case 'p':
        matrix = &P;
        break;

      case 'Q':
      case 'q':
        matrix = &Q;
        break;

      case 'R':
      case 'r':
        matrix = &R;
        break;

      case 'S':
      case 's':
        matrix = &S;
        break;

      case 'T':
      case 't':
        matrix = &T;
        break;

      case 'U':
      case 'u':
        matrix = &U;
        break;

      case 'V':
      case 'v':
        matrix = &V;
        break;

      case 'W':
      case 'w':
        matrix = &W;
        break;

      case 'X':
      case 'x':
        matrix = &X;
        break;

      case 'Y':
      case 'y':
        matrix = &Y;
        break;

      case 'Z':
      case 'z':
        matrix = &Z;
        break;

      case '0':
        matrix = &ZERO;
        break;

      case '1':
        matrix = &ONE;
        break;

      case '2':
        matrix = &TWO;
        break;

      case '3':
        matrix = &THREE;
        break;

      case '4':
        matrix = &FOUR;
        break;

      case '5':
        matrix = &FIVE;
        break;

      case '6':
        matrix = &SIX;
        break;

      case '7':
        matrix = &SEVEN;
        break;

      case '8':
        matrix = &EIGHT;
        break;

      case '9':
        matrix = &NINE;
        break;

      case ' ':
        matrix = &SPACE;
        break;
    }

    //Adding that specific character into the main matrix.
    for (int i = 0; i < 8; i++) {
      for (int j = 0; j < 5; j++) {
        finalMatrix[i][startPosition + j] = (*matrix)[i][j];
      }
    }

    startPosition += 6;

  }
}

//Function called when button pressed.
void pressed() {

  if (debounceTime + buttonChange > millis()) { //Debouncing the switch.
    return;
  }

  buttonPressed = millis();
  buttonChange = millis();

  //Attaching the interrupt to HIGH now to overwrite the previous interrupt.
  //Starts looking for button release. Used to check time of button presses.
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), released, HIGH);
}

void released() {

  if (debounceTime + buttonChange > millis()) {  //Debouncing the switch.
    return;
  }

  buttonReleased = millis();

  if ((buttonReleased - buttonPressed) >= SPEED_CHANGE_TIME) { //Changing the speed if button pressed for >= 4 seconds.
    if (speed == SLOW) {
      speed = MEDIUM;
      Serial.println("Speed changed to MEDIUM");
    }
    else if (speed == MEDIUM) {
      speed = FAST;
      Serial.println("Speed changed to FAST");
    }
    else {
      speed = SLOW;
      Serial.println("Speed changed to SLOW");
    }
  } else { //Changing the modes if short button presses.
    if (mode == LEFT) {
      mode = RIGHT;
      Serial.println("Now scrolling RIGHT");
    }
    else {
      mode = LEFT;
      Serial.println("Now scrolling LEFT");
    }

  }

  buttonChange = millis();

  //Interrupt changed to low again to look for button down.
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), pressed, LOW);
}


//Function to shift the matrix to left or right.
void shiftMatrix() {

  int buff;

  for (int i = 0; i < row; i++) {
    if (mode == LEFT) { //if scroll left:
      buff = finalMatrix[i][0]; //stores the first column
      for (int j = 0; j < (requiredLength - 1); j++ ) {
        finalMatrix[i][j] = finalMatrix[i][j + 1]; //shifting each column to left.
      }
      finalMatrix[i][requiredLength - 1] = buff;
    }
    else if (mode == RIGHT) { //if scroll right:
      buff = finalMatrix[i][requiredLength - 1]; //stores the last column
      for (int j = (requiredLength - 1); j > 0; j--) {
        finalMatrix[i][j] = finalMatrix[i][j - 1]; //shifting each column to right.
      }
      finalMatrix[i][0] = buff;
    }
  }
}