#include <SPI.h>
#include <Ethernet.h>
#include <EEPROM.h>
#include <Adafruit_NeoPixel.h>
#include "Mudbus.h"

#define LED_PIN    5
#define NUM_LEDS   8
#define RL1 14
#define RL2 15

#define EEPROM_IP_ADDRESS 0
#define EEPROM_GATEWAY 4
#define EEPROM_SUBNET 8
#define EEPROM_CONFIG_SET 12
#define EEPROM_MAC_ADDRESS 13

IPAddress ip, gateway, subnet;
uint8_t mac[6];

EthernetServer server(80);
Mudbus Mb;
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

const uint8_t numInputs = 10;
const uint8_t inputPins[numInputs] = {6, 7, 8, 9, 4, 3, 19, 18, 17, 16};
uint8_t tugas[numInputs] = {1, 1, 1, 1, 1, 1, 2, 4, 5, 9};     
uint8_t antrian[numInputs] = {0};
uint8_t perintah = 0;

unsigned long previousMillis = 0;

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(0));  // Initialize random number generator
  
  loadIPSettings();
  Ethernet.begin(mac, ip, gateway, subnet);
  
  server.begin();
  Serial.print(F("Server is at "));
  Serial.println(Ethernet.localIP());
  
  pinMode(RL1, OUTPUT);
  pinMode(RL2, OUTPUT);
  
  strip.begin();
  strip.show();
  
  for (uint8_t i = 0; i < numInputs; i++) {
    pinMode(inputPins[i], INPUT_PULLUP);
  }
  
  Mb.Run();
  
  for (uint8_t i = 0; i < 10; i++) {
    Mb.R[i] = tugas[i];
  }
  
  ledStartupSequence();
  printSerialInstructions();
}

void loop() {
  Mb.Run();
  
  handleEthernetClient();
  
  digitalWrite(RL1, Mb.C[10]); 
  digitalWrite(RL2, Mb.C[11]);

  handleEthernetClient();
  handleSerialCommands();
  
  for (uint8_t i = 0; i < 10; i++) {
    Mb.C[i] = digitalRead(inputPins[i]);
    tugas[i] = Mb.R[i];
  }
  
  unsigned long currentMillis = millis();
  
  updateAntrian();
  
  perintah = getMaxTugas();
  
  handleLEDControl(perintah, currentMillis);
}

void sendWebPage(EthernetClient& client) {
  client.println(F("HTTP/1.1 200 OK"));
  client.println(F("Content-Type: text/html"));
  client.println();
  client.println(F("<html><head><title>Arduino Network Config</title></head><body>"));
  client.println(F("<h1>Arduino Network Configuration</h1>"));
  
  // IP settings form
  client.println(F("<h2>IP Settings</h2>"));
  client.println(F("<form method='post' action='/setip'>"));
  client.print(F("IP Address: <input type='text' name='ip' value='"));
  client.print(Ethernet.localIP());
  client.println(F("'><br>"));
  client.print(F("Gateway: <input type='text' name='gateway' value='"));
  client.print(Ethernet.gatewayIP());
  client.println(F("'><br>"));
  client.print(F("Subnet: <input type='text' name='subnet' value='"));
  client.print(Ethernet.subnetMask());
  client.println(F("'><br>"));
  client.println(F("<input type='submit' value='Update IP Settings'>"));
  client.println(F("</form>"));
  
  // MAC address form
  client.println(F("<h2>MAC Address</h2>"));
  client.println(F("<form method='post' action='/setmac'>"));
  client.print(F("MAC Address: <input type='text' name='mac' value='"));
  for (uint8_t i = 0; i < 6; i++) {
    client.print(mac[i], HEX);
    if (i < 5) client.print(':');
  }
  client.println(F("'><br>"));
  client.println(F("<input type='submit' value='Update MAC Address'>"));
  client.println(F("</form>"));
  
  // Generate random MAC button
  client.println(F("<form method='post' action='/generatemac'>"));
  client.println(F("<input type='submit' value='Generate Random MAC'>"));
  client.println(F("</form>"));
  
  client.println(F("</body></html>"));
}

void sendResponse(EthernetClient& client, const __FlashStringHelper* message) {
  client.println(F("HTTP/1.1 200 OK"));
  client.println(F("Content-Type: text/plain"));
  client.println();
  client.println(message);
}

void handleSerialCommands() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    command.trim();
    
    if (command.startsWith("setip ")) {
      handleSerialIPConfig(command);
    } else if (command.startsWith("setmac ")) {
      handleSerialMACConfig(command);
    } else if (command == "genmac") {
      handleSerialGenerateMAC();
    } else if (command == "settings") {
      printCurrentSettings();
    } else if (command == "restart") {
      Serial.println(F("Restarting device..."));
      delay(1000);
      softReset();
    } else {
      Serial.println(F("Invalid command. Type 'help' for instructions."));
    }
  }
}

