// Copyright (c) 2020-2023 Cesanta Software Limited
// All rights reserved
//
// Multithreading example.
// For each incoming request, we spawn a separate thread, that sleeps for
// some time to simulate long processing time, produces an output and
// hands over that output to the request handler function.
//
// We pass POST body to the worker thread, and respond with a calculated CRC

#include "mongoose.h"

struct thread_data {
  struct mg_queue queue;  // Worker -> Connection queue
  struct mg_str body;     // Copy of message body
};

static void start_thread(void *(*f)(void *), void *p) {
#ifdef _WIN32
#define usleep(x) Sleep((x) / 1000)
  _beginthread((void(__cdecl *)(void *)) f, 0, p);
#else
#include <pthread.h>
  pthread_t thread_id = (pthread_t) 0;
  pthread_attr_t attr;
  (void) pthread_attr_init(&attr);
  (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_create(&thread_id, &attr, f, p);
  pthread_attr_destroy(&attr);
#endif
}

static void *worker_thread(void *param) {
  struct thread_data *d = (struct thread_data *) param;
  char buf[100];  // On-stack buffer for the message queue

  mg_queue_init(&d->queue, buf, sizeof(buf));  // Init queue
  usleep(1 * 1000 * 1000);                     // Simulate long execution time

  // Send a response to the connection
  if (d->body.len == 0) {
    mg_queue_printf(&d->queue, "Send me POST data");
  } else {
    uint32_t crc = mg_crc32(0, d->body.ptr, d->body.len);
    mg_queue_printf(&d->queue, "crc32: %#x", crc);
    free((char *) d->body.ptr);
  }

  // Wait until connection reads our message, then it is safe to quit
  while (d->queue.tail != d->queue.head) usleep(1000);
  MG_INFO(("done, cleaning up..."));
  free(d);
  return NULL;
}

// HTTP request callback
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_HTTP_MSG) {
    // Received HTTP request. Allocate thread data and spawn a worker thread
    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
    struct thread_data *d = (struct thread_data *) calloc(1, sizeof(*d));
    d->body = mg_strdup(hm->body);   // Pass received body to the worker
    start_thread(worker_thread, d);  // Start a thread
    *(void **) c->data = d;          // Memorise data pointer in c->data
  } else if (ev == MG_EV_POLL) {
    // Poll event. Delivered to us every mg_mgr_poll interval or faster
    struct thread_data *d = *(struct thread_data **) c->data;
    size_t len;
    char *buf;
    // Check if we have a message from the worker
    if (d != NULL && (len = mg_queue_next(&d->queue, &buf)) > 0) {
      // Got message from worker. Send a response and cleanup
      mg_http_reply(c, 200, "", "%.*s\n", (int) len, buf);
      mg_queue_del(&d->queue, len);  // Delete message
      *(void **) c->data = NULL;     // Forget about thread data
    }
  }
  (void) fn_data;
}

int main(void) {
  struct mg_mgr mgr;
  mg_mgr_init(&mgr);
  mg_log_set(MG_LL_DEBUG);  // Set debug log level
  mg_http_listen(&mgr, "http://localhost:8000", fn, NULL);  // Create listener
  for (;;) mg_mgr_poll(&mgr, 10);  // Event loop. Use 10ms poll interval
  mg_mgr_free(&mgr);               // Cleanup
  return 0;
}