/*
     This file is part of libmicrohttpd
     Copyright (C) 2024 Christian Grothoff (and other contributing authors)

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

     This library 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
     Lesser General Public License for more details.

     You should have received a copy of the GNU Lesser General Public
     License along with this library; if not, write to the Free Software
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
/**
 * @file options-generator.c
 * @brief Generates code based on Recutils database
 * @author Christian Grothoff
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#if ! defined(_WIN32) || defined(__CYGWIN__)
#  include <unistd.h>
#endif
#include <sys/stat.h>
#include <errno.h>

#define MAX_ARGS 3

struct Option
{
  struct Option *next;
  char *name;
  unsigned int value;
  char *type;
  char *comment;
  char *custom_setter;
  unsigned int argc;
  char *arguments[MAX_ARGS];
  unsigned int desc;
  char *descriptions[MAX_ARGS];
  char *conditional;
};

static FILE *f;

static char *category;

typedef void
(*Callback) (const char *name,
             unsigned int value,
             const char *comment,
             const char *type,
             const char *conditional,
             const char *custom_setter,
             unsigned int argc,
             char **arguments,
             unsigned int desc,
             char **descriptions);


static int
my_asprintf (char **buf,
             const char *format,
             ...)
{
  int ret;
  va_list args;

  va_start (args,
            format);
  ret = vsnprintf (NULL,
                   0,
                   format,
                   args);
  va_end (args);
  *buf = malloc (ret + 1);
  va_start (args,
            format);
  ret = vsprintf (*buf,
                  format,
                  args);
  va_end (args);
  return ret;
}


static void
iterate (struct Option *head,
         Callback cb)
{
  for (struct Option *o = head; NULL != o; o = o->next)
  {
    if (0 == strcmp ("end",
                     o->name))
      continue;
    cb (o->name,
        o->value,
        o->comment,
        o->type,
        o->conditional,
        o->custom_setter,
        o->argc,
        o->arguments,
        o->desc,
        o->descriptions);
  }
}


static void
check (const char *name,
       unsigned int value,
       const char *comment,
       const char *type,
       const char *conditional,
       const char *custom_setter,
       unsigned int argc,
       char **arguments,
       unsigned int desc,
       char **descriptions)
{
  if (argc != desc)
  {
    fprintf (stderr,
             "Mismatch between descriptions and arguments for `%s'\n",
             name);
    exit (2);
  }
  if ( (NULL == type) &&
       ( (0 == argc) ||
         (1 != argc) ) )
  {
    fprintf (stderr,
             "Type and argument missing for `%s' and not exactly 1 argument\n",
             name);
    exit (2);
  }
  for (unsigned int i = 0; i<argc; i++)
  {
    const char *arg = arguments[i];

    if (NULL == (strrchr (arg, ' ')))
    {
      fprintf (stderr,
               "Mandatory space missing in argument%u of `%s'\n",
               i,
               name);
      exit (2);
    }
  }
  if (NULL != strchr (name, ' '))
  {
    fprintf (stderr,
             "Spaces are not allowed in names, found one in `%s'\n",
             name);
    exit (2);
  }
}


static char *
indent (char *pfx,
        const char *input)
{
  char *ret = strdup (input);
  char *xfx = strdup (pfx);
  char *off;
  size_t pos = 0;

  while ( (strlen (xfx) > 0) &&
          (isspace (xfx[strlen (xfx) - 1])) )
    xfx[strlen (xfx) - 1] = '\0';
  while (NULL != (off = strchr (ret + pos, '\n')))
  {
    char *tmp;

    my_asprintf (&tmp,
                 "%.*s\n%s%s",
                 (int) (off - ret),
                 ret,
                 (off[1] == '\n')
              ? xfx
              : pfx,
                 off + 1);
    pos = (off - ret) + strlen (pfx) + 1;
    free (ret);
    ret = tmp;
  }
  return ret;
}


static char *
uppercase (const char *input)
{
  char *ret = strdup (input);

  for (size_t i = 0; '\0' != ret[i]; i++)
    ret[i] = toupper (ret[i]);
  return ret;
}


static char *
capitalize (const char *input)
{
  char *ret = strdup (input);

  ret[0] = toupper (ret[0]);
  return ret;
}


static char *
lowercase (const char *input)
{
  char *ret = strdup (input);

  for (size_t i = 0; '\0' != ret[i]; i++)
    ret[i] = tolower (ret[i]);
  return ret;
}


static void
dump_enum (const char *name,
           unsigned int value,
           const char *comment,
           const char *type,
           const char *conditional,
           const char *custom_setter,
           unsigned int argc,
           char **arguments,
           unsigned int desc,
           char **descriptions)
{
  printf ("  /**\n   * %s\n   */\n  MHD_%c_O_%s = %u\n  ,\n\n",
          indent ("   * ", comment),
          (char) toupper (*category),
          uppercase (name),
          value);
}


