#include <SPI.h>
#include <Wire.h>
#include <MIDI.h>
#include <midi_Defs.h>
#include <midi_Message.h>
#include <midi_Namespace.h>
#include <midi_Settings.h>
#include <EEPROM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

/* OLED DISPLAY VARIABLES */
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT);

/* CHANGE PINS TO SUIT YOUR ARDUINO PINOUT */
const int sw1Pin = A0;
const int sw2Pin = A1;
const int sw3Pin = A2;
const int sw4Pin = A3;
const int sw5Pin = 10;
const int RELAY1 = 2; //set pin for RELAY 1 (CLEAN) D2
const int RELAY2 = 3; //set pin for RELAY 2 (GAIN1/GAIN2) D3
const int RELAY3 = 4; //set pin for RELAY 3 (BOOST) D4
const int RELAY4 = 5; //set pin for RELAY 4 (SAT) D5
const int RELAY5 = 6; //set pin for RELAY 5 (PLEXI) D6
const int RELAY6 = 7; //set pin for RELAY 6 (FX LOOP)
const int RELAY7 = 8; //set pin for RELAY 7 (SPARE)
const int RELAY8 = 9; //set pin for RELAY 8 (SPARE)
const int storeControl = 11;
const int storePin = 12;
const int mutePin = 13;

#define StoreSave 104

// define output pins
int outputPin[] = {2, 3, 4, 5, 6, 7, 8, 9};

int swPush = 0;
boolean swState = LOW;
boolean swStateLast = LOW;

const unsigned char NaN [] PROGMEM = {
  0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
  0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x03,
  0x80, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x1f, 0xc0, 0x00,
  0x00, 0x00, 0x03, 0xf8, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x3f, 0xe0, 0x00, 0x00, 0x00,
  0x03, 0xf8, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xf8,
  0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
  0xe0, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x1f, 0xe0, 0x00,
  0x00, 0x07, 0xfc, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x03,
  0xfc, 0x00, 0x0f, 0x00, 0x07, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x03, 0xfc, 0x00,
  0x00, 0x04, 0x7f, 0xe0, 0x03, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xe0, 0x01, 0xfc, 0x00, 0x00, 0xff,
  0xff, 0xf0, 0x00, 0xf8, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x0c, 0xc0, 0x00, 0x00, 0xff, 0xff, 0xfc,
  0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x03, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0x83, 0xf0,
  0x00, 0x00, 0xff, 0xff, 0xff, 0xc7, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe3, 0x80, 0x00, 0x00,
  0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x7f, 0xff,
  0xf7, 0xf0, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xf7, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0,
  0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xfe, 0x8f, 0xf0, 0x00, 0x00,
  0x00, 0x1f, 0x80, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x03, 0x80, 0x00, 0x00
};

uint8_t midi_channel = 0;
MIDI_CREATE_DEFAULT_INSTANCE();

void handleError(int8_t err)
{
  digitalWrite(LED_BUILTIN, (err == 0) ? LOW : HIGH);
}

void midiStore() {
  boolean storeState = false;
  storeState = digitalRead(storeControl);
  if (!storeState) {
    midiCtrlChange(MIDI.getInputChannel(), 104);
    digitalWrite (storePin, LOW);
    delay(40);
    digitalWrite (storePin, HIGH);
  }
}

//MIDI PROG NUMBER
int currentProgramNumber = 0;

#define  channelAddress 129  //0 to 127 used for MIDI program presets
#define  firstOnAddress 130  //0 to 127 used for MIDI program presets
#define  lastProgram 131  //load the last selected program on startup

//MIDI MUTE FUNCTIONS
void mute() {
    digitalWrite(mutePin, HIGH);
    delay(40);
    digitalWrite(mutePin, LOW);
  }

void recallPinStates(int programNumber);

void recallPinStates(int programNumber)  {
  int pinStates = 0;
  int loopCount = 0;
  int outputPin = 12; 
  pinStates = EEPROM.read(programNumber);
    for (int loopCount = 7; loopCount >=0 ; loopCount--)  {
      //read individual bits and set matching output pin
      digitalWrite(outputPin, bitRead(pinStates, loopCount));
      outputPin++;
    }
}

//MIDI PC FUNCTIONS
void handleProgramChange(byte channel, byte number) { 
   mute();
   delay(5);
   currentProgramNumber = number;
   recallPinStates(currentProgramNumber);
   //store as the last selected PC
   EEPROM.write(lastProgram,currentProgramNumber);
}


