/*
=====================================================================
ARDUINO SERIAL PIN CONTROL AND MONITORING FIRMWARE
=====================================================================
Version: 1.3.0
Author: [Your Name]
Board Support: UNO, NANO, MEGA2560, LEONARDO (auto-detected)

DESCRIPTION
---------------------------------------------------------------------
This firmware enables external control and monitoring of Arduino
digital pins through a serial interface. It is designed for
integration with Python or similar host software.

The firmware supports dynamic pin-mode configuration, runtime
output control, input monitoring, duration measurement, and pin-map
query. All commands and responses use ASCII text terminated by '\n'.

=====================================================================
ASCII COMMAND REFERENCE
=====================================================================

+----------------+--------------------------------+-------------------------------------+-------------------------------------------------------------+
| COMMAND        | EXAMPLE REQUEST                | EXAMPLE RESPONSE                    | DESCRIPTION                                                 |
+----------------+--------------------------------+-------------------------------------+-------------------------------------------------------------+
| GET_VERSION    | GET_VERSION                    | VERSION:1.3.0                       | Returns firmware version to confirm serial communication.   |
|                |                                |                                     |                                                             |
| SET_MODE       | SET_MODE:8:OUTPUT              | ACK:SET_MODE:8:OUTPUT               | Configures a pin as INPUT or OUTPUT dynamically.            |
|                | SET_MODE:2:INPUT               | ACK:SET_MODE:2:INPUT                |                                                             |
|                |                                |                                     |                                                             |
| GET_PINMAP     | GET_PINMAP                     | PINMAP:INPUT:2;OUTPUT:8,9,10,11     | Returns the current input and output pin assignments.       |
|                |                                |                                     |                                                             |
| SET_DEFAULT    | SET_DEFAULT:8:HIGH             | ACK:SET_DEFAULT:8:HIGH              | Sets an output pin to a default state until changed.        |
|                |                                |                                     |                                                             |
| SET_FOR        | SET_FOR:9:HIGH:500             | ACK:SET_FOR:9:HIGH:500              | Sets an output pin to a state for a duration (ms).          |
|                |                                |                                     | Automatically reverts afterwards.                           |
|                |                                |                                     |                                                             |
| WATCH          | WATCH:2                        | ACK:WATCH:2                         | Begins monitoring an input pin. Reports state changes as:   |
|                |                                | CHANGE:2:HIGH:1421                  |   - Pin number, new state, and duration since last change.  |
|                |                                |                                     |                                                             |
| GET_STATE      | GET_STATE:2                    | STATE:2:LOW                         | Returns current digital state of a specified pin.           |
|                |                                |                                     |                                                             |
| GET_DURATION   | GET_DURATION:2                 | DURATION:2:1431                     | Returns elapsed time since the pin’s last state change.     |
|                |                                |                                     |                                                             |
| WAIT_FOR       | WAIT_FOR:2:HIGH                | WAIT_RESULT:2:HIGH:1432             | Waits until a pin reaches target state; returns duration.   |
|                |                                |                                     |                                                             |
| ERROR HANDLING | UNKNOWN COMMAND                | ERROR:UNKNOWN_COMMAND               | Returned if a command is unrecognised or malformed.         |
+----------------+--------------------------------+-------------------------------------+-------------------------------------------------------------+

=====================================================================
OPERATIONAL NOTES
---------------------------------------------------------------------
- Baud rate: 115200
- Line termination: newline ('\n')
- States are HIGH or LOW
- Durations in milliseconds
- All commands and responses are ASCII

=====================================================================
*/

#include <Arduino.h>

// ------------------------------------------------------------------
// Board-specific pin range detection
// ------------------------------------------------------------------
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
  const int FIRST_PIN = 2;
  const int LAST_PIN = 13;
#elif defined(ARDUINO_AVR_MEGA2560)
  const int FIRST_PIN = 2;
  const int LAST_PIN = 53;
#elif defined(ARDUINO_AVR_LEONARDO)
  const int FIRST_PIN = 2;
  const int LAST_PIN = 13;
#else
  const int FIRST_PIN = 2;
  const int LAST_PIN = 13;
#endif

const int NUM_PINS = LAST_PIN - FIRST_PIN + 1;