static const char *
var_name (const char *arg)
{
  const char *space;

  space = strrchr (arg, ' ');
  while ('*' == space[1])
    space++;
  return space + 1;
}


static void
dump_union_members (const char *name,
                    unsigned int value,
                    const char *comment,
                    const char *type,
                    const char *conditional,
                    const char *custom_setter,
                    unsigned int argc,
                    char **arguments,
                    unsigned int desct,
                    char **descriptions)
{
  if (NULL == type)
    return;
  if (1 >= argc)
    return;

  printf ("/**\n * Data for #MHD_%c_O_%s\n */\n%s\n{\n",
          (char) toupper (*category),
          uppercase (name),
          type);
  for (unsigned int i = 0; i<argc; i++)
  {
    const char *arg = arguments[i];
    const char *desc = descriptions[i];
    const char *vn = var_name (arg);

    printf ("  /**\n   * %s\n   */\n  %.*sv_%s;\n\n",
            indent ("   * ",
                    desc),
            (int) (vn - arg),
            arg,
            vn);
  }
  printf ("};\n\n");
}


static void
dump_union (const char *name,
            unsigned int value,
            const char *comment,
            const char *type,
            const char *conditional,
            const char *custom_setter,
            unsigned int argc,
            char **arguments,
            unsigned int desc,
            char **descriptions)
{
  const char *xcomment = xcomment = descriptions[0];

  fprintf (f,
           "  /**\n"
           "   * Value for #MHD_%c_O_%s.%s%s\n"
           "   */\n",
           (char) toupper (*category),
           uppercase (name),
           NULL != xcomment
          ? "\n   * "
          : "",
           NULL != xcomment
          ? indent ("   * ", xcomment)
          : "");
  if (NULL != type)
  {
    fprintf (f,
             "  %s %s;\n",
             type,
             lowercase (name));
  }
  else
  {
    const char *arg = arguments[0];
    const char *vn = var_name (arg);

    fprintf (f,
             "  %.*s%s;\n",
             (int) (vn - arg),
             arg,
             lowercase (name));
  }
  fprintf (f,
           "\n");
}


static void
dump_struct (const char *name,
             unsigned int value,
             const char *comment,
             const char *type,
             const char *conditional,
             const char *custom_setter,
             unsigned int argc,
             char **arguments,
             unsigned int desc,
             char **descriptions)
{
  if (NULL != conditional)
    fprintf (f,
             "#ifdef HAVE_%s",
             uppercase (conditional));
  dump_union (name,
              value,
              comment,
              type,
              conditional,
              custom_setter,
              argc,
              arguments,
              desc,
              descriptions);
  if (NULL != conditional)
    fprintf (f,
             "#endif\n");
  fprintf (f,
           "\n");
}


