#include <SPI.h>
#include <Ethernet.h>

// Replace with the twitch bot's username
#define TWITCH_BOT_USERNAME "<your bot username>"

// Replace with the twitch bot's OAuth key
#define OAUTH_KEY "oauth:<your oauth token here>"

// Twitch IRC server and port
#define TWITCH_IRC_SERVER "irc.chat.twitch.tv"
#define TWITCH_IRC_PORT 6667

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
// Ethernet client object
EthernetClient client;

#define RED_LED 6
#define BLUE_LED 5
#define GREEN_LED 9
int brightness = 255;

int greenValue = 0;
int redValue = 0;
int blueValue = 0;

int fadeSpeed = 10;
bool isLedOn = false;
boolean turnLedOn = false;
long previousCheck = 0L;
String channel = "<your channel here>";
String clientId = "<your client id here>";
#define TWITCH_CLIENT_ID "<your client id here>"
#define TWITCH_LOGIN "<your channel here>"

bool isStreamLive = false;
bool internetConnected = false;
int brightnessIndex, colorIndex, psocRed, psocGreen, psocBlue = -1;

unsigned long delayBetweenRequests = 60000; // Time between requests (1 minute)

void sendMessage(String message) {
  client.print("PRIVMSG #" + channel + " :" + message + "\r\n");
}

void turnOnOffLed(bool turnOn) {
  for (int i = 0; i < 256; i++) {
    analogWrite(GREEN_LED, brightness);
    analogWrite(RED_LED, brightness);
    analogWrite(BLUE_LED, brightness);
    if (turnOn)
      brightness++;
    else
      brightness--;
    delay(fadeSpeed);
  }
  isLedOn = turnOn;
}

void setLedColor(int red, int green, int blue) {
  //Update these values as well so we can use them for brightness settings
  greenValue = green;
  redValue = red;
  blueValue = blue;
  for (int i = 0; i < 256; i++) {
    analogWrite(GREEN_LED, green);
    analogWrite(RED_LED, red);
    analogWrite(BLUE_LED, blue);
    delay(fadeSpeed);
  }
  isLedOn = true;
}

void updateLEDStrip() {
  if (turnLedOn) {
    if (!isLedOn) {
      turnOnOffLed(true);
    }
    for (int i = 0; i < 256; i++) {
      analogWrite(GREEN_LED, greenValue);
      analogWrite(RED_LED, redValue);
      analogWrite(BLUE_LED, blueValue);
    }
  }
  else if (isLedOn)
    turnOnOffLed(false);

}

void changeBrightness(int brightness) {
  if (brightness >= 0 && brightness <= 255) {
    int newRedValue = redValue * brightness / 255;
    int newGreenValue = greenValue * brightness / 255;
    int newBlueValue = blueValue * brightness / 255;

    int totalColorValue = newRedValue + newGreenValue + newBlueValue;
    float redRatio = (float)newRedValue / (float)totalColorValue;
    float greenRatio = (float)newGreenValue / (float)totalColorValue;
    float blueRatio = (float)newBlueValue / (float)totalColorValue;

    newRedValue = redRatio * brightness;
    newGreenValue = greenRatio * brightness;
    newBlueValue = blueRatio * brightness;

    setLedColor(newRedValue, newGreenValue, newBlueValue);
  }
  else {
    // Handle error if the brightness value is not within the range of 0-255
    Serial.println("ERROR: Invalid brightness value. Brightness must be between 0 and 255.");
  }
}

//Function to change the color of the lights
void changeColor(String currentColor, int colorIndex) {
  Serial.println("New color: " + currentColor);
  if (currentColor == "red" || colorIndex == 1) {
    //Set the lights to red
    setLedColor(255, 0, 0);
  } else if (currentColor == "green" || colorIndex == 2) {
    //Set the lights to green
    setLedColor(0, 255, 0);
  } else if (currentColor == "blue" || colorIndex == 3) {
    //Set the lights to blue
    setLedColor(0, 0, 255);
  } else if (currentColor == "yellow" || colorIndex == 4) {
    //Set the lights to yellow
    setLedColor(255, 255, 0);
  } else if (currentColor == "purple" || colorIndex == 5) {
    //Set the lights to purple
    setLedColor(128, 0, 128);
  } else if (currentColor == "orange" || colorIndex == 6) {
    //Set the lights to orange
    setLedColor(255, 165, 0);
  } else if (currentColor == "white" || colorIndex == 7) {
    //Set the lights to white
    setLedColor(255, 255, 255);
  } else if (currentColor == "off") {
    //Turn the lights off
    turnOnOffLed(false);
  } else {
    Serial.println(currentColor + " is an invalid color");
  }
}

//Dont let twitch users do anything funky
String cleanString(String input) {
  input.trim(); // remove extra spaces from the beginning and end of the string
  input.replace(" ", ""); // remove all spaces in the string
  input.toLowerCase(); // set the string to all lowercase

  // remove any illegal characters
  for (int i = 0; i < input.length(); i++) {
    if (!isAlphaNumeric(input[i])) {
      input.remove(i, 1);
      i--;
    }
  }
  return input;
}

