// SPDX-FileCopyrightText: 2024 Cesanta Software Limited
// SPDX-License-Identifier: GPL-2.0-only or commercial
// Generated by Mongoose Wizard, https://mongoose.ws/wizard/

#include "mongoose.h"
#include "mongoose_glue.h"

#if !defined(HTTP_URL) && !defined(HTTPS_URL)
#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
#define HTTP_URL "http://0.0.0.0:8080"
#define HTTPS_URL "https://0.0.0.0:8443"
#else
#define HTTP_URL "http://0.0.0.0:80"
#define HTTPS_URL "https://0.0.0.0:443"
#endif
#endif

#ifndef offsetof
#define offsetof(st, m) ((size_t) ((char *) &((st *) 0)->m - (char *) 0))
#endif

#define NO_CACHE_HEADERS "Cache-Control: no-cache\r\n"
#define JSON_HEADERS "Content-Type: application/json\r\n" NO_CACHE_HEADERS

// How to create a self signed Elliptic Curve certificate, see
// https://github.com/cesanta/mongoose/blob/master/test/certs/generate.sh
#define TLS_CERT                                                       \
  "-----BEGIN CERTIFICATE-----\n"                                      \
  "MIIBMTCB2aADAgECAgkAluqkgeuV/zUwCgYIKoZIzj0EAwIwEzERMA8GA1UEAwwI\n" \
  "TW9uZ29vc2UwHhcNMjQwNTA3MTQzNzM2WhcNMzQwNTA1MTQzNzM2WjARMQ8wDQYD\n" \
  "VQQDDAZzZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASo3oEiG+BuTt5y\n" \
  "ZRyfwNr0C+SP+4M0RG2pYkb2v+ivbpfi72NHkmXiF/kbHXtgmSrn/PeTqiA8M+mg\n" \
  "BhYjDX+zoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDRwAw\n" \
  "RAIgTXW9MITQSwzqbNTxUUdt9DcB+8pPUTbWZpiXcA26GMYCIBiYw+DSFMLHmkHF\n" \
  "+5U3NXW3gVCLN9ntD5DAx8LTG8sB\n"                                     \
  "-----END CERTIFICATE-----\n"

#define TLS_KEY                                                        \
  "-----BEGIN EC PRIVATE KEY-----\n"                                   \
  "MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n" \
  "AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n" \
  "4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"                             \
  "-----END EC PRIVATE KEY-----\n"

#define CONN_OTA 'O'
#define CONN_FILE_UPLOAD 'F'
#define CONN_ACTION 'A'
#define CONN_HANDLED 'Z'

typedef void (*data_func_t)(void *);
typedef bool (*array_get_func_t)(void *, size_t);
typedef void (*array_set_func_t)(void *, size_t);
typedef bool (*action_check_func_t)(void);
typedef void (*action_start_func_t)(struct mg_str);
typedef void *(*ota_open_func_t)(char *, size_t);
typedef bool (*ota_close_func_t)(void *);
typedef bool (*ota_write_func_t)(void *, void *, size_t);
typedef void (*custom_reply_func_t)(struct mg_connection *,
                                    struct mg_http_message *);
typedef size_t (*file_read_func_t)(char *, size_t, void *, size_t);
typedef bool (*file_write_func_t)(char *, size_t, void *, size_t);

struct mg_mgr g_mgr;  // Mongoose event manager

#if WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
// Every time device state changes, this counter increments.
// Used by the heartbeat endpoint, to signal the UI when to refresh
static unsigned long s_device_change_version = 0;

struct attribute {
  const char *name;
  const char *type;
  const char *format;
  size_t offset;
  size_t size;
  bool readonly;
};

struct apihandler {
  const char *name;
  const char *type;
  bool readonly;
  int read_level;
  int write_level;
  unsigned long version;
};

struct apihandler_custom {
  struct apihandler common;
  void (*reply)(struct mg_connection *, struct mg_http_message *);
};

struct apihandler_file {
  struct apihandler common;
  file_read_func_t reader;
  file_write_func_t writer;
};

struct apihandler_ota {
  struct apihandler common;
  ota_open_func_t opener;
  ota_close_func_t closer;
  ota_write_func_t writer;
};

struct apihandler_action {
  struct apihandler common;
  action_check_func_t checker;  // Checker function for actions
  action_start_func_t starter;  // Starter function for actions
};

struct apihandler_data {
  struct apihandler common;
  const struct attribute *attributes;  // Points to the strucure descriptor
  size_t data_size;                    // Size of C structure
  data_func_t getter;                  // Getter/check/begin function
  data_func_t setter;                  // Setter/start/end function
};

struct apihandler_array {
  struct apihandler common;
  const struct attribute *attributes;  // Points to the strucure descriptor
  size_t data_size;                    // Size of C structure
  array_get_func_t getter;             // Getter function
  array_set_func_t setter;             // Setter function
};

struct upload_state {
  char marker;           // Tells that we're a file upload connection
  size_t expected;       // POST data length, bytes
  size_t received;       // Already received bytes
  struct apihandler *h;  // API handler
  void *ctx;             // OTA context
};

struct file_state {
  char *path;
  size_t ofs;
  size_t end;
  struct apihandler_file *h;
};

struct custom_api_handler {
  struct custom_api_handler *next;
  struct mg_str url_pattern;
  mg_event_handler_t handler;
  int read_level;
  int write_level;
};
static struct custom_api_handler *s_custom_handlers;

