// =====================================================
// BASICAIO ARDUINO MCP - ARCHIVO MADRE
// -----------------------------------------------------
// Este archivo contiene la logica MCP / JSON-RPC.
// El docente NO deberia modificar este archivo.
//
// Las herramientas se agregan en:
//   basicaio_tools.h
//
// =====================================================

// -----------------------------------------------------
// Configuracion general
// -----------------------------------------------------
#define BASICAIO_SERIAL_SPEED 9600
#define BASICAIO_MAX_TOOLS 20

// -----------------------------------------------------
// Variables internas
// -----------------------------------------------------
String basicaioCurrentArgumentsJson = "{}";

// -----------------------------------------------------
// Tipo de funcion para una herramienta BASICAIO
// Todas las herramientas docentes tienen esta forma:
//   String mi_tool()
// -----------------------------------------------------
typedef String (*BasicAIOToolFunction)();

struct BasicAIOTool {
  const char* name;
  const char* description;
  const char* inputSchema;
  BasicAIOToolFunction function;
};

BasicAIOTool basicaioTools[BASICAIO_MAX_TOOLS];
int basicaioToolsCount = 0;

// -----------------------------------------------------
// Registro interno de herramientas
// -----------------------------------------------------
void registerTool(
  const char* name,
  const char* description,
  const char* inputSchema,
  BasicAIOToolFunction function
) {
  if (basicaioToolsCount >= BASICAIO_MAX_TOOLS) {
    return;
  }

  basicaioTools[basicaioToolsCount].name = name;
  basicaioTools[basicaioToolsCount].description = description;
  basicaioTools[basicaioToolsCount].inputSchema = inputSchema;
  basicaioTools[basicaioToolsCount].function = function;
  basicaioToolsCount++;
}

class BasicAIOToolRegister {
public:
  BasicAIOToolRegister(
    const char* name,
    const char* description,
    const char* inputSchema,
    BasicAIOToolFunction function
  ) {
    registerTool(name, description, inputSchema, function);
  }
};

// Macro que usa el archivo docente.
// Debe colocarse debajo de cada funcion de herramienta.
#define REGISTER_TOOL(NAME, DESCRIPTION, INPUT_SCHEMA, FUNCTION) \
  BasicAIOToolRegister autoRegister_##FUNCTION(NAME, DESCRIPTION, INPUT_SCHEMA, FUNCTION);

