/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * VgaGames 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "nodelink.h"

void init_nodelink(void);
void dest_nodelink(void);

struct nodelink_val {
  int len;
  int stack;
};

static struct VG_Nodelink * nodelink_create(void);
static void nodelink_destroy(struct VG_Nodelink *);
static void nodelink_destroyall(void);
static void nodelink_clear(struct VG_Nodelink *);
static VG_BOOL nodelink_add(struct VG_Nodelink *, const char *, const char *, int, VG_BOOL);
static void nodelink_del(struct VG_Nodelink *, const char *, const char *, VG_BOOL);
static VG_BOOL nodelink_islink(struct VG_Nodelink *, const char *, const char *, VG_BOOL);
static int nodelink_get(struct VG_Nodelink *, const char *, const char ***, VG_BOOL);
static void nodelink_deactivate(struct VG_Nodelink *, const char *, const char *, VG_BOOL);
static void nodelink_activate(struct VG_Nodelink *, const char *, const char *, VG_BOOL);
static void nodelink_reactivate(struct VG_Nodelink *);
static int nodelink_moveto(struct VG_Nodelink *, const char *, const char *, const char ***, VG_BOOL);
static void nodelink_dump(FILE *);


/* set functions */
void
init_nodelink(void)
{
  vg4data.lists.nodelink.list = SML3_calloc(1, sizeof(*vg4data.lists.nodelink.list));  /* first list-element is unused */
  vg4data.lists.nodelink.list->prev = vg4data.lists.nodelink.list->next = NULL;
  vg4->nodelink->create = nodelink_create;
  vg4->nodelink->destroy = nodelink_destroy;
  vg4->nodelink->destroyall = nodelink_destroyall;
  vg4->nodelink->clear = nodelink_clear;
  vg4->nodelink->add = nodelink_add;
  vg4->nodelink->del = nodelink_del;
  vg4->nodelink->islink = nodelink_islink;
  vg4->nodelink->get = nodelink_get;
  vg4->nodelink->deactivate = nodelink_deactivate;
  vg4->nodelink->activate = nodelink_activate;
  vg4->nodelink->reactivate = nodelink_reactivate;
  vg4->nodelink->moveto = nodelink_moveto;
  vg4->nodelink->dump = nodelink_dump;
} /* Ende init_nodelink */


void
dest_nodelink(void)
{
  vg4->nodelink->destroyall();
  free(vg4data.lists.nodelink.list);  /* first list-element is unused */
  vg4data.lists.nodelink.list = NULL;
} /* Ende dest_nodelink */


/* nodelink_create:
 * create a nodelink
 * @return  nodelink
 */
static struct VG_Nodelink *
nodelink_create(void)
{
  struct VG_Nodelink *ndlnk, **ndlnkp;

  ndlnk = SML3_calloc(1, sizeof(*ndlnk));
  ndlnk->prev = ndlnk->next = NULL;
  ndlnk->ndle.hs = SML3_hashnew(NULL, 0);

  for (ndlnkp = &vg4data.lists.nodelink.list; *ndlnkp != NULL; ndlnkp = &(*ndlnkp)->next) { ndlnk->prev = *ndlnkp; }
  *ndlnkp = ndlnk;

  return ndlnk;
} /* Ende nodelink_create */


/* nodelink_destroy:
 * destroy a nodelink
 * @param ndlnk  nodelink
 */
static void
nodelink_destroy(struct VG_Nodelink *ndlnk)
{
  if (ndlnk == NULL) { return; }

  SML3_hashfree(&ndlnk->ndle.hs);

  if (ndlnk->prev != NULL) { ndlnk->prev->next = ndlnk->next; }
  if (ndlnk->next != NULL) { ndlnk->next->prev = ndlnk->prev; }

  free(ndlnk);
} /* Ende nodelink_destroy */


/* nodelink_destroyall:
 * destroy all nodelinks
 */