struct attribute s_ADC_Value_attributes[] = {
  {"adc_value", "int", NULL, offsetof(struct ADC_Value, adc_value), 0, false},
  {NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_PWM_attributes[] = {
  {"value", "int", NULL, offsetof(struct PWM, value), 0, false},
  {NULL, NULL, NULL, 0, 0, false}
};
struct attribute s_leds_attributes[] = {
  {"led3", "bool", NULL, offsetof(struct leds, led3), 0, false},
  {"led1", "bool", NULL, offsetof(struct leds, led1), 0, false},
  {"led2", "bool", NULL, offsetof(struct leds, led2), 0, false},
  {NULL, NULL, NULL, 0, 0, false}
};

struct apihandler_data s_apihandler_ADC_Value = {{"ADC_Value", "data", false, 0, 0, 0UL}, s_ADC_Value_attributes, sizeof(struct ADC_Value), (void (*)(void *)) glue_get_ADC_Value, (void (*)(void *)) glue_set_ADC_Value};
struct apihandler_data s_apihandler_PWM = {{"PWM", "data", false, 0, 0, 0UL}, s_PWM_attributes, sizeof(struct PWM), (void (*)(void *)) glue_get_PWM, (void (*)(void *)) glue_set_PWM};
struct apihandler_data s_apihandler_leds = {{"leds", "data", false, 0, 0, 0UL}, s_leds_attributes, sizeof(struct leds), (void (*)(void *)) glue_get_leds, (void (*)(void *)) glue_set_leds};

static struct apihandler *s_apihandlers[] = {
  (struct apihandler *) &s_apihandler_ADC_Value,
  (struct apihandler *) &s_apihandler_PWM,
  (struct apihandler *) &s_apihandler_leds
};

static struct apihandler *get_api_handler(struct mg_str name) {
  size_t num_handlers = sizeof(s_apihandlers) / sizeof(s_apihandlers[0]);
  size_t i;
  if (name.len == 0) return NULL;
  if (num_handlers == 0) return NULL;
  for (i = 0; i < num_handlers; i++) {
    struct apihandler *h = s_apihandlers[i];
    size_t n = strlen(h->name);
    if (n > name.len) continue;
    if (strncmp(name.buf, h->name, n) != 0) continue;
    if (name.len > n && name.buf[n] != '/') continue;
    return h;
  }
  return NULL;
}

static struct apihandler *find_handler(struct mg_http_message *hm) {
  if (hm->uri.len < 6 || strncmp(hm->uri.buf, "/api/", 5) != 0) return NULL;
  return get_api_handler(mg_str_n(hm->uri.buf + 5, hm->uri.len - 5));
}

void mg_json_get_str2(struct mg_str json, const char *path, char *buf,
                      size_t len) {
  struct mg_str s = mg_json_get_tok(json, path);
  if (s.len > 1 && s.buf[0] == '"') {
    mg_json_unescape(mg_str_n(s.buf + 1, s.len - 2), buf, len);
  }
}

void mongoose_set_http_handlers(const char *name, ...) {
  struct apihandler *h = get_api_handler(mg_str(name));
  va_list ap;
  va_start(ap, name);
  if (h == NULL) {
    MG_ERROR(("No API with name [%s]", name));
  } else if (strcmp(h->type, "data") == 0) {
    ((struct apihandler_data *) h)->getter = va_arg(ap, data_func_t);
    ((struct apihandler_data *) h)->setter = va_arg(ap, data_func_t);
  } else if (strcmp(h->type, "array") == 0) {
    ((struct apihandler_array *) h)->getter = va_arg(ap, array_get_func_t);
    ((struct apihandler_array *) h)->setter = va_arg(ap, array_set_func_t);
  } else if (strcmp(h->type, "action") == 0) {
    ((struct apihandler_action *) h)->checker = va_arg(ap, action_check_func_t);
    ((struct apihandler_action *) h)->starter = va_arg(ap, action_start_func_t);
  } else if (strcmp(h->type, "file") == 0) {
    ((struct apihandler_file *) h)->reader = va_arg(ap, file_read_func_t);
    ((struct apihandler_file *) h)->writer = va_arg(ap, file_write_func_t);
  } else if (strcmp(h->type, "ota") == 0 || strcmp(h->type, "upload") == 0) {
    ((struct apihandler_ota *) h)->opener = va_arg(ap, ota_open_func_t);
    ((struct apihandler_ota *) h)->closer = va_arg(ap, ota_close_func_t);
    ((struct apihandler_ota *) h)->writer = va_arg(ap, ota_write_func_t);
  } else if (strcmp(h->type, "custom") == 0) {
    ((struct apihandler_custom *) h)->reply = va_arg(ap, custom_reply_func_t);
  } else {
    MG_ERROR(("Setting [%s] failed: not implemented", name));
  }
  va_end(ap);
}

#if WIZARD_ENABLE_HTTP_UI_LOGIN

struct user {
  struct user *next;
  char name[32];   // User name
  char token[21];  // Login token
  int level;       // Access level
};

static int (*s_auth)(const char *, const char *) = glue_authenticate;
static struct user *s_users;  // List of authenticated users

void mongoose_set_auth_handler(int (*fn)(const char *, const char *)) {
  s_auth = fn;
}

// Parse HTTP requests, return authenticated user or NULL
static struct user *authenticate(struct mg_http_message *hm) {
  char user[100], pass[100];
  struct user *u, *result = NULL;
  mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));

  if (user[0] != '\0' && pass[0] != '\0') {
    // Both user and password is set, auth by user/password via glue API
    int level = s_auth(user, pass);
    MG_DEBUG(("user %s, level: %d", user, level));
    if (level > 0) {  // Proceed only if the firmware authenticated us
      // uint64_t uid = hash(3, mg_str(user), mg_str(":"), mg_str(pass));
      for (u = s_users; u != NULL && result == NULL; u = u->next) {
        if (strcmp(user, u->name) == 0) result = u;
      }
      // Not yet authenticated, add to the list
      if (result == NULL) {
        result = (struct user *) mg_calloc(1, sizeof(*result));
        mg_snprintf(result->name, sizeof(result->name), "%s", user);
        mg_random_str(result->token, sizeof(result->token) - 1);
        result->level = level, result->next = s_users, s_users = result;
      }
    }
  } else if (user[0] == '\0' && pass[0] != '\0') {
    for (u = s_users; u != NULL && result == NULL; u = u->next) {
      if (strcmp(u->token, pass) == 0) result = u;
    }
  }
  MG_VERBOSE(("[%s/%s] -> %s", user, pass, result ? "OK" : "FAIL"));
  return result;
}

static void handle_login(struct mg_connection *c, struct user *u) {
  char cookie[256];
  mg_snprintf(cookie, sizeof(cookie),
              "Set-Cookie: access_token=%s; Path=/; "
              "%sHttpOnly; SameSite=Lax; Max-Age=%d\r\n",
              u->token, c->is_tls ? "Secure; " : "", 3600 * 24);
  mg_http_reply(c, 200, cookie, "{%m:%m,%m:%d}\n",  //
                MG_ESC("user"), MG_ESC(u->name),    //
                MG_ESC("level"), u->level);
}

static void handle_logout(struct mg_connection *c) {
  char cookie[256];
  mg_snprintf(cookie, sizeof(cookie),
              "Set-Cookie: access_token=; Path=/; "
              "Expires=Thu, 01 Jan 1970 00:00:00 UTC; "
              "%sHttpOnly; Max-Age=0; \r\n",
              c->is_tls ? "Secure; " : "");
  mg_http_reply(c, 401, cookie, "Unauthorized\n");
}
#endif  // WIZARD_ENABLE_HTTP_UI_LOGIN

struct action_state {
  char marker;       // Tells that we're an action connection
  bool (*fn)(void);  // Action status function
};

static void get_file_name_from_uri(struct mg_str uri, char *buf, size_t len) {
  struct mg_str parts[3];
  memset(parts, 0, sizeof(parts));           // Init match parts
  mg_match(uri, mg_str("/api/*/*"), parts);  // Fetch file name
  mg_url_decode(parts[1].buf, parts[1].len, buf, len, 0);
}