// -----------------------------------------------------
// Utilidades de texto
// -----------------------------------------------------
bool basicaioIsSpace(char c) {
  return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

int skipSpaces(const String &text, int index) {
  while (index < text.length() && basicaioIsSpace(text[index])) {
    index++;
  }
  return index;
}

String unescapeJsonString(String text) {
  String result = "";

  for (int i = 0; i < text.length(); i++) {
    char c = text[i];

    if (c == '\\' && i + 1 < text.length()) {
      char next = text[i + 1];

      if (next == '"') result += '"';
      else if (next == '\\') result += '\\';
      else if (next == '/') result += '/';
      else if (next == 'n') result += '\n';
      else if (next == 'r') result += '\r';
      else if (next == 't') result += '\t';
      else result += next;

      i++;
    } else {
      result += c;
    }
  }

  return result;
}

// -----------------------------------------------------
// Lector simple de JSON
// -----------------------------------------------------
// Este lector NO intenta reemplazar un parser JSON completo.
// Esta pensado para mensajes simples generados por BASICAIO,
// por ejemplo:
// {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"digital_write","arguments":{"pin":13,"value":"HIGH"}}}
// -----------------------------------------------------

int findJsonKey(const String &json, const char* key) {
  String pattern = String("\"") + String(key) + String("\"");
  return json.indexOf(pattern);
}

String getJsonRawValue(const String &json, const char* key) {
  int keyIndex = findJsonKey(json, key);
  if (keyIndex < 0) return "";

  int colonIndex = json.indexOf(':', keyIndex);
  if (colonIndex < 0) return "";

  int start = skipSpaces(json, colonIndex + 1);
  if (start >= json.length()) return "";

  char first = json[start];

  if (first == '"') {
    bool escaped = false;

    for (int i = start + 1; i < json.length(); i++) {
      char c = json[i];

      if (escaped) {
        escaped = false;
      } else if (c == '\\') {
        escaped = true;
      } else if (c == '"') {
        return json.substring(start, i + 1);
      }
    }

    return "";
  }

  if (first == '{' || first == '[') {
    char openChar = first;
    char closeChar = first == '{' ? '}' : ']';
    int depth = 0;
    bool inString = false;
    bool escaped = false;

    for (int i = start; i < json.length(); i++) {
      char c = json[i];

      if (inString) {
        if (escaped) {
          escaped = false;
        } else if (c == '\\') {
          escaped = true;
        } else if (c == '"') {
          inString = false;
        }
      } else {
        if (c == '"') {
          inString = true;
        } else if (c == openChar) {
          depth++;
        } else if (c == closeChar) {
          depth--;

          if (depth == 0) {
            return json.substring(start, i + 1);
          }
        }
      }
    }

    return "";
  }

  int end = start;
  while (
    end < json.length() &&
    json[end] != ',' &&
    json[end] != '}' &&
    json[end] != ']'
  ) {
    end++;
  }

  String raw = json.substring(start, end);
  raw.trim();
  return raw;
}

String rawJsonToString(String raw, String defaultValue) {
  raw.trim();

  if (raw.length() == 0) {
    return defaultValue;
  }

  if (raw[0] == '"' && raw.length() >= 2 && raw[raw.length() - 1] == '"') {
    return unescapeJsonString(raw.substring(1, raw.length() - 1));
  }

  if (raw == "null") {
    return defaultValue;
  }

  return raw;
}

String getJsonStringValue(const String &json, const char* key, String defaultValue) {
  return rawJsonToString(getJsonRawValue(json, key), defaultValue);
}

String getJsonObjectValue(const String &json, const char* key) {
  String raw = getJsonRawValue(json, key);
  raw.trim();

  if (raw.length() > 0 && raw[0] == '{') {
    return raw;
  }

  return "{}";
}

// -----------------------------------------------------
// Helpers simples para que el docente lea argumentos
// -----------------------------------------------------
String getRawArgValue(const char* name) {
  return getJsonStringValue(basicaioCurrentArgumentsJson, name, "");
}

int getIntArg(const char* name, int defaultValue) {
  String value = getRawArgValue(name);
  value.trim();

  if (value.length() == 0) {
    return defaultValue;
  }

  return value.toInt();
}

long getLongArg(const char* name, long defaultValue) {
  String value = getRawArgValue(name);
  value.trim();

  if (value.length() == 0) {
    return defaultValue;
  }

  return value.toInt();
}

String getStringArg(const char* name, const char* defaultValue) {
  String value = getRawArgValue(name);

  if (value.length() == 0) {
    return String(defaultValue);
  }

  return value;
}

bool getBoolArg(const char* name, bool defaultValue) {
  String value = getRawArgValue(name);
  value.trim();

  if (value.length() == 0) return defaultValue;

  if (
    value == "true" ||
    value == "TRUE" ||
    value == "1" ||
    value == "HIGH" ||
    value == "high"
  ) {
    return true;
  }

  if (
    value == "false" ||
    value == "FALSE" ||
    value == "0" ||
    value == "LOW" ||
    value == "low"
  ) {
    return false;
  }

  return defaultValue;
}

// -----------------------------------------------------
// Escapado JSON para imprimir Strings de forma segura
// -----------------------------------------------------
void printJsonEscaped(const char* text) {
  if (text == nullptr) return;

  for (int i = 0; text[i] != '\0'; i++) {
    char c = text[i];

    if (c == '"') Serial.print(F("\\\""));
    else if (c == '\\') Serial.print(F("\\\\"));
    else if (c == '\n') Serial.print(F("\\n"));
    else if (c == '\r') Serial.print(F("\\r"));
    else if (c == '\t') Serial.print(F("\\t"));
    else Serial.print(c);
  }
}

void printJsonEscaped(String text) {
  printJsonEscaped(text.c_str());
}

void printJsonId(String rawId) {
  rawId.trim();

  if (rawId.length() == 0) {
    Serial.print(F("null"));
  } else {
    Serial.print(rawId);
  }
}

// -----------------------------------------------------
// Incluir herramientas docentes
// -----------------------------------------------------
#include "basicaio_tools.h"

// -----------------------------------------------------
// Metodos MCP soportados
// -----------------------------------------------------
// IMPORTANTE:
// Usamos constantes int en lugar de enum Method para evitar
// problemas con el preprocesador automatico de Arduino IDE,
// que a veces genera prototipos antes de declarar tipos propios.
const int M_INITIALIZE = 1;
const int M_TOOLS_LIST = 2;
const int M_TOOLS_CALL = 3;
const int M_UNKNOWN = 4;
const int M_MISSING_METHOD = 5;

int parseMethod(String method) {
  if (method.length() == 0) return M_MISSING_METHOD;
  if (method == "initialize") return M_INITIALIZE;
  if (method == "tools/list") return M_TOOLS_LIST;
  if (method == "tools/call") return M_TOOLS_CALL;
  return M_UNKNOWN;
}

// -----------------------------------------------------
// Buscar y ejecutar herramienta por nombre
// -----------------------------------------------------
bool callToolByName(const char* name, String argumentsJson, String &resultText) {
  if (name == nullptr || String(name).length() == 0) {
    resultText = "ERR Missing tool name";
    return false;
  }

  basicaioCurrentArgumentsJson = argumentsJson;

  for (int i = 0; i < basicaioToolsCount; i++) {
    if (strcmp(name, basicaioTools[i].name) == 0) {
      resultText = basicaioTools[i].function();
      basicaioCurrentArgumentsJson = "{}";
      return true;
    }
  }

  basicaioCurrentArgumentsJson = "{}";
  resultText = String("ERR Unknown tool: ") + String(name);
  return false;
}

// -----------------------------------------------------
// Respuestas JSON-RPC
// -----------------------------------------------------
void printJsonRpcTextResult(String rawId, String resultText) {
  Serial.print(F("{\"jsonrpc\":\"2.0\",\"id\":"));
  printJsonId(rawId);
  Serial.print(F(",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\""));
  printJsonEscaped(resultText);
  Serial.print(F("\"}]}}"));
  Serial.println();
}

void printJsonRpcError(String rawId, int code, const char* message) {
  Serial.print(F("{\"jsonrpc\":\"2.0\",\"id\":"));
  printJsonId(rawId);
  Serial.print(F(",\"error\":{\"code\":"));
  Serial.print(code);
  Serial.print(F(",\"message\":\""));
  printJsonEscaped(message);
  Serial.print(F("\"}}"));
  Serial.println();
}

// -----------------------------------------------------
// MCP: initialize
// -----------------------------------------------------
void handleInitialize(String rawId) {
  Serial.print(F("{\"jsonrpc\":\"2.0\",\"id\":"));
  printJsonId(rawId);
  Serial.print(F(",\"result\":{"
                 "\"protocolVersion\":\"2025-03-26\","
                 "\"capabilities\":{},"
                 "\"serverInfo\":{"
                   "\"name\":\"ArduinoMCP\","
                   "\"version\":\"1.0.0\""
                 "}"
               "}}"));
  Serial.println();
}

// -----------------------------------------------------
// MCP: tools/list
// La lista se genera automaticamente desde REGISTER_TOOL.
// -----------------------------------------------------
void handleToolsList(String rawId) {
  Serial.print(F("{\"jsonrpc\":\"2.0\",\"id\":"));
  printJsonId(rawId);
  Serial.print(F(",\"result\":{\"tools\":["));

  for (int i = 0; i < basicaioToolsCount; i++) {
    if (i > 0) Serial.print(F(","));

    Serial.print(F("{\"name\":\""));
    printJsonEscaped(basicaioTools[i].name);

    Serial.print(F("\",\"description\":\""));
    printJsonEscaped(basicaioTools[i].description);

    Serial.print(F("\",\"inputSchema\":"));
    Serial.print(basicaioTools[i].inputSchema);

    Serial.print(F("}"));
  }

  Serial.print(F("]}}"));
  Serial.println();
}

// -----------------------------------------------------
// MCP: tools/call
// -----------------------------------------------------
void handleToolsCall(String rawId, String paramsJson) {
  String name = getJsonStringValue(paramsJson, "name", "");
  String argumentsJson = getJsonObjectValue(paramsJson, "arguments");

  String resultText = "";
  callToolByName(name.c_str(), argumentsJson, resultText);
  printJsonRpcTextResult(rawId, resultText);
}

void handleUnknown(String rawId) {
  printJsonRpcError(rawId, -32601, "Method not found");
}

void handleMissingMethod(String rawId) {
  printJsonRpcError(rawId, -32600, "Missing 'method' field");
}

void handleParseError() {
  Serial.println(F("{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{\"code\":-32700,\"message\":\"Parse error\"}}"));
}

// -----------------------------------------------------
// Arduino setup / loop
// -----------------------------------------------------
void setup() {
  Serial.begin(BASICAIO_SERIAL_SPEED);

  // Configuracion inicial del hardware docente.
  // Esta funcion esta definida en basicaio_tools.h
  setupTeacherHardware();
}

void loop() {
  if (!Serial.available()) return;

  String line = Serial.readStringUntil('\n');
  line.trim();

  if (line.length() == 0) return;

  String rawId = getJsonRawValue(line, "id");
  String method = getJsonStringValue(line, "method", "");
  int parsedMethod = parseMethod(method);

  switch (parsedMethod) {
    case M_INITIALIZE:
      handleInitialize(rawId);
      break;

    case M_TOOLS_LIST:
      handleToolsList(rawId);
      break;

    case M_TOOLS_CALL:
      handleToolsCall(
        rawId,
        getJsonObjectValue(line, "params")
      );
      break;

    case M_MISSING_METHOD:
      handleMissingMethod(rawId);
      break;

    default:
      handleUnknown(rawId);
      break;
  }
}