static void
dump_option_macros (const char *name,
                    unsigned int value,
                    const char *comment,
                    const char *type,
                    const char *conditional,
                    const char *custom_setter,
                    unsigned int argc,
                    char **arguments,
                    unsigned int desct,
                    char **descriptions)
{
  printf (
    "/**\n * %s\n",
    indent (" * ", comment));
  for (unsigned int off = 0; off<desct; off++)
  {
    const char *arg = arguments[off];
    const char *desc = descriptions[off];
    const char *vn = var_name (arg);

    printf (" * @param %s %s\n",
            vn,
            indent (" *   ",
                    desc));
  }
  if (0 == desct)
    printf (" * @param value the value of the parameter");
  printf (" * @return structure with the requested setting\n */\n");
  printf ("#  define MHD_%c_OPTION_%s(",
          (char) toupper (*category),
          uppercase (name));
  if (0 == argc)
    printf ("value");
  else
    for (unsigned int i = 0; i<argc; i++)
    {
      if (0 != i)
        printf (",");
      printf ("%s",
              var_name (arguments[i]));
    }
  printf (") \\\n"
          "        MHD_NOWARN_COMPOUND_LITERALS_ \\\n"
          "          (const struct MHD_%sOptionAndValue) \\\n"
          "        { \\\n"
          "          .opt = MHD_%c_O_%s,  \\\n",
          capitalize (category),
          (char) toupper (*category),
          uppercase (name));
  if (0 == argc)
    printf ("          .val.%s = (value) \\\n",
            lowercase (name));
  else
    for (unsigned int i = 0; i<argc; i++)
    {
      const char *vn = var_name (arguments[i]);

      if (1 < argc)
        printf ("          .val.%s.v_%s = (%s)%s \\\n",
                lowercase (name),
                vn,
                vn,
                (i < argc - 1)
              ? ","
              : "");
      else
        printf ("          .val.%s = (%s)%s \\\n",
                lowercase (name),
                vn,
                (i < argc - 1)
                ? ","
                : "");
    }

  printf ("        } \\\n"
          "        MHD_RESTORE_WARN_COMPOUND_LITERALS_\n");
}


static void
dump_option_static_functions (const char *name,
                              unsigned int value,
                              const char *comment,
                              const char *type,
                              const char *conditional,
                              const char *custom_setter,
                              unsigned int argc,
                              char **arguments,
                              unsigned int desct,
                              char **descriptions)
{
  printf (
    "\n"
    "/**\n * %s\n",
    indent (" * ", comment));
  for (unsigned int off = 0; off < desct; off++)
  {
    const char *arg = arguments[off];
    const char *desc = descriptions[off];
    const char *vn = var_name (arg);

    printf (" * @param %s %s\n",
            vn,
            indent (" *   ",
                    desc));
  }
  if (0 == desct)
    printf (" * @param value the value of the parameter");
  printf (" * @return structure with the requested setting\n */\n");
  printf ("static MHD_INLINE struct MHD_%sOptionAndValue\n"
          "MHD_%c_OPTION_%s (\n",
          capitalize (category),
          (char) toupper (*category),
          uppercase (name));
  if (0 == argc)
    printf ("  %s value",
            NULL != type
            ? type
            : arguments[0]);
  else
    for (unsigned int i = 0; i<argc; i++)
    {
      const char *arg = arguments[i];
      const char *vn
        = var_name (arg);

      if (0 != i)
        printf (",\n");
      printf ("  %.*s%s",
              (int) (vn - arg),
              arg,
              vn);
    }
  printf (
    "\n"
    "  )\n"
    "{\n"
    "  struct MHD_%sOptionAndValue opt_val;\n\n"
    "  opt_val.opt = MHD_%c_O_%s;\n",
    capitalize (category),
    (char) toupper (*category),
    uppercase (name));
  if (0 == argc)
    printf ("  opt_val.val.%s = (value); \\\n",
            lowercase (name));
  else
    for (unsigned int i = 0; i<argc; i++)
    {
      const char *vn = var_name (arguments[i]);

      if (1 < argc)
        printf ("  opt_val.val.%s.v_%s = %s;\n",
                lowercase (name),
                vn,
                vn);
      else
        printf ("  opt_val.val.%s = %s;\n",
                lowercase (name),
                vn);
    }
  printf ("\n  return opt_val;\n}\n\n");
}


static void
dump_option_documentation_functions (const char *name,
                                     unsigned int value,
                                     const char *comment,
                                     const char *type,
                                     const char *conditional,
                                     const char *custom_setter,
                                     unsigned int argc,
                                     char **arguments,
                                     unsigned int desct,
                                     char **descriptions)
{

  fprintf (f,
           "/**\n * %s\n",
           indent (" * ", comment));
  for (unsigned int off = 0; off<desct; off++)
  {
    const char *arg = arguments[off];
    const char *desc = descriptions[off];
    const char *vn = var_name (arg);

    fprintf (f, " * @param %s %s\n",
             vn,
             indent (" *   ",
                     desc));
  }
  if (0 == desct)
    fprintf (f, " * @param value the value of the parameter");
  fprintf (f, " * @return structure with the requested setting\n */\n");
  fprintf (f,"struct MHD_%sOptionAndValue\n"
           "MHD_%c_OPTION_%s (\n",
           capitalize (category),
           (char) toupper (*category),
           uppercase (name));
  if (0 == argc)
    fprintf (f,
             "  %s value",
             NULL != type
             ? type
             : arguments[0]);
  else
    for (unsigned int i = 0; i<argc; i++)
    {
      const char *arg = arguments[i];
      const char *vn = var_name (arg);

      if (0 != i)
        fprintf (f, ",\n");
      fprintf (f,
               "  %.*s%s",
               (int) (vn - arg),
               arg,
               vn);
    }
  fprintf (f,
           "\n  );\n\n");
}