static void
nodelink_destroyall(void)
{
  struct VG_Nodelink *ndlnk0, *ndlnk1;

  if (vg4data.lists.nodelink.list == NULL) { return; }

  for (ndlnk0 = vg4data.lists.nodelink.list->next; ndlnk0 != NULL; ndlnk0 = ndlnk1) {
    if (ndlnk0->prev != NULL) { ndlnk0->prev->next = ndlnk0->next; }
    if (ndlnk0->next != NULL) { ndlnk0->next->prev = ndlnk0->prev; }
    ndlnk1 = ndlnk0->next;
    ndlnk0->prev = ndlnk0->next = NULL;
    vg4->nodelink->destroy(ndlnk0);
  }
  vg4data.lists.nodelink.list->prev = vg4data.lists.nodelink.list->next = NULL;
} /* Ende nodelink_destroyall */


/* nodelink_clear:
 * clear a nodelink
 * @param ndlnk  nodelink
 */
static void
nodelink_clear(struct VG_Nodelink *ndlnk)
{
  if (ndlnk == NULL) { return; }
  SML3_hashclear(ndlnk->ndle.hs);
} /* Ende nodelink_clear */


/* nodelink_add:
 * add a link between two nodes, if not already linked
 * @param ndlnk     nodelink
 * @param nodeid1   node to link from
 * @param nodeid2   node to link to
 * @param len       distance between the two nodes
 * @param bidirect  whether also link from nodeid2 to nodeid1, if not already linked
 * @return  VG_TRUE = OK or VG_FALSE = not or just partially linked
 */
static VG_BOOL
nodelink_add(struct VG_Nodelink *ndlnk, const char *nodeid1, const char *nodeid2, int len, VG_BOOL bidirect)
{
  struct SML3_hash *hs1;
  struct SML3_hashelem *he1;
  struct nodelink_val ndval;

  if (ndlnk == NULL) { return VG_FALSE; }
  if (nodeid1 == NULL || *nodeid1 == '\0') { return VG_FALSE; }
  if (nodeid2 == NULL || *nodeid2 == '\0') { return VG_FALSE; }
  if (strcmp(nodeid1, nodeid2) == 0) { return VG_FALSE; }
  if (len < 1) { return VG_FALSE; }

  ndval.len = len;
  ndval.stack = 0;

  /* hs[nodeid1][nodeid2] = ndval */
  hs1 = SML3_hashsubset(ndlnk->ndle.hs, nodeid1, strlen(nodeid1) + 1);
  he1 = SML3_hashget(hs1, nodeid2, strlen(nodeid2) + 1);
  if (he1 != NULL) { return VG_FALSE; }
  he1 = SML3_hashset(hs1, nodeid2, strlen(nodeid2) + 1);
  SML3_hashelem_valset(he1, &ndval, sizeof(ndval));

  if (bidirect) {
    /* hs[nodeid2][nodeid1] = ndval */
    hs1 = SML3_hashsubset(ndlnk->ndle.hs, nodeid2, strlen(nodeid2) + 1);
    he1 = SML3_hashget(hs1, nodeid1, strlen(nodeid1) + 1);
    if (he1 != NULL) { return VG_FALSE; }
    he1 = SML3_hashset(hs1, nodeid1, strlen(nodeid1) + 1);
    SML3_hashelem_valset(he1, &ndval, sizeof(ndval));
  }

  return VG_TRUE;
} /* Ende nodelink_add */


/* nodelink_del:
 * delete a link between two nodes, if linked
 * @param ndlnk    nodelink
 * @param nodeid1  node from
 * @param nodeid2  node to
 * @param bidirect  whether also delete link from nodeid2 to nodeid1, if linked
 */
static void
nodelink_del(struct VG_Nodelink *ndlnk, const char *nodeid1, const char *nodeid2, VG_BOOL bidirect)
{
  struct SML3_hash *hs1;
  struct SML3_hashelem *he1;

  if (ndlnk == NULL) { return; }
  if (nodeid1 == NULL || *nodeid1 == '\0') { return; }
  if (nodeid2 == NULL || *nodeid2 == '\0') { return; }
  if (strcmp(nodeid1, nodeid2) == 0) { return; }

  /* hs[nodeid1][nodeid2] */
  hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid1, strlen(nodeid1) + 1);
  if (hs1 != NULL) {
    he1 = SML3_hashget(hs1, nodeid2, strlen(nodeid2) + 1);
    if (he1 != NULL) {
      SML3_hashdel(he1);
      if (SML3_hashlist(hs1, NULL, 0) == NULL) {
        SML3_hashdel(SML3_hashparentelem(hs1));
      }
    }
  }

  if (bidirect) {
    /* hs[nodeid2][nodeid1] */
    hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid2, strlen(nodeid2) + 1);
    if (hs1 != NULL) {
      he1 = SML3_hashget(hs1, nodeid1, strlen(nodeid1) + 1);
      if (he1 != NULL) {
        SML3_hashdel(he1);
        if (SML3_hashlist(hs1, NULL, 0) == NULL) {
          SML3_hashdel(SML3_hashparentelem(hs1));
        }
      }
    }
  }
} /* Ende nodelink_del */