void handleSerialIPConfig(const String& command) {
  String params = command.substring(6);
  int firstSpace = params.indexOf(' ');
  int secondSpace = params.indexOf(' ', firstSpace + 1);
  
  if (firstSpace == -1 || secondSpace == -1) {
    Serial.println(F("Invalid IP configuration format."));
    return;
  }
  
  String ipStr = params.substring(0, firstSpace);
  String gatewayStr = params.substring(firstSpace + 1, secondSpace);
  String subnetStr = params.substring(secondSpace + 1);
  
  IPAddress newIP, newGateway, newSubnet;
  
  if (newIP.fromString(ipStr) && newGateway.fromString(gatewayStr) && newSubnet.fromString(subnetStr)) {
    saveIPSettings(newIP, newGateway, newSubnet);
    Serial.println(F("IP settings updated. Device will restart in 5 seconds."));
    delay(5000);
    softReset();
  } else {
    Serial.println(F("Invalid IP settings."));
  }
}

void handleSerialMACConfig(const String& command) {
  String macStr = command.substring(7);
  if (parseMACAddress(macStr)) {
    saveMACSettings();
    Serial.println(F("MAC address updated. Device will restart in 5 seconds."));
    delay(5000);
    softReset();
  } else {
    Serial.println(F("Invalid MAC address."));
  }
}

void handleSerialGenerateMAC() {
  generateRandomMAC();
  saveMACSettings();
  Serial.println(F("New MAC address generated and saved. Device will restart in 5 seconds."));
  delay(5000);
  softReset();
}

void printSerialInstructions() {
  Serial.println(F("Available commands:"));
  Serial.println(F("1. Set IP: setip <ip> <gateway> <subnet>"));
  Serial.println(F("2. Set MAC: setmac <mac>"));
  Serial.println(F("3. Generate random MAC: genmac"));
  Serial.println(F("4. Print current settings: settings"));
  Serial.println(F("5. Restart device: restart"));
}

void printCurrentSettings() {
  Serial.println(F("Current settings:"));
  Serial.print(F("IP: ")); Serial.println(Ethernet.localIP());
  Serial.print(F("Gateway: ")); Serial.println(Ethernet.gatewayIP());
  Serial.print(F("Subnet: ")); Serial.println(Ethernet.subnetMask());
  Serial.print(F("MAC: "));
  for (uint8_t i = 0; i < 6; i++) {
    Serial.print(mac[i], HEX);
    if (i < 5) Serial.print(':');
  }
  Serial.println();
}

void loadIPSettings() {
  if (EEPROM.read(EEPROM_CONFIG_SET) == 1) {
    for (uint8_t i = 0; i < 4; i++) {
      ip[i] = EEPROM.read(EEPROM_IP_ADDRESS + i);
      gateway[i] = EEPROM.read(EEPROM_GATEWAY + i);
      subnet[i] = EEPROM.read(EEPROM_SUBNET + i);
    }
    for (uint8_t i = 0; i < 6; i++) {
      mac[i] = EEPROM.read(EEPROM_MAC_ADDRESS + i);
    }
    Serial.println(F("Loaded IP and MAC settings from EEPROM"));
  } else {
    ip = IPAddress(192, 168, 1, 177);
    gateway = IPAddress(192, 168, 1, 1);
    subnet = IPAddress(255, 255, 255, 0);
    generateRandomMAC();
    Serial.println(F("Using default IP settings and generated MAC"));
  }
  
  Serial.print(F("IP: ")); Serial.println(ip);
  Serial.print(F("Gateway: ")); Serial.println(gateway);
  Serial.print(F("Subnet: ")); Serial.println(subnet);
  Serial.print(F("MAC: "));
  for (uint8_t i = 0; i < 6; i++) {
    Serial.print(mac[i], HEX);
    if (i < 5) Serial.print(':');
  }
  Serial.println();
}

void generateRandomMAC() {
  mac[0] = 0xDE;  // Locally administered address
  for (int i = 1; i < 6; i++) {
    mac[i] = random(0xFF);
  }
}

void handleEthernetClient() {
  EthernetClient client = server.available();
  if (client) {
    boolean currentLineIsBlank = true;
    String httpRequest = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        httpRequest += c;
        if (c == '\n' && currentLineIsBlank) {
          if (httpRequest.indexOf("POST /setip") != -1) {
            handleIPConfiguration(client, httpRequest);
          } else if (httpRequest.indexOf("POST /setmac") != -1) {
            handleMACConfiguration(client, httpRequest);
          } else if (httpRequest.indexOf("POST /generatemac") != -1) {
            handleGenerateMAC(client);
          } else {
            sendWebPage(client);
          }
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
        } else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
  }
}

void handleIPConfiguration(EthernetClient& client, const String& httpRequest) {
  IPAddress newIP, newGateway, newSubnet;
  if (parseIPSettings(httpRequest, newIP, newGateway, newSubnet)) {
    saveIPSettings(newIP, newGateway, newSubnet);
    sendResponse(client, F("IP settings updated. Device will restart in 5 seconds."));
    delay(5000);
    softReset();
  } else {
    sendResponse(client, F("Invalid IP settings"));
  }
}