static void do_upload(struct mg_connection *c, int ev) {
  struct upload_state *us = (struct upload_state *) c->data;
  struct apihandler_file *fh = (struct apihandler_file *) us->h;
  struct apihandler_ota *oh = (struct apihandler_ota *) us->h;
  bool is_ota = (strcmp(us->h->type, "ota") == 0);
  if (ev == MG_EV_READ && us->expected > 0 && c->recv.len > 0) {
    size_t alignment = 512;  // Maximum flash write granularity (iMXRT, Pico)
    size_t aligned = (us->received + c->recv.len < us->expected)
                         ? aligned = MG_ROUND_DOWN(c->recv.len, alignment)
                         : c->recv.len;  // Last write can be unaligned
    bool ok = true;  // aligned > 0 ? writer(us, c->recv.buf, aligned) : true;
    if (aligned > 0 && is_ota) {
      ok = oh->writer(us->ctx, c->recv.buf, aligned);
    }
    if (aligned > 0 && !is_ota) {
      ok = fh->writer((char *) us->ctx, us->received, c->recv.buf, aligned);
    }
    us->received += aligned;
    // MG_DEBUG(("%lu chunk: %lu/%lu, %lu/%lu, ok: %d", c->id, aligned,
    //           c->recv.len, us->received, us->expected, ok));
    mg_iobuf_del(&c->recv, 0, aligned);  // Delete received data
    if (ok == false) {
      mg_http_reply(c, 400, "", "Upload error\n");
      if (is_ota) oh->closer(us->ctx);
      if (!is_ota) mg_free(us->ctx);
      memset(us, 0, sizeof(*us));
      c->is_draining = 1;  // Close connection when response it sent
    } else if (us->received >= us->expected) {
      // Uploaded everything. Send response back
      MG_INFO(("%lu done, %lu bytes", c->id, us->received));
      mg_http_reply(c, 200, NULL, "%lu ok\n", us->received);
      if (is_ota) oh->closer(us->ctx);
      if (!is_ota) mg_free(us->ctx);
      memset(us, 0, sizeof(*us));
      c->is_draining = 1;  // Close connection when response it sent
    }
  }

  if (ev == MG_EV_CLOSE) {
    if (is_ota) oh->closer(us->ctx);
    if (!is_ota) mg_free(us->ctx);
  }
}

static void start_uploads(struct mg_connection *c, struct mg_http_message *hm) {
  struct upload_state *us = (struct upload_state *) c->data;

  if (sizeof(*us) > sizeof(c->data)) {
    mg_error(c,
             "FAILURE: sizeof(c->data) == %lu, need %lu."
             " Set #define MG_DATA_SIZE XXX",
             sizeof(c->data), sizeof(*us));
    return;
  }

  // Catch upload requests early, without buffering whole body
  // When we receive MG_EV_HTTP_HDRS event, that means we've received all
  // HTTP headers but not necessarily full HTTP body
  if (mg_strcmp(hm->method, mg_str("POST")) == 0) {
    struct apihandler *h = find_handler(hm);
    char path[MG_PATH_MAX];
    get_file_name_from_uri(hm->uri, path, sizeof(path));
    if (h != NULL && strcmp(h->type, "ota") == 0) {
      struct apihandler_ota *oh = (struct apihandler_ota *) h;
      us->ctx = oh->opener(path, hm->body.len);
      if (us->ctx == NULL) {
        mg_http_reply(c, 400, JSON_HEADERS, "OTA start error\n");
        c->is_draining = 1;
      } else {
        us->h = h;
        us->marker = CONN_OTA;
        us->expected = hm->body.len;
        mg_iobuf_del(&c->recv, 0, hm->head.len);
        c->pfn = NULL;
        mg_call(c, MG_EV_READ, &c->recv.len);
      }
    } else if (h != NULL && strcmp(h->type, "file") == 0) {
      us->ctx = mg_strdup(mg_str(path)).buf;  // Store file name
      us->h = h;
      us->expected = hm->body.len;
      us->marker = CONN_FILE_UPLOAD;
      c->pfn = NULL;
      mg_iobuf_del(&c->recv, 0, hm->head.len);
      mg_call(c, MG_EV_READ, &c->recv.len);
    }
  }
}

static void handle_action(struct mg_connection *c, struct mg_http_message *hm,
                          bool (*check_fn)(void),
                          void (*start_fn)(struct mg_str)) {
  if (hm->body.len > 0) {
    start_fn(hm->body);
    if (check_fn()) {
      struct action_state *as = (struct action_state *) c->data;
      as->marker = CONN_ACTION;
      as->fn = check_fn;
    } else {
      mg_http_reply(c, 200, JSON_HEADERS, "false");
    }
  } else {
    mg_http_reply(c, 200, JSON_HEADERS, "%s", check_fn() ? "true" : "false");
  }
}

size_t print_struct(void (*out)(char, void *), void *ptr, va_list *ap) {
  const struct attribute *a = va_arg(*ap, struct attribute *);
  char *data = va_arg(*ap, char *);
  size_t i, len = 0;
  for (i = 0; a[i].name != NULL; i++) {
    char *buf = data + a[i].offset;
    len += mg_xprintf(out, ptr, "%s%m:", i == 0 ? "" : ",", MG_ESC(a[i].name));
    if (strcmp(a[i].type, "int") == 0) {
      len += mg_xprintf(out, ptr, "%d", *(int *) buf);
    } else if (strcmp(a[i].type, "double") == 0) {
      const char *fmt = a[i].format;
      if (fmt == NULL) fmt = "%g";
      len += mg_xprintf(out, ptr, fmt, *(double *) buf);
    } else if (strcmp(a[i].type, "bool") == 0) {
      len += mg_xprintf(out, ptr, "%s", *(bool *) buf ? "true" : "false");
    } else if (strcmp(a[i].type, "string") == 0) {
      // We don't use MG_ESC cause the buffer may not be 0-terminated
      len += mg_xprintf(out, ptr, "%m", mg_print_esc, a[i].size, buf);
    } else {
      len += mg_xprintf(out, ptr, "null");
    }
  }
  return len;
}

static void populate_struct_from_json(struct mg_str json, char *tmp,
                                      const struct attribute *attrs) {
  size_t i;
  for (i = 0; attrs[i].name != NULL; i++) {
    const struct attribute *a = &attrs[i];
    char jpath[100];
    mg_snprintf(jpath, sizeof(jpath), "$.%s", a->name);
    if (strcmp(a->type, "int") == 0) {
      double d;
      if (mg_json_get_num(json, jpath, &d)) {
        int v = (int) d;
        memcpy(tmp + a->offset, &v, sizeof(v));
      }
    } else if (strcmp(a->type, "bool") == 0) {
      mg_json_get_bool(json, jpath, (bool *) (tmp + a->offset));
    } else if (strcmp(a->type, "double") == 0) {
      mg_json_get_num(json, jpath, (double *) (tmp + a->offset));
    } else if (strcmp(a->type, "string") == 0) {
      mg_json_get_str2(json, jpath, tmp + a->offset, a->size);
    }
  }
}