//MIDI CC FUNCTIONS
void midiCtrlChange(byte c, byte v) {

  int address = currentProgramNumber;
  int number = 0;
  byte pinStateArray[8] = {0,0,0,0,0,0,0,0};
  int pinState = 0;
  int i = 0;

  mute();
  MIDI.sendControlChange(c, v, 0xB0 | midi_channel);
  if (c == 102 && v == 0) {
    for (int resetAddress = 0; resetAddress < 128; resetAddress++) {
      EEPROM.write(resetAddress, 0x01); 
    }
  }
  if (c == 104 && v == 0) {
    for (int outputPin = 1; outputPin < 10; outputPin++)  {
  //read output pin and store in bit array
  pinStateArray[i] = digitalRead(outputPin);
  //add individual pin state into total byte 
  pinState += (pinStateArray[i] << (7-i));
  i++;  
  }
    EEPROM.write(address, pinState);
  }
  if (v > 64) {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(WHITE, BLACK);
    display.print(" SIRIUS AMPLIFICATION ");
    display.setCursor(0, 55);
    display.setTextSize(1);
    display.setTextColor(WHITE, BLACK);
    display.print("CC:");
    display.setCursor(20, 55);
    display.print(c);
    display.setCursor(40, 55);
    display.print("V:");
    display.display();
    display.print(v);
    display.print("    ");
    display.display();
  }
  if (v <= 64) {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(WHITE, BLACK);
    display.print(" SIRIUS AMPLIFICATION ");
    display.setCursor(0, 55);
    display.setTextSize(1);
    display.setTextColor(WHITE, BLACK);
    display.print("CC:");
    display.setCursor(20, 55);
    display.print(c);
    display.setCursor(40, 55);
    display.print("V:");
    display.display();
    display.print(v);
    display.print("    ");
    display.display();
  }
  if (c == 59 && v > 64) {
    //FX Loop Bypass CC MSG
    digitalWrite(RELAY6, HIGH);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("FX:");
    if (v == 127) display.print("ON");
    display.print("    ");
    display.display();
  }
  if (c == 59 && v <= 64) {
    //FX Loop Bypass CC MSG
    digitalWrite(RELAY6, LOW);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("FX:");
    if (v == 0) display.print("OFF");
    display.print("    ");
    display.display();
  }
  if (c == 85 && v > 64) {
    //Clean Channel On CC MSG
    digitalWrite(RELAY1, HIGH);
    digitalWrite(RELAY2, LOW);
    digitalWrite(RELAY3, LOW);
    digitalWrite(RELAY4, LOW);
    digitalWrite(RELAY5, LOW);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("CLEAN");
    display.print("   ");
    display.display();
  }
  if (c == 85 && v <= 64) {
    //Clean Channel Off CC MSG
  }
  if (c == 86 && v > 64) {
    //Gain Channel On CC MSG
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY2, LOW);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("GAIN 1");
    display.print("    ");
    display.display();
  }
  if (c == 86 && v <= 64) {
    //Gain Channel Off CC MSG
  }
  if (c == 87 && v > 64) {
    //Channel On CC MSG
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY2, HIGH);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("GAIN 2");
    display.print("    ");
    display.display();
  }
  if (c == 87 && v <= 64) {
    //Channel Off CC MSG
    digitalWrite(RELAY2, LOW);
  }
  if (c == 88 && v > 64) {
    //Channel On CC MSG
    digitalWrite(RELAY3, HIGH);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("BOOST:");
    if (v == 127) display.print("ON");
    display.print("    ");
    display.display();
  }
  if (c == 88 && v <= 64) {
    //Channel Off CC MSG
    digitalWrite(RELAY3, LOW);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("BOOST:");
    if (v == 0) display.print("OFF");
    display.print("    ");
    display.display();
  }
  if (c == 89 && v > 64) {
    //Toggle On CC MSG
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY5, HIGH);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("PLEXI:");
    if (v == 127) display.print("ON");
    display.print("   ");
    display.display();
  }
  if (c == 89 && v <= 64) {
    //Toggle Off CC MSG
    digitalWrite(RELAY1, HIGH);
    digitalWrite(RELAY2, LOW);
    digitalWrite(RELAY3, LOW);
    digitalWrite(RELAY4, LOW);
    digitalWrite(RELAY5, LOW);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("PLEXI:");
    if (v == 0) display.print("OFF");
    display.print("   ");
    display.display();
  }
  if (c == 90 && v > 64) {
    //Toggle On CC MSG
    digitalWrite(RELAY1, LOW);
    digitalWrite(RELAY4, HIGH);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("JTS:");
    if (v == 127) display.print("ON");
    display.print("    ");
    display.display();
  }
  if (c == 90 && v <= 64) {
    //Toggle Off CC MSG
    digitalWrite(RELAY4, LOW);
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.setTextColor(WHITE, BLACK);
    display.print("JTS:");
    if (v == 0) display.print("OFF");
    display.print("    ");
    display.display();
  }
  if (c == 91 && v > 64) {
    //Reverb Volume On CC MSG
    //Use digitalWrite(OUTPUT,HIGH); for direct functions
  }
  if (c == 91 && v <= 64) {
    //Reverb Volume Off CC MSG
    //Use digitalWrite(OUTPUT,LOW); for direct functions
  }
  if (c == 92 && v > 64) {
    //Tremelo Volume On CC MSG
    //Use digitalWrite(OUTPUT,HIGH); for direct functions
  }
  if (c == 92 && v <= 64) {
    //Tremelo Volume Off CC MSG
    //Use digitalWrite(OUTPUT,LOW); for direct functions
  }
}