/* nodelink_islink:
 * return whether two nodes are linked
 * @param ndlnk      nodelink
 * @param nodeid1    node from
 * @param nodeid2    node to
 * @param withdeact  whether ignore deactivation of links
 * @return  VG_TRUE = linked or VG_FALSE = not linked
 */
static VG_BOOL
nodelink_islink(struct VG_Nodelink *ndlnk, const char *nodeid1, const char *nodeid2, VG_BOOL withdeact)
{
  struct SML3_hash *hs1;
  struct SML3_hashelem *he1;
  struct nodelink_val *ndvalp;

  if (ndlnk == NULL) { return VG_FALSE; }
  if (nodeid1 == NULL || *nodeid1 == '\0') { return VG_FALSE; }
  if (nodeid2 == NULL || *nodeid2 == '\0') { return VG_FALSE; }
  if (strcmp(nodeid1, nodeid2) == 0) { return VG_FALSE; }

  hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid1, strlen(nodeid1) + 1);
  if (hs1 == NULL) { return VG_FALSE; }
  he1 = SML3_hashget(hs1, nodeid2, strlen(nodeid2) + 1);
  if (he1 == NULL) { return VG_FALSE; }

  ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he1, NULL);
  SML3_assert(ndvalp != NULL, "ndvalp == NULL");
  if (!withdeact && ndvalp->stack > 0) { return VG_FALSE; }

  return VG_TRUE;
} /* Ende nodelink_islink */


/* nodelink_get:
 * get linked nodes of a node
 * @param ndlnk      nodelink
 * @param nodeid     node
 * @param linkids    for returning linked nodes,
 *                   must be freed with free()
 * @param withdeact  whether ignore deactivation of links
 * @return  number of nodes in linkids
 */
static int
nodelink_get(struct VG_Nodelink *ndlnk, const char *nodeid, const char ***linkids, VG_BOOL withdeact)
{
  struct SML3_hash *hs1;
  struct SML3_hashelem *he1;
  struct nodelink_val *ndvalp;
  const char *key;
  int lanz;

  if (linkids != NULL) { *linkids = NULL; }
  if (ndlnk == NULL) { return 0; }
  if (nodeid == NULL || *nodeid == '\0') { return 0; }

  hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid, strlen(nodeid) + 1);
  if (hs1 == NULL) { return 0; }

  lanz = 0;
  for (he1 = SML3_hashlist(hs1, NULL, 0); he1 != NULL; he1 = SML3_hashlist(hs1, he1, 0)) {
    key = (const char *)SML3_hashelem_keyget(he1, NULL);
    ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he1, NULL);
    SML3_assert(ndvalp != NULL, "ndvalp == NULL");
    if (!withdeact && ndvalp->stack > 0) { continue; }

    if (linkids != NULL) {
      if (lanz++ == 0) {
        *linkids = SML3_malloc(sizeof(**linkids) * (lanz + 1));
      } else {
        *linkids = SML3_realloc(*linkids, sizeof(**linkids) * (lanz + 1));
      }
      (*linkids)[lanz - 1] = key;
    } else {
      lanz++;
    }
  }

  if (linkids != NULL && lanz > 0) {
    (*linkids)[lanz] = NULL;
  }

  return lanz;
} /* Ende nodelink_get */


/* nodelink_deactivate:
 * deactivate a link between two nodes, using a stack of deactivation
 * @param ndlnk     nodelink
 * @param nodeid1   node from
 * @param nodeid2   node to
 * @param bidirect  whether also deactivate link from nodeid2 to nodeid1
 */