static void handle_object(struct mg_connection *c, struct mg_http_message *hm,
                          struct apihandler_data *h) {
  void *data = mg_calloc(1, h->data_size);
  h->getter(data);
  if (hm->body.len > 0 && h->data_size > 0) {
    char *tmp = mg_calloc(1, h->data_size);
    memcpy(tmp, data, h->data_size);
    populate_struct_from_json(hm->body, tmp, h->attributes);
    // If structure changes, increment version
    if (memcmp(data, tmp, h->data_size) != 0) s_device_change_version++;
    if (h->setter != NULL) h->setter(tmp);  // Can be NULL if readonly
    mg_free(tmp);
    h->getter(data);  // Re-sync again after setting
  }
  mg_http_reply(c, 200, JSON_HEADERS, "{%M}\n", print_struct, h->attributes,
                data, 0);
  mg_free(data);
}

static size_t print_array(void (*out)(char, void *), void *ptr, va_list *ap) {
  struct apihandler_array *ha = va_arg(*ap, struct apihandler_array *);
  struct mg_http_message *hm = va_arg(*ap, struct mg_http_message *);
  struct mg_str parts[4] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
  size_t i = 0, len = 0, start = 0, stop = ~(size_t) 0;

  if (mg_match(hm->uri, mg_str("/api/*/*/*"), parts) && parts[2].len > 0) {
    mg_str_to_num(parts[1], 10, &start, sizeof(start));
    mg_str_to_num(parts[2], 10, &stop, sizeof(stop));
  } else if (mg_match(hm->uri, mg_str("/api/*/*"), parts) && parts[1].len > 0) {
    mg_str_to_num(parts[1], 10, &start, sizeof(start));
    stop = start;
  }
  if (start != stop) len += mg_xprintf(out, ptr, "[");
  void *data = mg_calloc(1, ha->data_size);
  for (i = start; i <= stop; i++) {
    if (ha->getter(data, i) == false) break;
    if (i > start) len += mg_xprintf(out, ptr, ",");
    len += mg_xprintf(out, ptr, "{%M}", print_struct, ha->attributes, data, i);
  }
  if (len == 0) len += mg_xprintf(out, ptr, "null");
  if (start != stop) len += mg_xprintf(out, ptr, "]");
  mg_free(data);
  return len;
}

static void handle_array(struct mg_connection *c, struct mg_http_message *hm,
                         struct apihandler_array *h) {
  if (hm->body.len > 0 && h->data_size > 0) {
    char *tmp = mg_calloc(2, h->data_size);  // Allocate struct and backup
    // The URI is /api/NAME/ITEM_INDEX. Get the array item index from the URI
    size_t index = 0;
    struct mg_str parts[3] = {{0, 0}, {0, 0}, {0, 0}};
    mg_match(hm->uri, mg_str("/api/*/#"), parts);
    mg_str_to_num(parts[1], 10, &index, sizeof(index));
    // Fetch current item, then call a setter to update it
    if (h->getter(tmp, index)) {
      memcpy(tmp + h->data_size, tmp, h->data_size);  // Make a backup
      populate_struct_from_json(hm->body, tmp, h->attributes);
      if (memcmp(tmp, tmp + h->data_size, h->data_size) != 0) {
        s_device_change_version++;  // Structure changed, signal to the UI
      }
      if (h->setter != NULL) h->setter(tmp, index);
    }
    mg_free(tmp);
  }
  mg_http_reply(c, 200, JSON_HEADERS, "%M\n", print_array, h, hm);
  MG_INFO(("Array response size: %lu", c->send.len));
}

size_t print_timeseries(void (*out)(char, void *), void *ptr, va_list *ap) {
  uint32_t *timestamps = va_arg(*ap, uint32_t *);
  double *values = va_arg(*ap, double *);
  size_t count = va_arg(*ap, size_t);
  size_t i, len = 0;
  for (i = 0; i < count; i++) {
    const char *comma = i == 0 ? "" : ",";
    len += mg_xprintf(out, ptr, "%s[%lu,%g]", comma, timestamps[i], values[i]);
  }
  return len;
}

static void file_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
  struct file_state *s = (struct file_state *) c->data;
  // struct apihandler_file *h = *(struct apihandler_file **) c->data;
  // if (ev != MG_EV_POLL) MG_INFO(("%d %lu %s", ev, c->send.len, s->path));
  if (c->send.len < MG_IO_SIZE && s->path != NULL &&
      (ev == MG_EV_POLL || ev == MG_EV_WRITE)) {
    char buf[512];
    size_t len = 0, size = 0;
    if (s->ofs < s->end) {
      size = s->end - s->ofs;
      if (size > sizeof(buf)) size = sizeof(buf);
      // MG_INFO(("Trying to read %lu bytes ...", size));
      len = s->h->reader(s->path, s->ofs, buf, size);
    }
    // MG_INFO(("Read %lu buytes..", len));
    s->ofs += len;
    if (len == 0) {
      mg_free(s->path);
      memset(s, 0, sizeof(*s));
      c->is_draining = 1;
    } else {
      mg_send(c, buf, len);
    }
  } else if (ev == MG_EV_CLOSE) {
    mg_free(s->path);
    memset(s, 0, sizeof(*s));
  }
  (void) ev_data;
}

static void handle_file(struct mg_connection *c, struct mg_http_message *hm,
                        struct apihandler_file *h) {
  struct file_state *s = (struct file_state *) c->data;
  char path[MG_PATH_MAX];
  struct mg_str parts[4] = {{0, 0}, {0, 0}, {0, 0}};
  s->ofs = 0, s->end = ~(size_t) 0;

  if (mg_match(hm->query, mg_str("*/*"), parts) && parts[1].len > 0) {
    mg_str_to_num(parts[0], 0, &s->ofs, sizeof(s->ofs));
    mg_str_to_num(parts[1], 0, &s->end, sizeof(s->end));
  } else if (hm->query.len > 0) {
    mg_str_to_num(hm->query, 0, &s->ofs, sizeof(s->ofs));
  }
  get_file_name_from_uri(hm->uri, path, sizeof(path));
  s->h = h, s->path = mg_strdup(mg_str(path)).buf;
  // MG_DEBUG(("File: %s, ofs: %lu, end: %lu", s->path, s->ofs, s->end));
  mg_printf(c, "HTTP/1.1 200 OK\r\n\r\n");
  c->fn = file_ev_handler;
}