void saveIPSettings(IPAddress newIP, IPAddress newGateway, IPAddress newSubnet) {
  for (uint8_t i = 0; i < 4; i++) {
    EEPROM.write(EEPROM_IP_ADDRESS + i, newIP[i]);
    EEPROM.write(EEPROM_GATEWAY + i, newGateway[i]);
    EEPROM.write(EEPROM_SUBNET + i, newSubnet[i]);
  }
  EEPROM.write(EEPROM_CONFIG_SET, 1);
  Serial.println(F("IP settings saved to EEPROM"));
}

void handleMACConfiguration(EthernetClient& client, const String& httpRequest) {
  String macStr = extractValueFromRequest(httpRequest, "mac");
  if (parseMACAddress(macStr)) {
    saveMACSettings();
    sendResponse(client, F("MAC address updated. Device will restart in 5 seconds."));
    delay(5000);
    softReset();
  } else {
    sendResponse(client, F("Invalid MAC address"));
  }
}

void saveMACSettings() {
  for (uint8_t i = 0; i < 6; i++) {
    EEPROM.write(EEPROM_MAC_ADDRESS + i, mac[i]);
  }
  Serial.println(F("MAC address saved to EEPROM"));
}

void handleGenerateMAC(EthernetClient& client) {
  generateRandomMAC();
  saveMACSettings();
  sendResponse(client, F("New MAC address generated and saved. Device will restart in 5 seconds."));
  delay(5000);
  softReset();
}

bool parseIPSettings(const String& request, IPAddress& ip, IPAddress& gateway, IPAddress& subnet) {
  String ipStr = extractValueFromRequest(request, "ip");
  String gatewayStr = extractValueFromRequest(request, "gateway");
  String subnetStr = extractValueFromRequest(request, "subnet");
  
  return ip.fromString(ipStr) && gateway.fromString(gatewayStr) && subnet.fromString(subnetStr);
}

bool parseMACAddress(const String& macStr) {
  uint8_t temp[6];
  int values[6];
  int i;

  if (6 == sscanf(macStr.c_str(), "%x:%x:%x:%x:%x:%x", &values[0], &values[1], &values[2], &values[3], &values[4], &values[5])) {
    for (i = 0; i < 6; ++i) {
      temp[i] = (uint8_t) values[i];
    }
    memcpy(mac, temp, 6);
    return true;
  }
  return false;
}

String extractValueFromRequest(const String& request, const char* key) {
  int start = request.indexOf(key);
  if (start == -1) return "";
  start = request.indexOf('=', start) + 1;
  int end = request.indexOf('&', start);
  if (end == -1) end = request.indexOf(' ', start);
  if (end == -1) return "";
  return request.substring(start, end);
}

void softReset() {
  asm volatile ("  jmp 0");  
}

void updateAntrian() {
  for (uint8_t i = 0; i < numInputs; i++) {
    antrian[i] = digitalRead(inputPins[i]) == LOW ? tugas[i] : 0;
  }
}

uint8_t getMaxTugas() {
  uint8_t maxTugas = 0;
  for (uint8_t i = 0; i < numInputs; i++) {
    if (antrian[i] > maxTugas) {
      maxTugas = antrian[i];
    }
  }
  return maxTugas;
}

void handleLEDControl(uint8_t perintah, unsigned long currentMillis) {
  switch (perintah) {
    case 0: ledsOff(); break;
    case 1: blinkColor(strip.Color(0, 255, 0), 1000, currentMillis); break;
    case 2: blinkColor(strip.Color(255, 0, 0), 700, currentMillis); break;
    case 3: alternateBlink(strip.Color(0, 255, 0), 50, currentMillis); break;
    case 4: blinkColor(strip.Color(255, 255, 0), 1000, currentMillis); break;
    case 5: blinkColor(strip.Color(128, 0, 128), 700, currentMillis); break;
    case 6: blinkColor(strip.Color(255, 255, 255), 500, currentMillis); break;
    case 7: blinkColor(strip.Color(255, 20, 147), 300, currentMillis); break;
    case 8: blinkColor(strip.Color(255, 0, 0), 300, currentMillis); break;
    case 9: blinkColor(strip.Color(0, 0, 255), 200, currentMillis); break;
  }
}

void ledsOff() {
  strip.clear();
  strip.show();
}

void blinkColor(uint32_t color, int interval, unsigned long currentMillis) {
  static bool ledState = false;
  
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    ledState = !ledState;
    strip.fill(ledState ? color : 0, 0, NUM_LEDS);
    strip.show();
  }
}

void alternateBlink(uint32_t color, int interval, unsigned long currentMillis) {
  static uint8_t ledIndex = 0;
  
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    strip.clear();
    strip.setPixelColor(ledIndex, color);
    strip.show();
    ledIndex = (ledIndex + 1) % NUM_LEDS;
  }
}

void ledStartupSequence() {
  uint32_t colors[] = {strip.Color(255, 0, 0), strip.Color(255, 255, 0), strip.Color(0, 255, 0)};
  for (uint8_t i = 0; i < 3; i++) {
    strip.fill(colors[i], 0, NUM_LEDS);
    strip.show();
    delay(1000);
    strip.clear();
    strip.show();
    delay(900);
  }
}