/*
  This file is part of TALER
  Copyright (C) 2019-2025 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file mhd_run.c
 * @brief API for running an MHD daemon with the
 *        GNUnet scheduler
 * @author Christian Grothoff
 */
#include "taler/platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include "taler/taler_util.h"
#include "taler/taler_mhd_lib.h"


/**
 * Entry in list of HTTP servers we are running.
 */
struct DaemonEntry
{
  /**
   * Kept in a DLL.
   */
  struct DaemonEntry *next;

  /**
   * Kept in a DLL.
   */
  struct DaemonEntry *prev;

  /**
   * The actual daemon.
   */
  struct MHD_Daemon *mhd;

  /**
   * Task running the HTTP server.
   */
  struct GNUNET_SCHEDULER_Task *mhd_task;

  /**
   * Set to true if we should immediately MHD_run() again.
   */
  bool triggered;

};


/**
 * Head of list of HTTP servers.
 */
static struct DaemonEntry *mhd_head;

/**
 * Tail of list of HTTP servers.
 */
static struct DaemonEntry *mhd_tail;


/**
 * Function that queries MHD's select sets and
 * starts the task waiting for them.
 *
 * @param[in,out] de daemon to start tasks for
 */
static void
prepare_daemon (struct DaemonEntry *de);


/**
 * Call MHD to process pending requests and then go back
 * and schedule the next run.
 *
 * @param cls our `struct DaemonEntry *`
 */
static void
run_daemon (void *cls)
{
  struct DaemonEntry *de = cls;

  de->mhd_task = NULL;
  do {
    de->triggered = false;
    GNUNET_assert (MHD_YES ==
                   MHD_run (de->mhd));
  } while (de->triggered);
  prepare_daemon (de);
}


static void
prepare_daemon (struct DaemonEntry *de)
{
  fd_set rs;
  fd_set ws;
  fd_set es;
  struct GNUNET_NETWORK_FDSet *wrs;
  struct GNUNET_NETWORK_FDSet *wws;
  int max;
  MHD_UNSIGNED_LONG_LONG timeout;
  int haveto;
  struct GNUNET_TIME_Relative tv;

  FD_ZERO (&rs);
  FD_ZERO (&ws);
  FD_ZERO (&es);
  wrs = GNUNET_NETWORK_fdset_create ();
  wws = GNUNET_NETWORK_fdset_create ();
  max = -1;
  GNUNET_assert (MHD_YES ==
                 MHD_get_fdset (de->mhd,
                                &rs,
                                &ws,
                                &es,
                                &max));
  haveto = MHD_get_timeout (de->mhd,
                            &timeout);
  if (haveto == MHD_YES)
    tv = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
                                        timeout);
  else
    tv = GNUNET_TIME_UNIT_FOREVER_REL;
  GNUNET_NETWORK_fdset_copy_native (wrs,
                                    &rs,
                                    max + 1);
  GNUNET_NETWORK_fdset_copy_native (wws,
                                    &ws,
                                    max + 1);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Adding run_daemon select task\n");
  de->mhd_task
    = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
                                   tv,
                                   wrs,
                                   wws,
                                   &run_daemon,
                                   de);
  GNUNET_NETWORK_fdset_destroy (wrs);
  GNUNET_NETWORK_fdset_destroy (wws);
}


void
TALER_MHD_daemon_start (struct MHD_Daemon *daemon)
{
  struct DaemonEntry *de;

  de = GNUNET_new (struct DaemonEntry);
  de->mhd = daemon;
  GNUNET_CONTAINER_DLL_insert (mhd_head,
                               mhd_tail,
                               de);
  prepare_daemon (de);
}


void
TALER_MHD_daemons_halt (void)
{
  for (struct DaemonEntry *de = mhd_head;
       NULL != de;
       de = de->next)
  {
    if (NULL != de->mhd_task)
    {
      GNUNET_SCHEDULER_cancel (de->mhd_task);
      de->mhd_task = NULL;
    }
    de->triggered = false;
  }
}


void
TALER_MHD_daemons_quiesce (void)
{
  for (struct DaemonEntry *de = mhd_head;
       NULL != de;
       de = de->next)
  {
    int fd;

    if (NULL != de->mhd_task)
    {
      GNUNET_SCHEDULER_cancel (de->mhd_task);
      de->mhd_task = NULL;
    }
    de->triggered = false;
    fd = MHD_quiesce_daemon  (de->mhd);
    GNUNET_break (0 == close (fd));
  }
}


void
TALER_MHD_daemons_destroy (void)
{
  struct DaemonEntry *de;

  while (NULL != (de = mhd_head))
  {
    struct MHD_Daemon *mhd = de->mhd;

    if (NULL != de->mhd_task)
    {
      GNUNET_SCHEDULER_cancel (de->mhd_task);
      de->mhd_task = NULL;
    }
    MHD_stop_daemon (mhd);
    GNUNET_CONTAINER_DLL_remove (mhd_head,
                                 mhd_tail,
                                 de);
    GNUNET_free (de);
  }
}


void
TALER_MHD_daemon_trigger (void)
{
  for (struct DaemonEntry *de = mhd_head;
       NULL != de;
       de = de->next)
  {
    if (NULL != de->mhd_task)
    {
      GNUNET_SCHEDULER_cancel (de->mhd_task);
      de->mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
                                               de);
    }
    else
    {
      de->triggered = true;
    }
  }
}


/* end of mhd_run.c */