static void
nodelink_deactivate(struct VG_Nodelink *ndlnk, const char *nodeid1, const char *nodeid2, VG_BOOL bidirect)
{
  struct SML3_hash *hs1;
  struct SML3_hashelem *he1;
  struct nodelink_val *ndvalp;

  if (ndlnk == NULL) { return; }
  if (nodeid1 == NULL || *nodeid1 == '\0') { return; }
  if (nodeid2 == NULL || *nodeid2 == '\0') { return; }
  if (strcmp(nodeid1, nodeid2) == 0) { return; }

  /* hs[nodeid1][nodeid2] */
  hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid1, strlen(nodeid1) + 1);
  if (hs1 != NULL) {
    he1 = SML3_hashget(hs1, nodeid2, strlen(nodeid2) + 1);
    if (he1 != NULL) {
      ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he1, NULL);
      SML3_assert(ndvalp != NULL, "ndvalp == NULL");
      if (++ndvalp->stack < 0) { ndvalp->stack--; }
    }
  }

  if (bidirect) {
    /* hs[nodeid2][nodeid1] */
    hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid2, strlen(nodeid2) + 1);
    if (hs1 != NULL) {
      he1 = SML3_hashget(hs1, nodeid1, strlen(nodeid1) + 1);
      if (he1 != NULL) {
        ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he1, NULL);
        SML3_assert(ndvalp != NULL, "ndvalp == NULL");
        if (++ndvalp->stack < 0) { ndvalp->stack--; }
      }
    }
  }
} /* Ende nodelink_deactivate */


/* nodelink_activate:
 * activate a link between two nodes, using a stack of deactivation
 * @param ndlnk     nodelink
 * @param nodeid1   node from
 * @param nodeid2   node to
 * @param bidirect  whether also activate link from nodeid2 to nodeid1
 */
static void
nodelink_activate(struct VG_Nodelink *ndlnk, const char *nodeid1, const char *nodeid2, VG_BOOL bidirect)
{
  struct SML3_hash *hs1;
  struct SML3_hashelem *he1;
  struct nodelink_val *ndvalp;

  if (ndlnk == NULL) { return; }
  if (nodeid1 == NULL || *nodeid1 == '\0') { return; }
  if (nodeid2 == NULL || *nodeid2 == '\0') { return; }
  if (strcmp(nodeid1, nodeid2) == 0) { return; }

  /* hs[nodeid1][nodeid2] */
  hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid1, strlen(nodeid1) + 1);
  if (hs1 != NULL) {
    he1 = SML3_hashget(hs1, nodeid2, strlen(nodeid2) + 1);
    if (he1 != NULL) {
      ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he1, NULL);
      SML3_assert(ndvalp != NULL, "ndvalp == NULL");
      if (--ndvalp->stack < 0) { ndvalp->stack = 0; }
    }
  }

  if (bidirect) {
    /* hs[nodeid2][nodeid1] */
    hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid2, strlen(nodeid2) + 1);
    if (hs1 != NULL) {
      he1 = SML3_hashget(hs1, nodeid1, strlen(nodeid1) + 1);
      if (he1 != NULL) {
        ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he1, NULL);
        SML3_assert(ndvalp != NULL, "ndvalp == NULL");
        if (--ndvalp->stack < 0) { ndvalp->stack = 0; }
      }
    }
  }
} /* Ende nodelink_activate */


/* nodelink_reactivate:
 * activate all links of all nodes
 * @param ndlnk    nodelink
 */
static void
nodelink_reactivate(struct VG_Nodelink *ndlnk)
{
  struct SML3_hash *hsub;
  struct SML3_hashelem *he1, *he2;
  struct nodelink_val *ndvalp;

  if (ndlnk == NULL) { return; }

  for (he1 = SML3_hashsublist(ndlnk->ndle.hs, NULL); he1 != NULL; he1 = SML3_hashsublist(ndlnk->ndle.hs, he1)) {
    hsub = SML3_hashsubhash(he1);
    for (he2 = SML3_hashlist(hsub, NULL, 0); he2 != NULL; he2 = SML3_hashlist(hsub, he2, 0)) {
      ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he2, NULL);
      SML3_assert(ndvalp != NULL, "ndvalp == NULL");
      ndvalp->stack = 0;
    }
  }
} /* Ende nodelink_reactivate */


