// Copyright(c) 2020 to 2022 ZettaScale Technology and others
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
// v. 1.0 which is available at
// http://www.eclipse.org/org/documents/edl-v10.php.
//
// SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "_confgen.h"

#include "dds/ddsrt/heap.h"

static const char *xlatmd(const char *str, const char **end)
{
  static struct { const char *search; const char *replace; } tr[] = {
    { "<p>", "" }, { "</p>", "" },
    { "<b>", "" }, { "</b>", "" },
    { "<i>", "" }, { "</i>", "" },
    { "<li>", " * " }, { "</li>", "\n" },
    { "<ul>", "" }, { "</ul>", "\n" },
    { "<sup>", "^" }, { "</sup>", "" },
    { "<code>", "``" }, { "</code>", "``" },
    { "&lt;empty&gt;", "<empty>" },
    { "*", "\\*" }, { "_", "\\_" },
    { NULL, NULL }
  };

  /* replace </p>\s*<p> by double newline */
  if (strncmp(str, "</p>", 4) == 0) {
    const char *ptr = str + 4;
    while (*ptr == '\n' || *ptr == ' ') ptr++;
    if (strncmp(ptr, "<p>", 3) == 0) {
      *end = ptr;
      return "\n\n";
    }
  }

  for (size_t cnt = 0; tr[cnt].search; cnt++) {
    size_t len = strlen(tr[cnt].search);
    if (strncmp(str, tr[cnt].search, len) == 0) {
      *end = str + len;
      return tr[cnt].replace;
    }
  }

  return NULL;
}

static char headerchars[] = {'#', '*', '=', '-', '^', '"', '_', '+', ':', '~'};

static void printhead(
  FILE *out,
  unsigned int level,
  unsigned int flags,
  struct cfgelem *elem,
  const struct cfgunit *units)
{
  (void)level;
  (void)flags;
  (void)units;
  assert(level < (sizeof(headerchars)));
  fprintf(out, ".. _`%s`:\n\n%s\n", elem->meta.title, elem->meta.title);
  for(size_t i = strlen(elem->meta.title); i > 0; --i)
    fputc(headerchars[level], out);
  fputs("\n\n", out);
}

static void printlink(
  FILE *out,
  unsigned int level,
  unsigned int flags,
  struct cfgelem *elem,
  const struct cfgunit *units)
{
  (void)level;
  (void)flags;
  (void)units;
  assert(elem->meta.title);
  fprintf(out, ":ref:`%s<%s>`", elem->name, elem->meta.title);
}

static void printtype(
  FILE *out,
  unsigned int level,
  unsigned int flags,
  struct cfgelem *elem,
  const struct cfgunit *units)
{
  (void)level;
  (void)flags;
  (void)units;
  assert(!isgroup(elem));
  if (isbool(elem)) {
    fputs("Boolean\n", out);
  } else if (islist(elem)) {
    assert(elem->meta.values);
    fputs("One of:\n", out);
    if (elem->value && strlen(elem->value))
      fprintf(out, "* Keyword: %s\n", elem->value);
    fputs("* Comma-separated list of: ", out);
    for (const char **v = elem->meta.values; *v; v++) {
      fprintf(out, "%s%s", v == elem->meta.values ? "" : ", ", *v);
    }
    fputs("\n", out);
    if (!elem->value || !strlen(elem->value))
      fputs("* Or empty\n", out);
  } else if (isenum(elem)) {
    assert(elem->meta.values);
    fputs("One of: ", out);
    for (const char **v = elem->meta.values; *v; v++) {
      fprintf(out, "%s%s", v == elem->meta.values ? "" : ", ", *v);
    }
    fputs("\n", out);
  } else if (isint(elem)) {
    fputs("Integer\n", out);
  } else if (elem->meta.unit) {
    fputs("Number-with-unit\n", out);
  } else if (isstring(elem)) {
    fputs("Text\n", out);
  }
}

#define FLAG_LF (1u<<0)

static void printattr(
  FILE *out,
  unsigned int level,
  unsigned int flags,
  struct cfgelem *elem,
  const struct cfgunit *units)
{
  if (flags & FLAG_LF)
    fputs("\n\n", out);
  printhead(out, level, flags, elem, units);
  printtype(out, level, flags, elem, units);
  fputs("\n", out);
  if (elem->description) {
    fputs(elem->meta.description, out);
    fputs("\n", out);
  }
}