// ------------------------------------------------------------------
// Dynamic role and state tracking
// ------------------------------------------------------------------
bool isInput[NUM_PINS];
bool isOutput[NUM_PINS];
bool watching[NUM_PINS];
unsigned long lastChangeTime[NUM_PINS];
int lastState[NUM_PINS];

// ------------------------------------------------------------------
// Setup
// ------------------------------------------------------------------
void setup() {
  Serial.begin(115200);

  // Default: all usable pins configured as OUTPUT and LOW
  for (int i = 0; i < NUM_PINS; i++) {
    int pin = FIRST_PIN + i;
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
    isInput[i] = false;
    isOutput[i] = true;
    watching[i] = false;
    lastState[i] = LOW;
    lastChangeTime[i] = millis();
  }

  Serial.println("READY");
}

// ------------------------------------------------------------------
// Main loop
// ------------------------------------------------------------------
void loop() {
  handleSerial();
  monitorWatchedPins();
}

// ------------------------------------------------------------------
// Serial command processing
// ------------------------------------------------------------------
void handleSerial() {
  static String inputString = "";
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\n') {
      inputString.trim();
      processCommand(inputString);
      inputString = "";
    } else {
      inputString += c;
    }
  }
}

// ------------------------------------------------------------------
// Command dispatcher
// ------------------------------------------------------------------
void processCommand(String cmd) {
  if (cmd == "GET_VERSION") {
    Serial.println("VERSION:1.3.0");
  } else if (cmd == "GET_PINMAP") {
    handleGetPinmap();
  } else if (cmd.startsWith("SET_MODE")) {
    handleSetMode(cmd);
  } else if (cmd.startsWith("SET_DEFAULT")) {
    handleSetDefault(cmd);
  } else if (cmd.startsWith("SET_FOR")) {
    handleSetFor(cmd);
  } else if (cmd.startsWith("WATCH")) {
    handleWatch(cmd);
  } else if (cmd.startsWith("GET_STATE")) {
    handleGetState(cmd);
  } else if (cmd.startsWith("GET_DURATION")) {
    handleGetDuration(cmd);
  } else if (cmd.startsWith("WAIT_FOR")) {
    handleWaitFor(cmd);
  } else {
    Serial.println("ERROR:UNKNOWN_COMMAND");
  }
}

// ------------------------------------------------------------------
// Command handlers
// ------------------------------------------------------------------

// --- SET_MODE:PIN:MODE ------------------------------------------------
void handleSetMode(String cmd) {
  int first = cmd.indexOf(':');
  int second = cmd.indexOf(':', first + 1);
  if (first == -1 || second == -1) return;

  int pin = cmd.substring(first + 1, second).toInt();
  String mode = cmd.substring(second + 1);

  if (pin < FIRST_PIN || pin > LAST_PIN) {
    Serial.println("ERROR:INVALID_PIN");
    return;
  }

  int index = pin - FIRST_PIN;
  if (mode == "INPUT") {
    pinMode(pin, INPUT);
    isInput[index] = true;
    isOutput[index] = false;
  } else if (mode == "OUTPUT") {
    pinMode(pin, OUTPUT);
    isInput[index] = false;
    isOutput[index] = true;
  } else {
    Serial.println("ERROR:INVALID_MODE");
    return;
  }

  Serial.print("ACK:SET_MODE:");
  Serial.print(pin);
  Serial.print(":");
  Serial.println(mode);
}

// --- GET_PINMAP -------------------------------------------------------
void handleGetPinmap() {
  String response = "PINMAP:INPUT:";
  bool firstInput = true;
  for (int i = 0; i < NUM_PINS; i++) {
    if (isInput[i]) {
      if (!firstInput) response += ",";
      response += String(FIRST_PIN + i);
      firstInput = false;
    }
  }
  response += ";OUTPUT:";
  bool firstOutput = true;
  for (int i = 0; i < NUM_PINS; i++) {
    if (isOutput[i]) {
      if (!firstOutput) response += ",";
      response += String(FIRST_PIN + i);
      firstOutput = false;
    }
  }
  Serial.println(response);
}