static void handle_api_call(struct mg_connection *c, struct mg_http_message *hm,
                            struct apihandler *h) {
  if (strcmp(h->type, "object") == 0 || strcmp(h->type, "data") == 0) {
    handle_object(c, hm, (struct apihandler_data *) h);
  } else if (strcmp(h->type, "array") == 0) {
    handle_array(c, hm, (struct apihandler_array *) h);
  } else if (strcmp(h->type, "action") == 0) {
    struct apihandler_action *ha = (struct apihandler_action *) h;
    handle_action(c, hm, ha->checker, ha->starter);
  } else if (strcmp(h->type, "file") == 0) {
    handle_file(c, hm, (struct apihandler_file *) h);
  } else if (strcmp(h->type, "custom") == 0) {
    ((struct apihandler_custom *) h)->reply(c, hm);
  } else {
    mg_http_reply(c, 500, JSON_HEADERS, "API type %s unknown\n", h->type);
  }
}

void glue_update_state(void) {
  s_device_change_version++;
}

void mongoose_add_custom_handler(const char *url_pattern,
                                 mg_event_handler_t handler, int read_level,
                                 int write_level) {
  struct custom_api_handler *ch =
      (struct custom_api_handler *) mg_calloc(1, sizeof(*ch));
  ch->url_pattern = mg_strdup(mg_str(url_pattern));
  ch->handler = handler;
  ch->read_level = read_level;
  ch->write_level = write_level;
  ch->next = s_custom_handlers;
  s_custom_handlers = ch;
}

struct custom_api_handler *find_custom_handler(struct mg_http_message *hm) {
  struct custom_api_handler *ch;
  for (ch = s_custom_handlers; ch != NULL; ch = ch->next) {
    if (mg_match(hm->uri, ch->url_pattern, NULL)) return ch;
  }
  return NULL;
}

// Mongoose event handler function, gets called by the mg_mgr_poll()
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_HTTP_HDRS && c->data[0] == 0) {
#if WIZARD_ENABLE_HTTP_UI_LOGIN
    // Send "Not Authorised" for unauthorised API endpoint accesses
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    struct custom_api_handler *ch = find_custom_handler(hm);
    if (ch != NULL || mg_match(hm->uri, mg_str("/api/#"), NULL) ||
        mg_match(hm->uri, mg_str("/websocket"), NULL)) {
      struct apihandler *h = find_handler(hm);
      struct user *u = authenticate(hm);
      if ((u == NULL ||
           (h != NULL && (u->level < h->read_level ||
                          (hm->body.len > 0 && u->level < h->write_level))) ||
           (ch != NULL &&
            (u->level < ch->read_level ||
             (hm->body.len > 0 && u->level < ch->write_level))))) {
        mg_http_reply(c, 403, JSON_HEADERS, "Not Authorised\n");
        c->data[0] = CONN_HANDLED;  // Mark this connection as handled
      }
    }
#endif
  }

  // We're checking c->is_websocket cause WS connection use c->data
  if (c->is_websocket == 0 && ev == MG_EV_HTTP_HDRS && c->data[0] == 0) {
    start_uploads(c, ev_data);
  }

  if (ev == MG_EV_POLL && c->is_websocket == 0 && c->data[0] == CONN_ACTION) {
    // Check if action in progress is complete
    struct action_state *as = (struct action_state *) c->data;
    if (as->fn() == false) {
      mg_http_reply(c, 200, JSON_HEADERS, "true");
      memset(as, 0, sizeof(*as));
    }
  } else if ((ev == MG_EV_READ || ev == MG_EV_CLOSE) && c->is_websocket == 0 &&
             (c->data[0] == CONN_OTA || c->data[0] == CONN_FILE_UPLOAD)) {
    do_upload(c, ev);
  } else if (ev == MG_EV_HTTP_MSG && c->is_websocket == 0 && c->data[0] == 0) {
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    struct custom_api_handler *ch = find_custom_handler(hm);
#if WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
    struct apihandler *h = find_handler(hm);
#if WIZARD_ENABLE_HTTP_UI_LOGIN
    struct user *u = authenticate(hm);
    if (mg_match(hm->uri, mg_str("/api/login"), NULL)) {
      handle_login(c, u);
    } else if (mg_match(hm->uri, mg_str("/api/logout"), NULL)) {
      handle_logout(c);
    } else
#endif
        if (mg_match(hm->uri, mg_str("/api/ok"), NULL)) {
      mg_http_reply(c, 200, JSON_HEADERS, "true\n");
    } else if (mg_match(hm->uri, mg_str("/websocket"), NULL)) {
      mg_ws_upgrade(c, hm, NULL);
    } else if (mg_match(hm->uri, mg_str("/api/heartbeat"), NULL)) {
      mg_http_reply(c, 200, JSON_HEADERS, "{%m:%lu}\n", MG_ESC("version"),
                    s_device_change_version);
    } else if (ch != NULL) {
      c->fn = ch->handler;
      ch->handler(c, ev, ev_data);
    } else if (h != NULL) {
      handle_api_call(c, hm, h);
    } else if (c->data[0] == 0)
#endif  // WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS
    {
#if WIZARD_ENABLE_HTTP_UI
      struct mg_http_serve_opts opts;
      memset(&opts, 0, sizeof(opts));
      opts.root_dir = "/web_root/";
      opts.fs = &mg_fs_packed;
      opts.extra_headers = NO_CACHE_HEADERS;
      mg_http_serve_dir(c, hm, &opts);
#else
      mg_http_reply(c, 200, "", ":)\n");
#endif  // WIZARD_ENABLE_HTTP_UI
    }
  } else if (ev == MG_EV_WS_MSG || ev == MG_EV_WS_CTL) {
    // Ignore received data
  } else if (ev == MG_EV_ACCEPT) {
    if (c->fn_data != NULL) {  // TLS listener
      struct mg_tls_opts opts;
      memset(&opts, 0, sizeof(opts));
      opts.cert = mg_str(TLS_CERT);
      opts.key = mg_str(TLS_KEY);
      mg_tls_init(c, &opts);
    }
  }

  if (ev == MG_EV_HTTP_MSG) {
    // Show this request
    int len = 0, spaces = 0;
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    struct mg_http_message tmp;
    memset(&tmp, 0, sizeof(tmp));
    len = mg_http_parse((char *) c->send.buf, c->send.len, &tmp);
    if (len < 0 || (size_t) len > c->send.len) len = c->send.len;
    while ((size_t) (len + spaces) < c->send.len &&
           (size_t) spaces < c->send.len &&
           (c->send.buf[c->send.len - spaces - 1] == '\r' ||
            c->send.buf[c->send.len - spaces - 1] == '\n'))
      spaces++;
    MG_DEBUG(("%lu %.*s %.*s %.*s: %lu %.*s -> %lu %.*s", c->id, hm->method.len,
              hm->method.buf, hm->uri.len, hm->uri.buf,
              c->send.len > 15 ? 3 : 0, &c->send.buf[9], hm->body.len,
              hm->body.len, hm->body.buf, c->send.len - len,
              c->send.len - len - spaces, c->send.buf + len));
    if (c->data[0] == CONN_HANDLED) {
      c->data[0] = 0;
      c->is_resp = 0;
    }
  }
}