void updateLedFromTwitchChatMessage(String incomingMessage) {
  //Check for the "!color" command
  if (incomingMessage.startsWith("!color")) {
    //Extract the color value from the incoming message
    String color = cleanString(incomingMessage.substring(7));
    Serial.println("Color " + color);
    changeColor(color, -1);
  }
  //Check for the "brightness" command
  if (incomingMessage.startsWith("!brightness")) {
    //Extract the brightness value from the incoming message
    String cleanedString = cleanString(incomingMessage.substring(11));
    int newBrightness = cleanedString.toInt();
    //Check if the new brightness value is valid
    if ((newBrightness != 0 || cleanedString == "0") && newBrightness >= 0 && newBrightness <= 255) {

      //Change the brightness of the lights
      changeBrightness(newBrightness);
    } else {
      Serial.println(cleanedString + " is not a valid brightness");
    }
  }
}

void connectToTwitch() {
  // Connect to Twitch IRC server
  if (client.connect(TWITCH_IRC_SERVER, TWITCH_IRC_PORT)) {
    client.print("PASS " + String(OAUTH_KEY) + "\r\n");
    client.print("NICK " + String(TWITCH_BOT_USERNAME) + "\r\n");
    client.print("JOIN #"+channel"+\r\n");
  } else {
    // Connection failed
    Serial.println("ERROR: Connection to Twitch failed.");
    Serial.println("Error code: " + String(client.getWriteError()));
  }
}

void receiveTwitchMessages() {
  while (client.connected()) {
    if (client.available()) {
      String response = client.readStringUntil('\n');
      if (response == "PING :tmi.twitch.tv\r\n") {
        client.print("PONG :tmi.twitch.tv\r\n");
      } else {
        Serial.println(response);
        int startIndex = response.indexOf(":", 1);
        int endIndex = response.indexOf("\r\n");
        if (endIndex < 0)
          endIndex = response.length();

        if (startIndex != -1 && endIndex != -1) {
          String message = response.substring(startIndex + 1, endIndex);
          Serial.println("The message is " + message);
          if (message.startsWith("!")) {
            Serial.println("Sending message: " + message);
            updateLedFromTwitchChatMessage(message);
          } else if (message.indexOf("Press !mystery to start off a mystery anytime! (I'm a bot)") >= 0 && !isStreamLive) {
            isStreamLive = true;
            turnOnOffLed(isStreamLive);
          } else if (message.indexOf("The stream is now offline.") >= 0 && isStreamLive) {
            isStreamLive = false;
            turnOnOffLed(isStreamLive);
          }
        }
      }
    }
    //The loop is inaccessible, so just call for psoc messages here instead
    receivePsocMessages();    //Via serial
  }
  client.stop();
}

void receivePsocMessages() {
  // Check for incoming serial data from the PSOC

  if (Serial1.available() > 0) {
    int redHexValue, greenHexValue, blueHexValue, b, c;
    String incomingData = Serial1.readStringUntil('\n');

    Serial.print("incomingData ");
    Serial.println(incomingData);
    if (incomingData.startsWith("RGB:")) {

      // Extract the red, green, and blue values from the incoming data
      sscanf(incomingData.c_str(), "RGB:%02X%02X%02X", &redHexValue, &greenHexValue, &blueHexValue);

      Serial.print("RGB " + String(redHexValue) + " " + String(greenHexValue) + " " + String(blueHexValue));

      if (psocRed != redValue || psocGreen != greenValue || psocBlue != blueValue) {
        turnLedOn = true;
        psocRed, redValue = redHexValue;
        psocGreen, greenValue = greenHexValue;
        psocBlue, blueValue = blueHexValue;
        updateLEDStrip();
      }
    } else if (incomingData.startsWith("Brightness Index: ")) {

      // Extract the red, green, and blue values from the incoming data
      sscanf(incomingData.c_str(), "Brightness Index: %d", &b);
      Serial.print("Brightness Index: " + String(b));

      //Change the brightness of the lights
      if (b != brightnessIndex) {
        changeBrightness(51 * b); //goes from 0-255
        brightnessIndex = b;
      }
    } else if (incomingData.startsWith("Color Index: ")) {

      // Extract the red, green, and blue values from the incoming data
      sscanf(incomingData.c_str(), "Color Index: %d", &c);
      Serial.print("Color Index: " + String(c));

      if (c != colorIndex) {
        changeColor("", c);
        colorIndex = c;

      }
    }
  }
}
void setup() {
  //  pinMode(led, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);

  turnOnOffLed(true);
  delay(5000);
  turnOnOffLed(false);
  Serial.begin(115200);
  Serial1.begin(115200);
  setupConnection();
  Serial.println("Setup done");
}
void setupConnection() {
  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    } else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
  } else {
    Serial.println("Internet is connected");
    internetConnected = true;
  }
  connectToTwitch();
  sendMessage("Hi Twitch - You can change the light colors with !color <color> commands! (sent via a bot)");

}

void loop() {
  if (millis() - previousCheck >= 120000L) { // check every 2 minutes
    previousCheck = millis();
    if (!internetConnected) {
      Serial.println("Attempting to setup connection");
      setupConnection();
    }
  }
  receiveTwitchMessages();  //Via IRC on ethernet
  receivePsocMessages();    //Via serial
}