static void
dump_option_set_switch (const char *name,
                        unsigned int value,
                        const char *comment,
                        const char *type,
                        const char *conditional,
                        const char *custom_setter,
                        unsigned int argc,
                        char **arguments,
                        unsigned int desc,
                        char **descriptions)
{
  if (NULL != conditional)
    fprintf (f,
             "#ifdef HAVE_%s",
             uppercase (conditional));
  fprintf (f,
           "    case MHD_%c_O_%s:\n",
           (char) toupper (*category),
           uppercase (name));
  if (NULL != custom_setter)
  {
    fprintf (f,
             "      %s\n",
             indent ("      ",
                     custom_setter));
  }
  else
  {
    if (0 == argc)
    {
      fprintf (f,
               "      settings->%s = option->val.%s;\n",
               lowercase (name),
               lowercase (name));
    }
    else
    {
      for (unsigned int i = 0; i<argc; i++)
      {
        const char *vn = var_name (arguments[i]);

        if (1 < argc)
          fprintf (f,
                   "      settings->%s.v_%s = option->val.%s.v_%s;\n",
                   lowercase (name),
                   vn,
                   lowercase (name),
                   vn);
        else
          fprintf (f,
                   "      settings->%s = option->val.%s;\n",
                   lowercase (name),
                   lowercase (name));
      }
    }
  }
  fprintf (f,
           "      continue;\n");
  if (NULL != conditional)
    fprintf (f,
             "#endif\n");
}


static char **
parse (char **target,
       const char *prefix,
       const char *input)
{
  if (0 != strncasecmp (prefix,
                        input,
                        strlen (prefix)))
    return NULL;
  *target = strdup (input + strlen (prefix));
  return target;
}