#if WIZARD_ENABLE_WEBSOCKET
struct ws_handler {
  unsigned timeout_ms;
  struct apihandler *h;
};
// We keep WS timers inside c->data, that's why the list of WS timers
// should fit there.
#define MG_DATA_BUF_SIZE sizeof(((struct mg_connection *) 0)->data)
#define WS_MAX (MG_DATA_BUF_SIZE / sizeof(uint64_t))

static struct ws_handler s_ws_handlers[WS_MAX];
// static size_t s_ws_handlers_count;

void mongoose_add_ws_reporter(unsigned ms, const char *name) {
  struct apihandler *h = get_api_handler(mg_str(name));
  size_t i = 0;
  while (i < WS_MAX && s_ws_handlers[i].h != NULL) i++;
  if (i >= WS_MAX) {
    MG_ERROR(("WS handlers limit exceeded, max %lu", WS_MAX));
  } else if (ms == 0) {
    MG_ERROR(("Invalid timeout for %s", name));
  } else if (h == NULL) {
    MG_ERROR(("No handler for %s", name));
  } else if (strcmp(h->type, "data") != 0 && strcmp(h->type, "array") != 0) {
    MG_ERROR(("Handler %s should be a array/data handler", name));
  } else {
    s_ws_handlers[i].timeout_ms = ms;
    s_ws_handlers[i].h = h;
  }
};

static void send_websocket_data(void) {
  struct mg_connection *c;
  uint64_t now = mg_millis();

  for (c = g_mgr.conns; c != NULL; c = c->next) {
    uint64_t *timers = (uint64_t *) &c->data[0];
    size_t i;

    if (c->is_websocket == 0) continue;  // Not a websocket connection? Skip
    if (c->send.len > 2048) continue;    // Too much data already? Skip

    for (i = 0; i < WS_MAX; i++) {
      if (s_ws_handlers[i].timeout_ms == 0) break;
      if (s_ws_handlers[i].h == NULL) break;
      if (mg_timer_expired(&timers[i], s_ws_handlers[i].timeout_ms, now)) {
        struct apihandler *ah = s_ws_handlers[i].h;
        if (strcmp(ah->type, "data") == 0) {
          struct apihandler_data *h = (struct apihandler_data *) ah;
          void *data = mg_calloc(1, h->data_size);
          h->getter(data);
          mg_ws_printf(c, WEBSOCKET_OP_TEXT, "{%m:{%M}}",
                       MG_ESC(h->common.name), print_struct, h->attributes,
                       data, 0);
          mg_free(data);
        } else if (strcmp(ah->type, "array") == 0) {
          struct apihandler_array *h = (struct apihandler_array *) ah;
          struct mg_http_message fake = {};
          mg_ws_printf(c, WEBSOCKET_OP_TEXT, "{%m:%M}\n",
                       MG_ESC(h->common.name), print_array, h, &fake);
        }
      }
    }
  }
}
#else
void mongoose_add_ws_handler(unsigned ms, void (*fn)(struct mg_connection *)) {
  (void) ms, (void) fn;
  MG_ERROR(("Websocket support is not enabled!"));
}
#endif  // WIZARD_ENABLE_WEBSOCKET

#endif  // WIZARD_ENABLE_HTTP || WIZARD_ENABLE_HTTPS

#if WIZARD_ENABLE_SNTP
static char s_sntp_server[128] = WIZARD_SNTP_URL;
static void (*s_sntp_handler)(uint64_t) = glue_sntp_on_time;
static uint64_t s_sntp_timer = 0;
bool s_sntp_response_received = false;
static void sntp_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_SNTP_TIME) {
    uint64_t t = *(uint64_t *) ev_data;
    s_sntp_handler(t);
    s_sntp_response_received = true;
    s_sntp_timer += (WIZARD_SNTP_INTERVAL_SECONDS) * 1000;
  }
  (void) c;
}

static void sntp_timer(void *param) {
  // uint64_t t1 = mg_now(), t2 = mg_millis();
  uint64_t timeout = (WIZARD_SNTP_INTERVAL_SECONDS) * 1000;
  if (s_sntp_response_received == false) timeout = 1000;
  // This function is called every second. Once we received a response,
  // trigger SNTP sync less frequently, as set by the define
  if (mg_timer_expired(&s_sntp_timer, timeout, mg_millis())) {
    mg_sntp_connect(param, s_sntp_server, sntp_ev_handler, NULL);
  }
}

void mongoose_set_sntp_handler(void (*fn)(uint64_t epoch_ms)) {
  s_sntp_handler = fn;
}
void mongoose_set_sntp_server(const char *url) {
  mg_snprintf(s_sntp_server, sizeof(s_sntp_server), "%s", url);
}
#endif  // WIZARD_ENABLE_SNTP

#if WIZARD_DNS_TYPE == 1
static char s_dns_server[128];
#endif

#if MG_ENABLE_TCPIP && WIZARD_ENABLE_WIFI
static void wifi_event_handler(struct mg_tcpip_if *ifp, int ev, void *ev_data);
#endif

#if MG_ENABLE_TCPIP
static void mif_fn(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
  if (ev == MG_TCPIP_EV_ST_CHG) {
    MG_VERBOSE(("State change: %u", *(uint8_t *) ev_data));
    if (*(uint8_t *) ev_data == MG_TCPIP_STATE_READY) {
      // TODO(): Fire a READY event to (re)start connections
    } else if (*(uint8_t *) ev_data == MG_TCPIP_STATE_DOWN) {
      // TODO(): Fire a DOWN event on link down ?
    }
  }
#if WIZARD_DNS_TYPE == 1
  else if (ev == MG_TCPIP_EV_DHCP_DNS) {
    mg_snprintf(s_dns_server, sizeof(s_dns_server), "udp://%M:53", mg_print_ip4,
                (uint32_t *) ev_data);
    ifp->mgr->dns4.url = s_dns_server;
    MG_DEBUG(("Set DNS to %s", ifp->mgr->dns4.url));
  }
#endif
#if WIZARD_ENABLE_SNTP && WIZARD_SNTP_TYPE == 1
  else if (ev == MG_TCPIP_EV_DHCP_SNTP) {
    mg_snprintf(s_sntp_server, sizeof(s_sntp_server), "udp://%M:123",
                mg_print_ip4, (uint32_t *) ev_data);
    MG_DEBUG(("Set SNTP to %s", s_sntp_server));
  }
#endif
#if MG_ENABLE_TCPIP && WIZARD_ENABLE_WIFI
  wifi_event_handler(ifp, ev, ev_data);
#endif
  (void) ifp;
}
#endif