/* nodelink_moveto:
 * get nodes to move from a start node up to a destination node
 * @param ndlnk      nodelink
 * @param nodeid1    start node
 * @param nodeid2    destination node
 * @param moveids    for returning nodes to pass to destination node (excluding start node),
 *                   must be freed with free()
 * @param withdeact  whether ignore deactivation of links
 * @return  number of nodes in moveids
 */
static int
nodelink_moveto(struct VG_Nodelink *ndlnk, const char *nodeid1, const char *nodeid2, const char ***moveids, VG_BOOL withdeact)
{
  /* Dijkstra Algorithmus */
  struct SML3_hash *hs1, *hmov, *hnod;
  struct SML3_hashelem *he1, *he2, *hemin;
  struct nodelink_val *ndvalp;
  const char *key;
  int lanz, imin;
  struct movval {
    int ilen;
    const char *node;
  } mval, *mvalp;

  if (moveids != NULL) { *moveids = NULL; }
  if (ndlnk == NULL) { return 0; }
  if (nodeid1 == NULL || *nodeid1 == '\0') { return 0; }
  if (nodeid2 == NULL || *nodeid2 == '\0') { return 0; }
  if (strcmp(nodeid1, nodeid2) == 0) { return 0; }

  /* get start node */
  hs1 = SML3_hashsubget(ndlnk->ndle.hs, nodeid1, strlen(nodeid1) + 1);
  if (hs1 == NULL) { return 0; }
  key = (const char *)SML3_hashelem_keyget(SML3_hashparentelem(hs1), NULL);

  hmov = SML3_hashnew(NULL, 0);
  hnod = SML3_hashnew(NULL, 0);

  /* set start node into hmov: node=struct-movval */
  he1 = SML3_hashset(hmov, key, strlen(key) + 1);
  mval.ilen = 0;
  mval.node = NULL;
  SML3_hashelem_valset(he1, &mval, sizeof(mval));

  while (SML3_hashlist(hmov, NULL, 0) != NULL) {
    imin = -1;
    hemin = NULL;

    /* search for node with smallest ilen */
    for (he1 = SML3_hashlist(hmov, NULL, 0); he1 != NULL; he1 = SML3_hashlist(hmov, he1, 0)) {
      key = (const char *)SML3_hashelem_keyget(he1, NULL);
      mvalp = (struct movval *)SML3_hashelem_valget(he1, NULL);
      if (imin < 0 || mvalp->ilen < imin) { hemin = he1; imin = mvalp->ilen; }
    }

    /* move node into hnod: node=struct-movval */
    key = (const char *)SML3_hashelem_keyget(hemin, NULL);
    mvalp = (struct movval *)SML3_hashelem_valget(hemin, NULL);
    he1 = SML3_hashset(hnod, key, strlen(key) + 1);
    SML3_hashelem_valset(he1, mvalp, sizeof(*mvalp));
    SML3_hashdel(hemin);

    /* get key and value of node in hnod */
    key = (const char *)SML3_hashelem_keyget(he1, NULL);
    mvalp = (struct movval *)SML3_hashelem_valget(he1, NULL);
    imin = mvalp->ilen;

    if (strcmp(key, nodeid2) == 0) { break; }  /* destination reached */

    /* get linked nodes */
    hs1 = SML3_hashsubget(ndlnk->ndle.hs, key, strlen(key) + 1);
    for (he1 = SML3_hashlist(hs1, NULL, 0); he1 != NULL; he1 = SML3_hashlist(hs1, he1, 0)) {
      key = (const char *)SML3_hashelem_keyget(he1, NULL);
      ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he1, NULL);

      if (!withdeact && ndvalp->stack > 0) { continue; }
      if (SML3_hashget(hnod, key, strlen(key) + 1) != NULL) { continue; }  /* linked node already in hnod */

      /* if linked node missing in hmov or its ilen greater, set it */
      he2 = SML3_hashset(hmov, key, strlen(key) + 1);
      mvalp = (struct movval *)SML3_hashelem_valget(he2, NULL);
      if (mvalp == NULL) {
        mval.ilen = imin + ndvalp->len;
        mval.node = (const char *)SML3_hashelem_keyget(SML3_hashparentelem(hs1), NULL);
        SML3_hashelem_valset(he2, &mval, sizeof(mval));
      } else if (mvalp->ilen > imin + ndvalp->len) {
        mvalp->ilen = imin + ndvalp->len;
        mvalp->node = (const char *)SML3_hashelem_keyget(SML3_hashparentelem(hs1), NULL);
      }
    }
  }

  SML3_hashfree(&hmov);

  /* get destination node in hnod */
  hemin = SML3_hashget(hnod, nodeid2, strlen(nodeid2) + 1);
  if (hemin == NULL) { SML3_hashfree(&hnod); return 0; }

  lanz = 0;
  while (hemin != NULL) {
    key = (const char *)SML3_hashelem_keyget(hemin, NULL);
    if (strcmp(key, nodeid1) == 0) { break; }
    hs1 = SML3_hashsubget(ndlnk->ndle.hs, key, strlen(key) + 1);
    SML3_assert(hs1 != NULL, "hs1 == NULL");
    key = (const char *)SML3_hashelem_keyget(SML3_hashparentelem(hs1), NULL);
    mvalp = (struct movval *)SML3_hashelem_valget(hemin, NULL);
    SML3_assert(mvalp != NULL && mvalp->node != NULL, "mvalp == NULL or mvalp->node == NULL");
    hemin = SML3_hashget(hnod, mvalp->node, strlen(mvalp->node) + 1);

    if (moveids != NULL) {
      if (lanz++ == 0) {
        *moveids = SML3_malloc(sizeof(**moveids) * (lanz + 1));
      } else {
        *moveids = SML3_realloc(*moveids, sizeof(**moveids) * (lanz + 1));
      }
      (*moveids)[lanz - 1] = key;
    } else {
      lanz++;
    }
  }

  if (moveids != NULL && lanz > 0) {
    const char *kptr;
    for (imin = 0; imin < lanz / 2; imin++) {
      kptr = (*moveids)[imin];
      (*moveids)[imin] = (*moveids)[lanz - imin - 1];
      (*moveids)[lanz - imin - 1] = kptr;
    }
    (*moveids)[lanz] = NULL;
  }

  SML3_hashfree(&hnod);

  return lanz;
} /* Ende nodelink_moveto */