void setup() {
  //INITIATE INPUT & OUTPUTS
  pinMode(sw1Pin, INPUT_PULLUP);
  pinMode(sw2Pin, INPUT_PULLUP);
  pinMode(sw3Pin, INPUT_PULLUP);
  pinMode(sw4Pin, INPUT_PULLUP);
  pinMode(sw5Pin, INPUT_PULLUP);
  pinMode(RELAY1, OUTPUT);
  pinMode(RELAY2, OUTPUT);
  pinMode(RELAY3, OUTPUT);
  pinMode(RELAY4, OUTPUT);
  pinMode(RELAY5, OUTPUT);
  pinMode(RELAY6, OUTPUT);
  pinMode(RELAY7, OUTPUT);
  pinMode(RELAY8, OUTPUT);
  pinMode(mutePin, OUTPUT);
  pinMode(storePin, OUTPUT);
  pinMode(storeControl, INPUT_PULLUP);

  // INITIATE CLEAN CHANNEL
  digitalWrite(RELAY1, HIGH);

  // INITIATE MIDI
  Serial.begin(9600);
  MIDI.setHandleError(handleError);
  MIDI.begin(MIDI_CHANNEL_OMNI);

  // INITIATE DISPLAY SPLASHSCREEN
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.drawBitmap(40, 5, NaN, 50, 50, WHITE);
  display.display();
  delay(3000);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE, BLACK);
  display.setCursor(0, 0);
  display.print(" SIRIUS AMPLIFICATION ");
  display.display();
  midiCtrlChange(85, 127);
}

void loop() {
  MIDI.read();
  //SWITCH 1 FUNCTIONS
  swState = digitalRead(sw1Pin);
  if (swState != swStateLast) 
  {
    if (swState == LOW) 
    {
      swPush++;
      if (swPush > 1) 
      {
        swPush = 0;
      }
      switch (swPush)
      {
        case 1:
          midiCtrlChange(86, 127);
          break;
        case 0:
          midiCtrlChange(87, 127);
          break;
      }
    }
    swStateLast = swState;
    delay(10);
    while (digitalRead(sw1Pin) == LOW) 
    {
      delay(5);
    }
  }
  //SWITCH 2 FUNCTIONS
  swState = digitalRead(sw2Pin);
  if (swState != swStateLast) 
  {
    if (swState == LOW)
    {
      swPush++;
      if (swPush > 1) 
      {
        swPush = 0;
      }
      switch (swPush)
      {
        case 1:
          midiCtrlChange(88, 127);
          break;
        case 0:
          midiCtrlChange(88, 0);
          break;
      }
    }
    swStateLast = swState;
    delay(10);
    while (digitalRead(sw2Pin) == LOW) 
    {
      delay(5);
    }
  }
  //SWITCH 3 FUNCTIONS
  swState = digitalRead(sw3Pin);
  if (swState != swStateLast) 
  {
    if (swState == LOW) 
    {
      swPush++;
      if (swPush > 1) 
      {
        swPush = 0;
      }
      switch (swPush)
      {
        case 1:
          midiCtrlChange(90, 127);
          break;
        case 0:
          midiCtrlChange(90, 0);
          break;
      }
    }
    swStateLast = swState;
    delay(10);
    while (digitalRead(sw3Pin) == LOW) 
    {
      delay(5);
    }
  }
  //SWITCH 4 FUNCTIONS
  swState = digitalRead(sw4Pin);
  if (swState != swStateLast) 
  {
    if (swState == LOW) 
    {
      swPush++;
      if (swPush > 1) 
      {
        swPush = 0;
      }
      switch (swPush)
      {
        case 1:
          midiCtrlChange(89, 127);
          break;
        case 0:
          midiCtrlChange(89, 0);
          break;
      }
    }
    swStateLast = swState;
    delay(10);
    while (digitalRead(sw4Pin) == LOW) 
    {
      delay(5);
    }
  }
  //SWITCH 5 FUNCTIONS
  swState = digitalRead(sw5Pin);
  if (swState != swStateLast) 
  {
    if (swState == LOW) 
    {
      swPush++;
      if (swPush > 1) 
      {
        swPush = 0;
      }
      switch (swPush)
      {
        case 1:
          midiCtrlChange(59, 127);
          break;
        case 0:
          midiCtrlChange(59, 0);
          break;
      }
    }
    swStateLast = swState;
    delay(10);
    while (digitalRead(sw5Pin) == LOW) 
    {
      delay(5);
    }
  }

  //STORE FUNCTIONS
  swState = digitalRead(storeControl);
  if (swState != swStateLast) 
  {
    if (swState == LOW) 
    {
      swPush++;
      if (swPush > 1) 
      {
        swPush = 0;
      }
      switch (swPush)
      {
        case 1:
          midiCtrlChange(104, 0);
          break;
      }
    }
    swStateLast = swState;
    delay(10);
    while (digitalRead(storeControl) == LOW) 
    {
      delay(5);
    }
  }
}