#if WIZARD_ENABLE_MQTT
static struct mongoose_mqtt_handlers s_mqtt_handlers = {
    glue_mqtt_connect, glue_mqtt_on_connect, glue_mqtt_on_message,
    glue_mqtt_on_cmd};

struct mg_connection *g_mqtt_conn;  // MQTT client connection

static void mqtt_event_handler(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_MQTT_OPEN) {
    s_mqtt_handlers.on_connect_fn(c, *(int *) ev_data);
  } else if (ev == MG_EV_MQTT_CMD) {
    s_mqtt_handlers.on_cmd_fn(c, ev_data);
  } else if (ev == MG_EV_MQTT_MSG) {
    struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
    s_mqtt_handlers.on_message_fn(c, mm->topic, mm->data);
  } else if (ev == MG_EV_CLOSE) {
    g_mqtt_conn = NULL;
  }
}

static void mqtt_timer(void *arg) {
  if (g_mqtt_conn == NULL) {
    g_mqtt_conn = s_mqtt_handlers.connect_fn(mqtt_event_handler);
  } else {
    mg_mqtt_ping(g_mqtt_conn);
  }
  (void) arg;
}

void mongoose_set_mqtt_handlers(struct mongoose_mqtt_handlers *h) {
  if (h->connect_fn) s_mqtt_handlers.connect_fn = h->connect_fn;
  if (h->on_message_fn) s_mqtt_handlers.on_message_fn = h->on_message_fn;
  if (h->on_connect_fn) s_mqtt_handlers.on_connect_fn = h->on_connect_fn;
  if (h->on_cmd_fn) s_mqtt_handlers.on_cmd_fn = h->on_cmd_fn;
}
#endif  // WIZARD_ENABLE_MQTT

#if WIZARD_ENABLE_MODBUS
static struct mongoose_modbus_handlers s_modbus_handlers = {
    glue_modbus_read_reg, glue_modbus_write_reg};

static void handle_modbus_pdu(struct mg_connection *c, uint8_t *buf,
                              size_t len) {
  MG_DEBUG(("Received PDU %p len %lu, hexdump:", buf, len));
  if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(buf, len);
  // size_t hdr_size = 8, max_data_size = sizeof(response) - hdr_size;
  if (len < 12) {
    MG_ERROR(("PDU too small"));
  } else {
    uint8_t func = buf[7];  // Function
    bool success = false;
    size_t response_len = 0;
    uint8_t response[260];
    memcpy(response, buf, 8);
    // uint16_t tid = mg_ntohs(*(uint16_t *) &buf[0]);  // Transaction ID
    // uint16_t pid = mg_ntohs(*(uint16_t *) &buf[0]);  // Protocol ID
    // uint16_t len = mg_ntohs(*(uint16_t *) &buf[4]);  // PDU length
    // uint8_t uid = buf[6];                            // Unit identifier
    if (func == 6) {  // write single holding register
      uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
      uint16_t value = mg_ntohs(*(uint16_t *) &buf[10]);
      success = s_modbus_handlers.write_reg_fn(start, value);
      *(uint16_t *) &response[8] = mg_htons(start);
      *(uint16_t *) &response[10] = mg_htons(value);
      response_len = 12;
      MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
    } else if (func == 16) {  // Write multiple
      uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
      uint16_t num = mg_ntohs(*(uint16_t *) &buf[10]);
      uint16_t i, *data = (uint16_t *) &buf[13];
      if ((size_t) (num * 2 + 10) < sizeof(response)) {
        for (i = 0; i < num; i++) {
          success = s_modbus_handlers.write_reg_fn((uint16_t) (start + i),
                                                   mg_htons(data[i]));
          if (success == false) break;
        }
        *(uint16_t *) &response[8] = mg_htons(start);
        *(uint16_t *) &response[10] = mg_htons(num);
        response_len = 12;
        MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
      }
    } else if (func == 3 || func == 4) {  // Read multiple
      uint16_t start = mg_ntohs(*(uint16_t *) &buf[8]);
      uint16_t num = mg_ntohs(*(uint16_t *) &buf[10]);
      if ((size_t) (num * 2 + 9) < sizeof(response)) {
        uint16_t i, val, *data = (uint16_t *) &response[9];
        for (i = 0; i < num; i++) {
          success = s_modbus_handlers.read_reg_fn((uint16_t) (start + i), &val);
          if (success == false) break;
          data[i] = mg_htons(val);
        }
        response[8] = (uint8_t) (num * 2);
        response_len = 9 + response[8];
        MG_DEBUG(("Glue returned %s", success ? "success" : "failure"));
      }
    }
    if (success == false) {
      response_len = 9;
      response[7] |= 0x80;
      response[8] = 4;  // Server Device Failure
    }
    *(uint16_t *) &response[4] = mg_htons((uint16_t) (response_len - 6));
    MG_DEBUG(("Sending PDU response %lu:", response_len));
    if (mg_log_level >= MG_LL_VERBOSE) mg_hexdump(response, response_len);
    mg_send(c, response, response_len);
  }
}

static void modbus_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
  // if (ev == MG_EV_OPEN) c->is_hexdumping = 1;
  if (ev == MG_EV_READ) {
    uint16_t len;
    if (c->recv.len < 7) return;  // Less than minimum length, buffer more
    len = mg_ntohs(*(uint16_t *) &c->recv.buf[4]);  // PDU length
    MG_INFO(("Got %lu, expecting %lu", c->recv.len, len + 6));
    if (c->recv.len < len + 6U) return;          // Partial frame, buffer more
    handle_modbus_pdu(c, c->recv.buf, len + 6);  // Parse PDU and call user
    mg_iobuf_del(&c->recv, 0, len + 6U);         // Delete received PDU
  }
  (void) ev_data;
}

void mongoose_set_modbus_handlers(struct mongoose_modbus_handlers *h) {
  if (h->read_reg_fn) s_modbus_handlers.read_reg_fn = h->read_reg_fn;
  if (h->write_reg_fn) s_modbus_handlers.write_reg_fn = h->write_reg_fn;
}
#endif  // WIZARD_ENABLE_MODBUS

#if WIZARD_CAPTIVE_PORTAL

#if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32
#define CAPTIVE_PORTAL_URL "udp://0.0.0.0:5533"
#else
#define CAPTIVE_PORTAL_URL "udp://0.0.0.0:53"
#endif