/* nodelink_dump:
 * dump nodelink entries
 * @param outfp  filepointer to dump to, or NULL = stdout
 */
static void
nodelink_dump(FILE *outfp)
{
  struct VG_Nodelink *ndlnk;
  struct SML3_hash *hsub;
  struct SML3_hashelem *he1, *he2;
  struct nodelink_val *ndvalp;
  const char *key;
  int inr;

  if (outfp == NULL) { outfp = stdout; }

  fprintf(outfp, "\nDump of nodelink entries\n");
  fprintf(outfp, "========================\n\n");

  if (vg4data.lists.nodelink.list == NULL) { return; }

  inr = 0;
  for (ndlnk = vg4data.lists.nodelink.list->next; ndlnk != NULL; ndlnk = ndlnk->next) {
    fprintf(outfp, "- %d\n", ++inr);
    for (he1 = SML3_hashsublist(ndlnk->ndle.hs, NULL); he1 != NULL; he1 = SML3_hashsublist(ndlnk->ndle.hs, he1)) {
      key = (const char *)SML3_hashelem_keyget(he1, NULL);
      fprintf(outfp, "  %s: ", key);
      hsub = SML3_hashsubhash(he1);
      for (he2 = SML3_hashlist(hsub, NULL, 0); he2 != NULL; he2 = SML3_hashlist(hsub, he2, 0)) {
        key = (const char *)SML3_hashelem_keyget(he2, NULL);
        ndvalp = (struct nodelink_val *)SML3_hashelem_valget(he2, NULL);
        SML3_assert(ndvalp != NULL, "ndvalp == NULL");
        fprintf(outfp, "%s[len=%d,deact=%d] ", key, ndvalp->len, ndvalp->stack);
      }
      fprintf(outfp, "\n");
    }
    fprintf(outfp, "\n");
  }
} /* Ende nodelink_dump */