int
main (int argc,
      char **argv)
{
  static char *dummy;
  struct Option *head = NULL;

  if (argc < 2)
  {
    fprintf (stderr,
             "Category argument required\n");
    return 3;
  }
  category = argv[1];

  {
    char *fn;
    char line[4092];
    char **larg = NULL;
    unsigned int off;
    struct Option *last = NULL;

    my_asprintf (&fn,
                 "%c_options.rec",
                 *category);
    f = fopen (fn, "r");
    if (NULL == f)
    {
      fprintf (stderr,
               "Failed to open %s: %s\n",
               fn,
               strerror (errno));
      free (fn);
      return 2;
    }
    off = 0;
TOP:
    while (1)
    {
      ssize_t r;

      if (NULL ==
          fgets (line,
                 sizeof (line),
                 f))
        break;
      r = strlen (line);
      off++;
      while ( (r > 0) &&
              ( (isspace (line[r - 1])) ||
                (line[r - 1] == '\n') ) )
        line[--r] = '\0'; /* remove new line */
      if (0 == r)
      {
        larg = NULL;
        continue;
      }
      if ( (NULL != larg) &&
           ( (0 == strncmp ("+ ",
                            line,
                            2) ) ||
             (0 == strcmp ("+",
                           line) ) ) )
      {
        if (larg == &dummy)
        {
          fprintf (stderr,
                   "Continuation after 'Value:' not supported on line %u\n",
                   off);
          exit (2);
        }

        *larg = realloc (*larg,
                         strlen (*larg) + r + 2);
        strcat (*larg,
                "\n");
        if (0 != strcmp ("+",
                         line) )
          strcat (*larg,
                  line + 2);
        continue;
      }
      if ( ('%' == line[0]) ||
           ('#' == line[0]) )
        continue;
      if (NULL == larg)
      {
        struct Option *o = malloc (sizeof (struct Option));

        memset (o, 0, sizeof (*o));
        if (NULL == head)
          head = o;
        else
          last->next = o;
        last = o;
      }
      if (NULL != (larg = parse (&last->name,
                                 "Name: ",
                                 line)))
        continue;
      if (NULL != (larg = parse (&last->comment,
                                 "Comment: ",
                                 line)))
        continue;
      if (NULL != (larg = parse (&last->type,
                                 "Type: ",
                                 line)))
        continue;
      if (NULL != (larg = parse (&last->conditional,
                                 "Conditional: ",
                                 line)))
        continue;
      if (NULL != (larg = parse (&last->custom_setter,
                                 "CustomSetter: ",
                                 line)))
        continue;
      if (0 == strncasecmp (line,
                            "Value: ",
                            strlen ("Value: ")))
      {
        char xdummy;

        if (1 == sscanf (line + strlen ("Value: "),
                         "%u%c",
                         &last->value,
                         &xdummy))
        {
          larg = &dummy;
          continue;
        }
        fprintf (stderr,
                 "Value on line %d not a number\n",
                 off);
        return 2;
      }
      for (unsigned int i = 0; i<MAX_ARGS; i++)
      {
        char name[32];

        snprintf (name,
                  sizeof (name),
                  "Argument%u: ",
                  i + 1);
        if (NULL != (larg = parse (&last->arguments[i],
                                   name,
                                   line)))
        {
          last->argc = i + 1;
          goto TOP;
        }
        snprintf (name,
                  sizeof (name),
                  "Description%u: ",
                  i + 1);
        if (NULL != (larg = parse (&last->descriptions[i],
                                   name,
                                   line)))
        {
          last->desc = i + 1;
          goto TOP;
        }
      }
      fprintf (stderr,
               "Could not parse line %u: `%s'\n",
               off,
               line);
      exit (2);
    }
    free (fn);
  }

  iterate (head,
           &check);

  /* Generate enum MHD_${CATEGORY}Option */
  printf ("/**\n"
          " * The options (parameters) for MHD %s\n"
          " */\n"
          "enum MHD_FIXED_ENUM_APP_SET_ MHD_%sOption\n"
          "{",
          category,
          capitalize (category));
  printf ("  /**\n"
          "   * Not a real option.\n"
          "   * Should not be used directly.\n"
          "   * This value indicates the end of the list of the options.\n"
          "   */\n"
          "  MHD_%c_O_END = 0\n"
          "  ,\n\n",
          (char) toupper (*category));
  iterate (head,
           &dump_enum);
  printf ("  /**\n"
          "   * The sentinel value.\n"
          "   * This value enforces specific underlying integer type for the enum.\n"
          "   * Do not use.\n"
          "   */\n"
          "  MHD_%c_O_SENTINEL = 65535\n\n",
          (char) toupper (*category));
  printf ("};\n\n");
  iterate (head,
           &dump_union_members);

  /* Generate union MHD_${CATEGORY}OptionValue */
  printf ("/**\n"
          " * Parameters for MHD %s options\n"
          " */\n"
          "union MHD_%sOptionValue\n"
          "{\n",
          category,
          capitalize (category));
  f = stdout;
  iterate (head,
           &dump_union);
  f = NULL;
  printf ("};\n\n");

  printf ("\n"
          "struct MHD_%sOptionAndValue\n"
          "{\n"
          "  /**\n"
          "   * The %s configuration option\n"
          "   */\n"
          "  enum MHD_%sOption opt;\n\n"
          "  /**\n"
          "   * The value for the @a opt option\n"
          "   */\n"
          "  union MHD_%sOptionValue val;\n"
          "};\n\n",
          capitalize (category),
          category,
          capitalize (category),
          capitalize (category));
  printf (
    "#if defined(MHD_USE_COMPOUND_LITERALS) && defined(MHD_USE_DESIG_NEST_INIT)\n");
  iterate (head,
           &dump_option_macros);
  printf (
    "\n"
    "/**\n"
    " * Terminate the list of the options\n"
    " * @return the terminating object of struct MHD_%sOptionAndValue\n"
    " */\n"
    "#  define MHD_%c_OPTION_TERMINATE() \\\n"
    "        MHD_NOWARN_COMPOUND_LITERALS_ \\\n"
    "          (const struct MHD_%sOptionAndValue) \\\n"
    "        { \\\n"
    "          .opt = (MHD_%c_O_END) \\\n"
    "        } \\\n"
    "        MHD_RESTORE_WARN_COMPOUND_LITERALS_\n\n",
    capitalize (category),
    (char) toupper (*category),
    capitalize (category),
    (char) toupper (*category));

  printf (
    "#else /* !MHD_USE_COMPOUND_LITERALS || !MHD_USE_DESIG_NEST_INIT */\n");
  printf ("MHD_NOWARN_UNUSED_FUNC_");
  iterate (head,
           &dump_option_static_functions);
  printf ("\n/**\n"
          " * Terminate the list of the options\n"
          " * @return the terminating object of struct MHD_%sOptionAndValue\n"
          " */\n"
          "static MHD_INLINE struct MHD_%sOptionAndValue\n"
          "MHD_%c_OPTION_TERMINATE (void)\n"
          "{\n"
          "  struct MHD_%sOptionAndValue opt_val;\n\n"
          "  opt_val.opt = MHD_%c_O_END;\n\n"
          "  return opt_val;\n"
          "}\n\n\n",
          capitalize (category),
          capitalize (category),
          (char) toupper (*category),
          capitalize (category),
          (char) toupper (*category));

  printf ("MHD_RESTORE_WARN_UNUSED_FUNC_\n");
  printf (
    "#endif /* !MHD_USE_COMPOUND_LITERALS || !MHD_USE_DESIG_NEST_INIT */\n");

  {
    char *doc_in;

    my_asprintf (&doc_in,
                 "microhttpd2_inline_%s_documentation.h.in",
                 category);
    (void) unlink (doc_in);
    f = fopen (doc_in, "w");
    if (NULL == f)
    {
      fprintf (stderr,
               "Failed to open `%s': %s\n",
               doc_in,
               strerror (errno));
      return 2;
    }
    fprintf (f,
             "/* Beginning of generated code documenting how to use options.\n"
             "   You should treat the following functions *as if* they were\n"
             "   part of the header/API. The actual declarations are more\n"
             "   complex, so these here are just for documentation!\n"
             "   We do not actually *build* this code... */\n"
             "#if 0\n\n");
    iterate (head,
             &dump_option_documentation_functions);
    fprintf (f,
             "/* End of generated code documenting how to use options */\n#endif\n\n");
    fclose (f);
    chmod (doc_in, S_IRUSR | S_IRGRP | S_IROTH);
  }

  {
    char *so_c;

    my_asprintf (&so_c,
                 "../mhd2/%s_set_options.c",
                 category);
    (void) unlink (so_c);
    f = fopen (so_c, "w");
    if (NULL == f)
    {
      fprintf (stderr,
               "Failed to open `%s': %s\n",
               so_c,
               strerror (errno));
      return 2;
    }
    fprintf (f,
             "/* This is generated code, it is still under LGPLv2.1+.\n"
             "   Do not edit directly! */\n"
             "/* *INDENT-OFF* */\n"
             "/**\n"
             " * @file %s_set_options.c\n"
             " * @author options-generator.c\n"
             " */\n"
             "\n",
             category);
    if (0 == strcmp (category, "daemon"))
    {
      fprintf (f,
               "#include \"mhd_sys_options.h\"\n"
               "#include \"sys_base_types.h\"\n"
               "#include \"sys_malloc.h\"\n"
               "#include <string.h>\n"
               "#include \"mhd_daemon.h\"\n"
               "#include \"daemon_options.h\"\n"
               "#include \"mhd_public_api.h\"\n"
               "\n");
    }
    else if (0 == strcmp (category, "response"))
    {
      fprintf (f,
               "#include \"mhd_sys_options.h\"\n"
               "#include \"response_set_options.h\"\n"
               "#include \"sys_base_types.h\"\n"
               "#include \"sys_bool_type.h\"\n"
               "#include \"response_options.h\"\n"
               "#include \"mhd_response.h\"\n"
               "#include \"mhd_public_api.h\"\n"
               "#include \"mhd_locks.h\"\n"
               "#include \"mhd_assert.h\"\n"
               "#include \"response_funcs.h\"\n"
               "\n");
    }

    fprintf (f,
             "\n"
             "MHD_FN_PAR_NONNULL_ALL_ MHD_EXTERN_\n"
             "enum MHD_StatusCode\n"
             "MHD_%s_set_options (\n"
             "  struct MHD_%s *%s,\n"
             "  const struct MHD_%sOptionAndValue *options,\n"
             "  size_t options_max_num)\n"
             "{\n",
             category,
             capitalize (category),
             category,
             capitalize (category));
    fprintf (f,
             "  struct %sOptions *restrict settings = %s->settings;\n"
             "  enum MHD_StatusCode res = MHD_SC_OK;\n"
             "  size_t i;\n",
             capitalize (category),
             category);
    if (0 == strcmp (category, "daemon"))
    {
      fprintf (f,
               "\n"
               "  if (mhd_DAEMON_STATE_NOT_STARTED != daemon->state)\n"
               "    return MHD_SC_TOO_LATE;\n"
               "\n");
    }
    else if (0 == strcmp (category, "response"))
    {
      fprintf (f,
               "  bool need_unlock = false;\n"
               "\n"
               "  if (response->frozen)\n"
               "    return MHD_SC_TOO_LATE;\n"
               "  if (response->reuse.reusable)\n"
               "  {\n"
               "    need_unlock = true;\n"
               "    if (! mhd_mutex_lock(&response->reuse.settings_lock))\n"
               "      return MHD_SC_RESPONSE_MUTEX_LOCK_FAILED;\n"
               "    mhd_assert (1 == mhd_atomic_counter_get(&response->reuse.counter));\n"
               "    if (response->frozen) /* Firm re-check under the lock */\n"
               "    {\n"
               "      mhd_mutex_unlock_chk(&response->reuse.settings_lock);\n"
               "      return MHD_SC_TOO_LATE;\n"
               "    }\n"
               "  }\n"
               "\n");
    }
    else
    {
      fprintf (f,
               "\n"
               "  if (NULL == settings)\n"
               "    return MHD_SC_TOO_LATE;\n"
               "\n");
    }
    fprintf (f,
             "  for (i = 0; i < options_max_num; i++)\n"
             "  {\n"
             "    const struct MHD_%sOptionAndValue *const option\n"
             "      = options + i;\n"
             "    switch (option->opt)\n"
             "    {\n",
             capitalize (category));
    fprintf (f,
             "    case MHD_%c_O_END:\n"
             "      i = options_max_num - 1;\n"
             "      break;\n",
             (char) toupper (*category));
    iterate (head,
             &dump_option_set_switch);
    fprintf (f,
             "    case MHD_%c_O_SENTINEL:\n"
             "    default: /* for -Wswitch-default -Wswitch-enum */ \n"
             "      res = MHD_SC_OPTION_UNKNOWN;\n"
             "      i = options_max_num - 1;\n"
             "      break;\n",
             (char) toupper (*category));
    fprintf (f,
             "    }\n"
             "  }\n");
    if (0 == strcmp (category, "response"))
    {
      fprintf (f,
               "\n"
               "  if (need_unlock)\n"
               "    mhd_mutex_unlock_chk(&response->reuse.settings_lock);\n"
               "\n");
    }
    fprintf (f,
             "  return res;\n"
             "}\n");
    fclose (f);
    chmod (so_c, S_IRUSR | S_IRGRP | S_IROTH);
    free (so_c);
  }

  {
    char *do_h;

    my_asprintf (&do_h,
                 "../mhd2/%s_options.h",
                 category);
    (void) unlink (do_h);
    f = fopen (do_h, "w");
    if (NULL == f)
    {
      fprintf (stderr,
               "Failed to open `%s': %s\n",
               do_h,
               strerror (errno));
      return 2;
    }
    fprintf (f,
             "/* This is generated code, it is still under LGPLv2.1+.\n"
             "   Do not edit directly! */\n"
             "/* *INDENT-OFF* */\n"
             "/**\n"
             " * @file %s_options.h\n"
             " * @author %s-options-generator.c\n"
             " */\n\n"
             "#ifndef MHD_%s_OPTIONS_H\n"
             "#define MHD_%s_OPTIONS_H 1\n"
             "\n"
             "#include \"mhd_sys_options.h\"\n"
             "#include \"mhd_public_api.h\"\n"
             "\n"
             "struct %sOptions\n"
             "{\n",
             category,
             category,
             uppercase (category),
             uppercase (category),
             capitalize (category));
    iterate (head,
             &dump_struct);
    fprintf (f,
             "};\n"
             "\n"
             "#endif /* ! MHD_%s_OPTIONS_H 1 */\n",
             uppercase (category));
    fclose (f);
    chmod (do_h, S_IRUSR | S_IRGRP | S_IROTH);
    free (do_h);
  }

  return 0;
}