static const uint8_t answer[] = {
    0xc0, 0x0c,          // Point to the name in the DNS question
    0,    1,             // 2 bytes - record type, A
    0,    1,             // 2 bytes - address class, INET
    0,    0,    0, 120,  // 4 bytes - TTL
    0,    4              // 2 bytes - address length
};

static void dns_fn(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_READ) {
    struct mg_dns_rr rr;  // Parse first question, offset 12 is header size
    size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
    MG_DEBUG(("DNS request parsed, result=%d", (int) n));
    if (n > 0) {
      char buf[512];
      uint32_t ip;
      struct mg_dns_header *h = (struct mg_dns_header *) buf;
      memset(buf, 0, sizeof(buf));  // Clear the whole datagram
      h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid;  // Copy tnxid
      h->num_questions = mg_htons(1);  // We use only the 1st question
      h->num_answers = mg_htons(1);    // And only one answer
      h->flags = mg_htons(0x8400);     // Authoritative response
      memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n);  // Copy question
      memcpy(buf + sizeof(*h) + n, answer, sizeof(answer));   // And answer
#if MG_ENABLE_TCPIP
      ip = c->mgr->ifp->ip;
#else
      ip = MG_TCPIP_IP;
#endif
      memcpy(buf + sizeof(*h) + n + sizeof(answer), &ip, 4);
      mg_send(c, buf, 12 + n + sizeof(answer) + 4);  // And send it!
    }
    mg_iobuf_del(&c->recv, 0, c->recv.len);
  }
  (void) ev_data;
}
#endif  // WIZARD_CAPTIVE_PORTAL

#if WIZARD_ENABLE_MDNS
static char s_mdns_name[40] = WIZARD_MDNS_NAME;
void glue_mdns_update_name(const char *newname) {
  strncpy(s_mdns_name, newname, sizeof(s_mdns_name));
}
#endif  // WIZARD_ENABLE_MDNS

#if MG_ENABLE_TCPIP && WIZARD_ENABLE_WIFI
static struct mongoose_wifi_handlers s_wifi_handlers = {
    glue_wifi_on_connect, glue_wifi_on_disconnect, glue_wifi_on_connect_error,
    glue_wifi_on_scan_result, glue_wifi_on_scan_end};

static void wifi_event_handler(struct mg_tcpip_if *ifp, int ev, void *ev_data) {
  if (ev == MG_TCPIP_EV_ST_CHG) {
    // Keep these separate from the main interface handler,
    // to support other Wi-Fi implementations that don't use our TCP/IP stack
    // (e.g.: ESP32) in the future
    if (*(uint8_t *) ev_data == MG_TCPIP_STATE_READY) {
      s_wifi_handlers.on_connect_fn(ifp);
    } else if (*(uint8_t *) ev_data == MG_TCPIP_STATE_DOWN) {
      s_wifi_handlers.on_disconnect_fn(ifp);
    }
  } else if (ev == MG_TCPIP_EV_WIFI_CONNECT_ERR) {
    s_wifi_handlers.on_connect_error_fn(ifp);
  } else if (ev == MG_TCPIP_EV_WIFI_SCAN_RESULT) {
    struct mg_wifi_scan_bss_data *bss =
        (struct mg_wifi_scan_bss_data *) ev_data;
    s_wifi_handlers.on_scan_result_fn(ifp, bss);
  } else if (ev == MG_TCPIP_EV_WIFI_SCAN_END) {
    s_wifi_handlers.on_scan_end_fn(ifp);
  }
}

void mongoose_set_wifi_handlers(struct mongoose_wifi_handlers *h) {
  if (h->on_connect_fn) s_wifi_handlers.on_connect_fn = h->on_connect_fn;
  if (h->on_disconnect_fn)
    s_wifi_handlers.on_disconnect_fn = h->on_disconnect_fn;
  if (h->on_connect_error_fn)
    s_wifi_handlers.on_connect_error_fn = h->on_connect_error_fn;
  if (h->on_scan_result_fn)
    s_wifi_handlers.on_scan_result_fn = h->on_scan_result_fn;
  if (h->on_scan_end_fn) s_wifi_handlers.on_scan_end_fn = h->on_scan_end_fn;
}
#endif  // WIZARD_ENABLE_WIFI

void mongoose_init(void) {
  mg_mgr_init(&g_mgr);      // Initialise event manager
  mg_log_set(MG_LL_DEBUG);  // Set log level to debug

#if WIZARD_DNS_TYPE == 2
  g_mgr.dns4.url = WIZARD_DNS_URL;
#endif

#if MG_ENABLE_TCPIP
  g_mgr.ifp->fn = mif_fn;  // add interface event handler
#endif

#if WIZARD_ENABLE_HTTP
  MG_INFO(("Starting HTTP listener"));
  mg_http_listen(&g_mgr, HTTP_URL, http_ev_handler, NULL);
#endif
#if WIZARD_ENABLE_HTTPS
  MG_INFO(("Starting HTTPS listener"));
  mg_http_listen(&g_mgr, HTTPS_URL, http_ev_handler, "");
#endif

#if WIZARD_ENABLE_SNTP
  MG_INFO(("Starting SNTP timer"));
  mg_timer_add(&g_mgr, 1000, MG_TIMER_REPEAT, sntp_timer, &g_mgr);
#endif

#if WIZARD_ENABLE_MQTT
  MG_INFO(("Starting MQTT reconnection timer"));
  mg_timer_add(&g_mgr, 5000, MG_TIMER_RUN_NOW | MG_TIMER_REPEAT, mqtt_timer,
               &g_mgr);
#endif

#if WIZARD_ENABLE_MODBUS
  {
    char url[100];
    mg_snprintf(url, sizeof(url), "tcp://0.0.0.0:%d", WIZARD_MODBUS_PORT);
    MG_INFO(("Starting Modbus-TCP server on port %d", WIZARD_MODBUS_PORT));
    mg_listen(&g_mgr, url, modbus_ev_handler, NULL);
  }
#endif

#if WIZARD_CAPTIVE_PORTAL
  MG_INFO(("Starting captive portal"));
  mg_listen(&g_mgr, CAPTIVE_PORTAL_URL, dns_fn, NULL);
#endif

#if WIZARD_ENABLE_MDNS
  MG_INFO(("Starting MDNS (domain name: %s.local)", s_mdns_name));
  mg_mdns_listen(&g_mgr, NULL, s_mdns_name);  // Mongoose #3351
#endif

  glue_lock_init();
  MG_INFO(("Mongoose init complete"));
}

void mongoose_poll(void) {
  glue_lock();
  mg_mgr_poll(&g_mgr, 10);
#if WIZARD_ENABLE_WEBSOCKET
  send_websocket_data();
#endif
  glue_unlock();
}