static void printelem(
  FILE *out,
  unsigned int level,
  unsigned int flags,
  struct cfgelem *elem,
  const struct cfgunit *units)
{
  if (flags & FLAG_LF)
    fputs("\n\n", out);
  printhead(out, level, flags, elem, units);
  flags &= ~FLAG_LF;
  if (hasattributes(elem)) {
    int cnt = 0;
    const char *sep = "Attributes: ";
    struct cfgelem *ce = firstelem(elem->attributes);
    while (ce) {
      if (!isnop(ce)) {
        fputs(sep, out);
        printlink(out, level, flags, ce, units);
        sep = ", ";
        cnt++;
      }
      ce = nextelem(elem->attributes, ce);
    }
    if (cnt != 0) {
      fputs("\n", out);
      flags |= FLAG_LF;
    }
  }
  if (haschildren(elem)) {
    const char *sep = "Children: ";
    struct cfgelem *ce = firstelem(elem->children);
    while (ce) {
      fputs(sep, out);
      printlink(out, level, flags, ce, units);
      sep = ", ";
      ce = nextelem(elem->children, ce);
    }
    fputs("\n", out);
    flags |= FLAG_LF;
  } else if (!isgroup(elem)) {
    if (flags & FLAG_LF)
      fputs("\n", out);
    printtype(out, level+1, flags, elem, units);
    flags |= FLAG_LF;
  }
  if (elem->description) {
    if (flags & FLAG_LF)
      fputs("\n", out);
    fputs(elem->meta.description, out);
    fputs("\n", out);
  }
  if (hasattributes(elem)) {
    struct cfgelem *ce = firstelem(elem->attributes);
    while (ce) {
      if (!isnop(ce))
        printattr(out, level, flags, ce, units);
      ce = nextelem(elem->attributes, ce);
    }
  }
  if (isgroup(elem)) {
    struct cfgelem *ce = firstelem(elem->children);
    while (ce) {
      printelem(out, level+1, flags, ce, units);
      ce = nextelem(elem->children, ce);
    }
  }
}

static int maketitles(
  struct cfgelem *elem, int attr, const char *path, size_t pathlen)
{
  char *str;
  size_t namelen = 0, len = 0;
  struct cfgelem *ce;
  if (ismoved(elem) || isdeprecated(elem) || elem->meta.title)
    return 0;
  namelen = strlen(name(elem));
  if (!(str = ddsrt_malloc(pathlen + namelen + (attr ? 4 : 2))))
    return -1;
  memcpy(str, path, pathlen);
  len += pathlen;
  if (attr) {
    str[len++] = '[';
    str[len++] = '@';
  } else {
    str[len++] = '/';
  }
  memcpy(str+len, name(elem), namelen);
  len += namelen;
  if (attr) {
    str[len++] = ']';
  }
  str[len] = '\0';
  elem->meta.title = str;
  ce = firstelem(elem->children);
  while (ce) {
    if (maketitles(ce, 0, str, len) == -1)
      return -1;
    ce = nextelem(elem->children, ce);
  }
  ce = firstelem(elem->attributes);
  while (ce) {
    if (maketitles(ce, 1, str, len) == -1)
      return -1;
    ce = nextelem(elem->attributes, ce);
  }
  return 0;
}

static int initrst(struct cfgelem *elem, const struct cfgunit *units)
{
  if (ismoved(elem) || isdeprecated(elem))
    return 0;
  if (makedescription(elem, units, xlatmd) == -1)
    return -1;
  for (struct cfgelem *ce = elem->children; ce && ce->name; ce++) {
    if (initrst(ce, units) == -1)
      return -1;
  }
  for (struct cfgelem *ce = elem->attributes; ce && ce->name; ce++) {
    if (initrst(ce, units) == - 1)
      return -1;
  }
  return 0;
}

int printrst(FILE *out, struct cfgelem *elem, const struct cfgunit *units)
{
  if (initrst(elem, units) == -1)
    return -1;
  if (maketitles(elem, 0, "/", 1) == -1)
    return -1;
  fputs(
    ".. _`configuration_reference`:\n\n"
    "****************************\n"
    "Configuration File Reference\n"
    "****************************\n\n"
    "Below is the full (generated) reference of XML you can use to configure |var-project|. "
    "The title of each section is the XML XPath notation to the relevant option.\n\n", out);
  printelem(out, 0u, 0u, elem, units);
  return 0;
}