// --- SET_DEFAULT:PIN:STATE --------------------------------------------
void handleSetDefault(String cmd) {
  int first = cmd.indexOf(':');
  int second = cmd.indexOf(':', first + 1);
  if (first == -1 || second == -1) return;

  int pin = cmd.substring(first + 1, second).toInt();
  String stateStr = cmd.substring(second + 1);
  bool state = (stateStr == "HIGH");

  digitalWrite(pin, state ? HIGH : LOW);
  Serial.print("ACK:SET_DEFAULT:");
  Serial.print(pin);
  Serial.print(":");
  Serial.println(state ? "HIGH" : "LOW");
}

// --- SET_FOR:PIN:STATE:DURATION ---------------------------------------
void handleSetFor(String cmd) {
  int first = cmd.indexOf(':');
  int second = cmd.indexOf(':', first + 1);
  int third = cmd.indexOf(':', second + 1);
  if (first == -1 || second == -1 || third == -1) return;

  int pin = cmd.substring(first + 1, second).toInt();
  String stateStr = cmd.substring(second + 1, third);
  unsigned long duration = cmd.substring(third + 1).toInt();
  bool state = (stateStr == "HIGH");

  digitalWrite(pin, state ? HIGH : LOW);
  delay(duration);
  digitalWrite(pin, state ? LOW : HIGH);

  Serial.print("ACK:SET_FOR:");
  Serial.print(pin);
  Serial.print(":");
  Serial.print(state ? "HIGH" : "LOW");
  Serial.print(":");
  Serial.println(duration);
}

// --- WATCH:PIN ---------------------------------------------------------
void handleWatch(String cmd) {
  int first = cmd.indexOf(':');
  if (first == -1) return;

  int pin = cmd.substring(first + 1).toInt();
  int index = pin - FIRST_PIN;
  if (index < 0 || index >= NUM_PINS || !isInput[index]) {
    Serial.println("ERROR:INVALID_PIN");
    return;
  }
  watching[index] = true;
  Serial.print("ACK:WATCH:");
  Serial.println(pin);
}

// --- GET_STATE:PIN -----------------------------------------------------
void handleGetState(String cmd) {
  int first = cmd.indexOf(':');
  if (first == -1) return;
  int pin = cmd.substring(first + 1).toInt();
  int state = digitalRead(pin);
  Serial.print("STATE:");
  Serial.print(pin);
  Serial.print(":");
  Serial.println(state == HIGH ? "HIGH" : "LOW");
}

// --- GET_DURATION:PIN --------------------------------------------------
void handleGetDuration(String cmd) {
  int first = cmd.indexOf(':');
  if (first == -1) return;
  int pin = cmd.substring(first + 1).toInt();
  int index = pin - FIRST_PIN;
  if (index < 0 || index >= NUM_PINS) return;
  unsigned long duration = millis() - lastChangeTime[index];
  Serial.print("DURATION:");
  Serial.print(pin);
  Serial.print(":");
  Serial.println(duration);
}

// --- WAIT_FOR:PIN:STATE ------------------------------------------------
void handleWaitFor(String cmd) {
  int first = cmd.indexOf(':');
  int second = cmd.indexOf(':', first + 1);
  if (first == -1 || second == -1) return;
  int pin = cmd.substring(first + 1, second).toInt();
  String targetStateStr = cmd.substring(second + 1);
  bool targetState = (targetStateStr == "HIGH");
  unsigned long startTime = millis();
  while (digitalRead(pin) != targetState) {
    delay(1);
  }
  unsigned long duration = millis() - startTime;
  Serial.print("WAIT_RESULT:");
  Serial.print(pin);
  Serial.print(":");
  Serial.print(targetState ? "HIGH" : "LOW");
  Serial.print(":");
  Serial.println(duration);
}

// ------------------------------------------------------------------
// Watch monitoring
// ------------------------------------------------------------------
void monitorWatchedPins() {
  for (int i = 0; i < NUM_PINS; i++) {
    if (watching[i] && isInput[i]) {
      int pin = FIRST_PIN + i;
      int currentState = digitalRead(pin);
      if (currentState != lastState[i]) {
        unsigned long now = millis();
        unsigned long duration = now - lastChangeTime[i];
        lastChangeTime[i] = now;
        lastState[i] = currentState;
        Serial.print("CHANGE:");
        Serial.print(pin);
        Serial.print(":");
        Serial.print(currentState == HIGH ? "HIGH" : "LOW");
        Serial.print(":");
        Serial.println(duration);
      }
    }
  }
}
