#include "meter_driver.h"
#include "config.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>

static constexpr uint32_t METER_READ_TIMEOUT_MS = 2000;

void meter_init() {
  Serial2.begin(9600, SERIAL_7E1, PIN_METER_RX, -1);
}

static bool parse_obis_ascii_value(const char *line, const char *obis, float &out_value) {
  const char *p = strstr(line, obis);
  if (!p) {
    return false;
  }
  const char *lparen = strchr(p, '(');
  if (!lparen) {
    return false;
  }
  const char *cur = lparen + 1;
  char num_buf[24];
  size_t n = 0;
  while (*cur && *cur != ')' && *cur != '*') {
    char c = *cur++;
    if ((c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' || c == ',') {
      if (c == ',') {
        c = '.';
      }
      if (n + 1 < sizeof(num_buf)) {
        num_buf[n++] = c;
      }
    } else if (n == 0) {
      continue;
    } else {
      break;
    }
  }
  if (n == 0) {
    return false;
  }
  num_buf[n] = '\0';
  out_value = static_cast<float>(atof(num_buf));
  return true;
}

static bool parse_obis_ascii_unit_scale(const char *line, const char *obis, float &value) {
  const char *p = strstr(line, obis);
  if (!p) {
    return false;
  }
  const char *asterisk = strchr(p, '*');
  if (!asterisk) {
    return false;
  }
  const char *end = strchr(asterisk, ')');
  if (!end) {
    return false;
  }
  char unit_buf[8];
  size_t ulen = 0;
  for (const char *c = asterisk + 1; c < end && ulen + 1 < sizeof(unit_buf); ++c) {
    if (*c == ' ') {
      continue;
    }
    unit_buf[ulen++] = *c;
  }
  unit_buf[ulen] = '\0';
  if (ulen == 0) {
    return false;
  }
  if (strcmp(unit_buf, "Wh") == 0) {
    value *= 0.001f;
    return true;
  }
  return false;
}

static bool meter_read_ascii(MeterData &data) {
  const uint32_t start_ms = millis();
  bool in_telegram = false;
  bool got_any = false;

  bool energy_ok = false;
  bool total_p_ok = false;
  bool p1_ok = false;
  bool p2_ok = false;
  bool p3_ok = false;
  char line[128];
  size_t line_len = 0;

  while (millis() - start_ms < METER_READ_TIMEOUT_MS) {
    while (Serial2.available()) {
      char c = static_cast<char>(Serial2.read());
      if (!in_telegram) {
        if (c == '/') {
          in_telegram = true;
          line_len = 0;
          line[line_len++] = c;
        }
        continue;
      }

      if (c == '\r') {
        continue;
      }
      if (c == '\n') {
        line[line_len] = '\0';
        if (line[0] == '!') {
          return got_any;
        }

        float value = NAN;
        if (parse_obis_ascii_value(line, "1-0:1.8.0", value)) {
          parse_obis_ascii_unit_scale(line, "1-0:1.8.0", value);
          data.energy_total_kwh = value;
          energy_ok = true;
          got_any = true;
        }
        if (parse_obis_ascii_value(line, "1-0:16.7.0", value)) {
          data.total_power_w = value;
          total_p_ok = true;
          got_any = true;
        }
        if (parse_obis_ascii_value(line, "1-0:36.7.0", value)) {
          data.phase_power_w[0] = value;
          p1_ok = true;
          got_any = true;
        }
        if (parse_obis_ascii_value(line, "1-0:56.7.0", value)) {
          data.phase_power_w[1] = value;
          p2_ok = true;
          got_any = true;
        }
        if (parse_obis_ascii_value(line, "1-0:76.7.0", value)) {
          data.phase_power_w[2] = value;
          p3_ok = true;
          got_any = true;
        }

        line_len = 0;
        continue;
      }
      if (line_len + 1 < sizeof(line)) {
        line[line_len++] = c;
      }
    }
    delay(5);
  }

  data.valid = energy_ok || total_p_ok || p1_ok || p2_ok || p3_ok;
  return data.valid;
}

bool meter_read(MeterData &data) {
  data.energy_total_kwh = NAN;
  data.total_power_w = NAN;
  data.phase_power_w[0] = NAN;
  data.phase_power_w[1] = NAN;
  data.phase_power_w[2] = NAN;
  data.valid = false;

  return meter_read_ascii(data);
}
