/*
**  OSSP cfg - Configuration Parsing
**  Copyright (c) 2002-2003 Ralf S. Engelschall <rse@engelschall.com>
**  Copyright (c) 2002-2003 The OSSP Project (http://www.ossp.org/)
**  Copyright (c) 2002-2003 Cable & Wireless Deutschland (http://www.cw.com/de/)
**
**  This file is part of OSSP cfg, a configuration parsing
**  library which can be found at http://www.ossp.org/pkg/lib/cfg/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  cfg_node.c: configuration nodes
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <limits.h>

#include "cfg_global.h"
#include "cfg_main.h"
#include "cfg_node.h"
#include "cfg_fmt.h"

/* create a configuration node */
cfg_rc_t
cfg_node_create(
    cfg_t *cfg,
    cfg_node_t **node)
{
    cfg_node_t *n;
    cfg_rc_t rc;

    /* argument sanity checking */
    if (node == NULL)
        return CFG_ERR_ARG;

    /* allocate memory */
    if ((rc = cfg_grid_alloc(cfg->grid_nodes, (void **)&n)) != CFG_OK)
        return rc;

    /* initialize node attributes */
    n->parent   = NULL;
    n->rbroth   = NULL;
    n->child1   = NULL;
    n->type     = CFG_NODE_TYPE_ARG;
    n->token    = NULL;
    cfg_data_init(&n->data);
    n->srcname  = NULL;
    n->srcpos   = 0;
    *node = n;

    return CFG_OK;
}

/* destroy a configuration node */
cfg_rc_t
cfg_node_destroy(
    cfg_t *cfg,
    cfg_node_t *node)
{
    /* argument sanity checking */
    if (node == NULL)
        return CFG_ERR_ARG;

    /* destroy memory */
    if (node->token != NULL)
        free(node->token);
    if (node->srcname != NULL)
        free(node->srcname);
    cfg_grid_free(cfg->grid_nodes, node);

    return CFG_OK;
}

/* clone a node */
cfg_rc_t
cfg_node_clone(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_t **node2)
{
    cfg_node_t *n;
    cfg_rc_t rc;

    /* argument sanity checking */
    if (node == NULL || node2 == NULL)
        return CFG_ERR_ARG;

    /* allocate memory for new node */
    if ((rc = cfg_grid_alloc(cfg->grid_nodes, (void **)&n)) != CFG_OK)
        return rc;

    /* clone node attributes */
    n->parent   = node->parent;
    n->rbroth   = node->rbroth;
    n->child1   = node->child1;
    n->type     = node->type;
    n->token    = (node->token != NULL ? strdup(node->token) : NULL);
    cfg_data_copy(&node->data, &n->data);
    n->srcname  = (node->srcname != NULL ? strdup(node->srcname) : NULL);
    n->srcpos   = node->srcpos;
    *node2 = n;

    return CFG_OK;
}

/* set a node attribute */
cfg_rc_t
cfg_node_set(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_attr_t attr,
    ...)
{
    va_list ap;

    /* argument sanity checking */
    if (node == NULL)
        return CFG_ERR_ARG;

    /* dispatch into individual attribute handling */
    va_start(ap, attr);
    switch (attr) {
        case CFG_NODE_ATTR_PARENT: {
            node->parent = (cfg_node_t *)va_arg(ap, cfg_node_t *);
            break;
        }
        case CFG_NODE_ATTR_LBROTH: {
            return CFG_ERR_USE; /* cannot be set, only get */
        }
        case CFG_NODE_ATTR_RBROTH: {
            node->rbroth = (cfg_node_t *)va_arg(ap, cfg_node_t *);
            break;
        }
        case CFG_NODE_ATTR_CHILD1: {
            node->child1 = (cfg_node_t *)va_arg(ap, cfg_node_t *);
            break;
        }
        case CFG_NODE_ATTR_CHILDL: {
            return CFG_ERR_USE; /* cannot be set, only get */
        }
        case CFG_NODE_ATTR_CHILDS: {
            return CFG_ERR_USE; /* cannot be set, only get */
        }
        case CFG_NODE_ATTR_NODES: {
            return CFG_ERR_USE; /* cannot be set, only get */
        }
        case CFG_NODE_ATTR_DEPTH: {
            return CFG_ERR_USE; /* cannot be set, only get */
        }
        case CFG_NODE_ATTR_TYPE: {
            node->type = (cfg_node_type_t)va_arg(ap, cfg_node_type_t);
            break;
        }
        case CFG_NODE_ATTR_TOKEN: {
            if (node->token != NULL)
                free(node->token);
            node->token = (char *)va_arg(ap, char *);
            if (node->token != NULL)
                node->token = strdup(node->token);
            break;
        }
        case CFG_NODE_ATTR_DATA: {
            return CFG_ERR_USE;
        }
        case CFG_NODE_ATTR_SRCNAME: {
            if (node->srcname != NULL)
                free(node->srcname);
            node->srcname = (char *)va_arg(ap, char *);
            if (node->srcname != NULL)
                node->srcname = strdup(node->srcname);
            break;
        }
        case CFG_NODE_ATTR_SRCPOS: {
            node->srcpos = (int)va_arg(ap, int);
            break;
        }
        default:
            return CFG_ERR_ARG;
    }
    va_end(ap);

    return CFG_OK;
}

/* INTERNAL: calculate number of nodes */
static int
cfg_node_get_nodes(
    cfg_node_t *node)
{
    int n;

    n = 0;
    if (node != NULL) {
        n++;
        if ((node = node->child1) != NULL) {
            n += cfg_node_get_nodes(node);
            if ((node = node->rbroth) != NULL)
                n += cfg_node_get_nodes(node);
        }
    }
    return n;
}

cfg_rc_t
cfg_node_get(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_attr_t attr,
    ...)
{
    va_list ap;

    /* argument sanity checking */
    if (node == NULL)
        return CFG_ERR_ARG;

    /* dispatch into individual attribute handling */
    va_start(ap, attr);
    switch (attr) {
        case CFG_NODE_ATTR_PARENT: {
            cfg_node_t **n = (cfg_node_t **)va_arg(ap, void *);
            if (n == NULL)
                return CFG_ERR_ARG;
            *n = node->parent;
            break;
        }
        case CFG_NODE_ATTR_LBROTH: {
            cfg_node_t **np = (cfg_node_t **)va_arg(ap, void *);
            cfg_node_t *n;
            if (np == NULL)
                return CFG_ERR_ARG;
            *np = NULL;
            if ((n = node->parent) != NULL) {
                if ((n = n->child1) != NULL) {
                    while (n->rbroth != node && n->rbroth != NULL)
                        n = n->rbroth;
                    if (n->rbroth == node)
                        *np = n;
                }
            }
            break;
        }
        case CFG_NODE_ATTR_RBROTH: {
            cfg_node_t **n = (cfg_node_t **)va_arg(ap, void *);
            if (n == NULL)
                return CFG_ERR_ARG;
            *n = node->rbroth;
            break;
        }
        case CFG_NODE_ATTR_CHILD1: {
            cfg_node_t **n = (cfg_node_t **)va_arg(ap, void *);
            if (n == NULL)
                return CFG_ERR_ARG;
            *n = node->child1;
            break;
        }
        case CFG_NODE_ATTR_CHILDL: {
            cfg_node_t **n = (cfg_node_t **)va_arg(ap, void *);
            if (n == NULL)
                return CFG_ERR_ARG;
            if ((*n = node->child1) != NULL)
                while ((*n)->rbroth != NULL)
                    *n = (*n)->rbroth;
            break;
        }
        case CFG_NODE_ATTR_CHILDS: {
            int *c = (int *)va_arg(ap, int *);
            cfg_node_t *n;
            if (c == NULL)
                return CFG_ERR_ARG;
            *c = 0;
            if ((n = node->child1) != NULL) {
                (*c)++;
                while (n->rbroth != NULL) {
                    n = n->rbroth;
                    (*c)++;
                }
            }
            break;
        }
        case CFG_NODE_ATTR_NODES: {
            int *k = (int *)va_arg(ap, int *);
            if (k == NULL)
                return CFG_ERR_ARG;
            *k = cfg_node_get_nodes(node);
            break;
        }
        case CFG_NODE_ATTR_DEPTH: {
            int *k = (int *)va_arg(ap, int *);
            cfg_node_t *n;
            if (k == NULL)
                return CFG_ERR_ARG;
            *k = 0;
            n = node;
            while ((n = n->parent) != NULL)
                (*k)++;
            break;
        }
        case CFG_NODE_ATTR_TYPE: {
            cfg_node_type_t *type = (cfg_node_type_t *)va_arg(ap, void *);
            if (type == NULL)
                return CFG_ERR_ARG;
            *type = node->type;
            break;
        }
        case CFG_NODE_ATTR_TOKEN: {
            char **token = (char **)va_arg(ap, char **);
            if (token == NULL)
                return CFG_ERR_ARG;
            *token = node->token;
            break;
        }
        case CFG_NODE_ATTR_DATA: {
            cfg_data_t **data = (cfg_data_t **)va_arg(ap, void *);
            if (data == NULL)
                return CFG_ERR_ARG;
            *data = &(node->data);
            break;
        }
        case CFG_NODE_ATTR_SRCNAME: {
            char **name = (char **)va_arg(ap, char **);
            if (name == NULL)
                return CFG_ERR_ARG;
            *name = node->srcname;
            break;
        }
        case CFG_NODE_ATTR_SRCPOS: {
            int *pos = (int *)va_arg(ap, int *);
            if (pos == NULL)
                return CFG_ERR_ARG;
            *pos = node->srcpos;
            break;
        }
        default:
            return CFG_ERR_ARG;
    }
    va_end(ap);

    return CFG_OK;
}

/* return root node */
cfg_rc_t
cfg_node_root(
    cfg_t *cfg,
    cfg_node_t **node)
{
    /* argument sanity checking */
    if (cfg == NULL || node == NULL)
        return CFG_ERR_ARG;

    /* just return the root pointer */
    *node = cfg->root;

    return CFG_OK;
}

/* forward declarations for internal stepping functions */
static cfg_rc_t
cfg_node_select_step1(
    cfg_t *, cfg_node_t *, cfg_node_t ***, long *, const char *);
static cfg_rc_t
cfg_node_select_step2(
    cfg_t *, cfg_node_t *, cfg_node_t ***, long *, const char *, const char *, size_t, long, long, long *);

/* INTERNAL: selection stepping function (part 3, matching node processing) */
static cfg_rc_t
cfg_node_select_step3(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_t ***result_vec, long *result_len,
    const char *spec,
    const char *cpSel, size_t nSel,
    long nFilMin, long nFilMax,
    long *nFound)
{
    cfg_rc_t rc;

    fprintf(stderr, "      step3: (1) cpSel=\"%s\", nSel=%d, spec=\"%s\", nFilMin=%ld, nFilMax=%ld, nFound=%ld\n",
            cpSel, nSel, spec, nFilMin, nFilMax, *nFound);

    if (spec[0] == '\0') {
        /* end of selection, node found */
        (*nFound)++;
        fprintf(stderr, "      step3: (2) found node!!\n");
        fprintf(stderr, "      step3:     current node=0x%lx type=%s token=\"%s\"\n",
                (unsigned long)node,
                (node != NULL ? (node->type == CFG_NODE_TYPE_SEQ ? "SEQ" : (node->type == CFG_NODE_TYPE_DIR ? "DIR" : "ARG")) : "<NULL>/.."),
                (node != NULL ? (node->token != NULL ? node->token : "<NULL>") : "<NULL>/.."));
        if (nFilMin <= *nFound && *nFound <= nFilMax) {
            if (result_len != NULL) {
                (*result_len)++;
                if (result_vec != NULL) {
                    if ((*result_vec = (cfg_node_t **)realloc(*result_vec, sizeof(cfg_node_t *)*((*result_len)+1))) == NULL)
                        return CFG_ERR_SYS;
                    (*result_vec)[(*result_len)-1] = node;
                    (*result_vec)[(*result_len)] = NULL;
                }
            }
        }
        rc = CFG_OK;
    }
    else {
        /* start over with next step */
        rc = cfg_node_select_step1(cfg, node, result_vec, result_len, spec);
    }
    return rc;
}

/* INTERNAL: selection stepping function (part 2, node matching) */
static cfg_rc_t
cfg_node_select_step2(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_t ***result_vec, long *result_len,
    const char *spec,
    const char *cpSel, size_t nSel,
    long nFilMin, long nFilMax,
    long *nFound)
{
    cfg_rc_t rc;
    char *token;
    cfg_node_t *node2;
    cfg_node_type_t type;

    fprintf(stderr, "    step2: (1) cpSel=\"%.*s\", nSel=%d, spec=\"%s\", nFilMin=%ld, nFilMax=%ld, nFound=%ld\n",
            nSel, cpSel, nSel, spec, nFilMin, nFilMax, *nFound);
    fprintf(stderr, "    step2:     current node=0x%lx type=%s token=\"%s\"\n",
            (unsigned long)node,
            (node != NULL ? (node->type == CFG_NODE_TYPE_SEQ ? "SEQ" : (node->type == CFG_NODE_TYPE_DIR ? "DIR" : "ARG")) : "<NULL>/.."),
            (node != NULL ? (node->token != NULL ? node->token : "<NULL>") : "<NULL>/.."));

    rc = CFG_OK;
    if (nSel == 1 && strncmp(cpSel, ".", nSel) == 0) {
        /* current node (no-op) */
        fprintf(stderr, "    step2: search current node, starting at node 0x%lx\n", (unsigned long)node);
        rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound);
    }
    else if (nSel == 2 && strncmp(cpSel, "..", nSel) == 0) {
        /* parent node */
        fprintf(stderr, "    step2: search parent node, starting at node 0x%lx\n", (unsigned long)node);
        if (cfg_node_get(cfg, node, CFG_NODE_ATTR_PARENT, &node) == CFG_OK && node != NULL)
            rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound);
    }
    else if (nSel == 4 && strncmp(cpSel, "....", nSel) == 0) {
        /* anchestor nodes */
        fprintf(stderr, "    step2: search ancestor nodes, starting at node 0x%lx\n", (unsigned long)node);
        while (cfg_node_get(cfg, node, CFG_NODE_ATTR_PARENT, &node) == CFG_OK && node != NULL)
            if ((rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound)) != CFG_OK)
                break;
    }
    else if (nSel == 1 && strncmp(cpSel, "-", nSel) == 0) {
        /* previous sibling node */
        fprintf(stderr, "    step2: search previous sibling node, starting at node 0x%lx\n", (unsigned long)node);
        if ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_LBROTH, &node)) == CFG_OK && node != NULL)
            rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound);
    }
    else if (nSel == 2 && strncmp(cpSel, "--", nSel) == 0) {
        /* preceeding sibling nodes */
        fprintf(stderr, "    step2: search preceeding sibling nodes, starting at node 0x%lx\n", (unsigned long)node);
        while (cfg_node_get(cfg, node, CFG_NODE_ATTR_LBROTH, &node) == CFG_OK && node != NULL)
            if ((rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound)) != CFG_OK)
                break;
    }
    else if (nSel == 1 && strncmp(cpSel, "+", nSel) == 0) {
        /* next sibling node */
        fprintf(stderr, "    step2: search next sibling node, starting at node 0x%lx\n", (unsigned long)node);
        if ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_RBROTH, &node)) == CFG_OK && node != NULL)
            rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound);
    }
    else if (nSel == 2 && strncmp(cpSel, "++", nSel) == 0) {
        /* following sibling nodes */
        fprintf(stderr, "    step2: search following sibling nodes, starting at node 0x%lx\n", (unsigned long)node);
        while (cfg_node_get(cfg, node, CFG_NODE_ATTR_RBROTH, &node) == CFG_OK && node != NULL)
            if ((rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound)) != CFG_OK)
                break;
    }
    else if (nSel == 0 /* "//" */) {
        /* descendant nodes */
        fprintf(stderr, "    step2: search descendant nodes, starting at node 0x%lx\n", (unsigned long)node);
#if 0 /* FIXME */
        if ((rc = cfg_node_apply(cfg, node, NULL, NULL, cfg_node_select_step2_descendant, &descendant_ctx)
            cfg_rc_t cfg_node_select_step2_descendant(cfg_t *cfg, cfg_node_t *node, void *_ctx));
#endif
        if ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_CHILD1, &node)) == CFG_OK && node != NULL) {
            do {
                if ((rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound)) != CFG_OK)
                    return rc;
                if ((rc = cfg_node_select_step2(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound)) != CFG_OK)
                    return rc;
            } while ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_RBROTH, &node)) == CFG_OK && node != NULL);
        }
        return CFG_OK;
    }
    else {
        /* child node */
        fprintf(stderr, "    step2: search child nodes, starting at node 0x%lx\n", (unsigned long)node);
        if ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_CHILD1, &node)) == CFG_OK && node != NULL) {
            do {
                if ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_TOKEN, &token)) != CFG_OK)
                    continue;
                if ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_TYPE, &type)) == CFG_OK && type == CFG_NODE_TYPE_DIR) {
                    if ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_CHILD1, &node2)) != CFG_OK || node2 == NULL)
                        continue;
                    if ((rc = cfg_node_get(cfg, node2, CFG_NODE_ATTR_TOKEN, &token)) != CFG_OK)
                        continue;
                }
                if (token != NULL) {
                    size_t l = strlen(token);
                    fprintf(stderr, "    step2: compare child node: token \"%s\"\n", token);
                    if (   (l == 1 && nSel == 1 && token[0] == '*')
                        || (l == nSel && strncmp(token, cpSel, nSel) == 0)) {
                        if ((rc = cfg_node_select_step3(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, nFound)) != CFG_OK)
                            return rc;
                    }
                }
            } while ((rc = cfg_node_get(cfg, node, CFG_NODE_ATTR_RBROTH, &node)) == CFG_OK && node != NULL);
        }
        return CFG_OK;
    }
    return rc;
}

/* INTERNAL: selection stepping function (part 1, selection parsing) */
static cfg_rc_t
cfg_node_select_step1(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_t ***result_vec, long *result_len,
    const char *spec)
{
    const char *cpSel; size_t nSel;
    long nFilMin, nFilMax;
    long n;
    const char *cp;
    char *cp2;
    cfg_rc_t rc;
    long nFound;

    fprintf(stderr, "  step1: (0) spec=\"%s\"\n", spec);

    /* stop processing if spec is empty */
    if (spec[0] == '\0')
        return CFG_OK;

    /* determine selection step information */
    cpSel = spec;
    nSel = strcspn(cpSel, "[/");
    cp = cpSel+nSel;
    nFilMin = 1;
    nFilMax = LONG_MAX;
    if (*cp == '[') {
        cp++;
        n = strtol(cp, &cp2, 10);
        if (cp2 > cp && n != 0)
            nFilMin = n;
        cp = cp2;
        if (*cp == ',') {
            cp++;
            n = strtol(cp, &cp2, 10);
            if (cp2 > cp && n != 0)
                nFilMax = n;
            cp = cp2;
        }
        else
            nFilMax = nFilMin;
        if (*cp != ']') {
            cfg_error_info(cfg, CFG_ERR_ARG, "invalid selection specification filter range");
            return CFG_ERR_ARG;
        }
        cp++;
    }
    if (*cp == '/')
        cp++;
    spec = cp;

    fprintf(stderr, "  step1: (1) cpSel=\"%s\", nSel=%d, spec=\"%s\", nFilMin=%ld, nFilMax=%ld\n",
            cpSel, nSel, spec, nFilMin, nFilMax);

    /* perform pre-selection if filter range is relative to last element */
    if (nFilMin < 0 || nFilMax < 0) {
        nFound = 0;
        fprintf(stderr, "  step1: pre-selection start\n");
        if ((rc = cfg_node_select_step2(cfg, node, NULL, NULL, spec, cpSel, nSel, 1, LONG_MAX, &nFound)) != CFG_OK)
            return rc;
        fprintf(stderr, "  step1: (1b) cpSel=\"%s\", nSel=%d, spec=\"%s\", nFilMin=%ld, nFilMax=%ld nFound=%ld\n", 
                cpSel, nSel, spec, nFilMin, nFilMax, nFound);
        fprintf(stderr, "  step1: pre-selection end\n");
        if (nFound == 0)
            return CFG_OK;
        if (nFilMin < 0) {
            nFilMin = nFound + (nFilMin+1);
            if (nFilMin < 1)
                nFilMin = 1;
        }
        if (nFilMax < 0) {
            nFilMax = nFound + (nFilMax+1);
            if (nFilMax < 1)
                nFilMax = 1;
        }
    }

    fprintf(stderr, "  step1: (2) cpSel=\"%s\", nSel=%d, spec=\"%s\", nFilMin=%ld, nFilMax=%ld\n",
            cpSel, nSel, spec, nFilMin, nFilMax);

    /* perform real selection */
    nFound = 0;
    if ((rc = cfg_node_select_step2(cfg, node, result_vec, result_len, spec, cpSel, nSel, nFilMin, nFilMax, &nFound)) != CFG_OK)
        return rc;

    fprintf(stderr, "  step1: (3) cpSel=\"%s\", nSel=%d, spec=\"%s\", nFilMin=%ld, nFilMax=%ld nFound=%ld\n",
            cpSel, nSel, spec, nFilMin, nFilMax, nFound);

    return CFG_OK;
}

/* select a node */
cfg_rc_t
cfg_node_select(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_t ***result_vec,
    const char *fmt,
    ...)
{
    cfg_rc_t rc;
    va_list ap;
    char *spec;
    char *cp;
    long result_len;

    /* argument sanity checking */
    if (cfg == NULL || result_vec == NULL || fmt == NULL)
        return CFG_ERR_ARG;

    /* on-the-fly create or just take over specification string */
    va_start(ap, fmt);
    spec = cfg_fmt_vasprintf(fmt, ap);
    va_end(ap);
    if (spec == NULL)
        return CFG_ERR_FMT;
    fprintf(stderr, "select: (1) spec=\"%s\"\n", spec);

    /* special cases for absolute (start from root-node) selection */
    cp = spec;
    if (cp[0] == '/') {
        /* <any,"/x"> is same as <root,"x"> */
        if (node != NULL)
            return CFG_ERR_USE;
        node = cfg->root;
        cp++;
    }
    else if (node == NULL) {
        /* <NULL, "..."> is same as <root, "..."> */
        node = cfg->root;
    }
    fprintf(stderr, "select: (2) spec=\"%s\"\n", cp);

    /* initialize result node array */
    result_len = 0;
    if ((*result_vec = (cfg_node_t **)malloc(sizeof(cfg_node_t *)*(result_len+1))) == NULL)
        return CFG_ERR_SYS;
    (*result_vec)[result_len] = NULL;

    /* perform the selection stepping */
    if ((rc = cfg_node_select_step1(cfg, node, result_vec, &result_len, cp)) != CFG_OK) {
        free(*result_vec);
        return rc;
    }
    fprintf(stderr, "select: (3) result_len=%ld\n", result_len);

    return CFG_OK;
}

/* return next matching node (iteration) */
cfg_rc_t
cfg_node_find(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_rc_t (*cb_fct_cmp)(cfg_t *, cfg_node_t *, void *),
    void *cb_ctx_cmp,
    cfg_node_t **cont)
{
    /* FIXME */
    return CFG_ERR_INT;
}

/* apply for each matching node (recursion) */
cfg_rc_t
cfg_node_apply(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_rc_t (*cb_fct_cmp)(cfg_t *, cfg_node_t *, void *),
    void *cb_ctx_cmp,
    cfg_rc_t (*cb_fct_cb)(cfg_t *, cfg_node_t *, void *),
    void *cb_ctx_cb)
{
    cfg_rc_t rc;

    /* argument sanity checking */
    if (cfg == NULL)
        return CFG_ERR_ARG;
    if (node != NULL) {
        if ((rc = cb_fct_cmp(cfg, node, cb_ctx_cmp)) == CFG_OK)
            cb_fct_cb(cfg, node, cb_ctx_cb);
        if (rc != CFG_ERR_NDE)
            return rc;
        if (node->child1 != NULL)
            if ((rc = cfg_node_apply(cfg, node->child1,
                                     cb_fct_cmp, cb_ctx_cmp,
                                     cb_fct_cb, cb_ctx_cb)) != CFG_OK)
                return rc;
        if (node->rbroth != NULL)
            if ((rc = cfg_node_apply(cfg, node->rbroth,
                                     cb_fct_cmp, cb_ctx_cmp,
                                     cb_fct_cb, cb_ctx_cb)) != CFG_OK)
                return rc;
    }
    return CFG_OK;
}

/* compare two nodes */
cfg_rc_t
cfg_node_cmp(
    cfg_t *cfg,
    cfg_node_t *node,
    void *token)
{
    /* argument sanity checking */
    if (cfg == NULL || node == NULL || token == NULL)
        return CFG_ERR_NDE;
    if (node->token == NULL && token == NULL)
        return CFG_OK;
    if (node->token == NULL || token == NULL)
        return CFG_ERR_NDE;
    if (strcmp(node->token, (char *)token) == 0)
        return CFG_OK;
    return CFG_ERR_NDE;
}

/* link to nodes together */
cfg_rc_t
cfg_node_link(
    cfg_t *cfg,
    cfg_node_t *node,
    cfg_node_attr_t attr,
    cfg_node_t *node2)
{
    cfg_node_t *n;

    /* argument sanity checking */
    if (node == NULL || node2 == NULL)
        return CFG_ERR_ARG;
    if (attr == CFG_NODE_ATTR_RBROTH) {
        /* make node a rbroth */
        n = node2;
        n->parent = node->parent;
        while (n->rbroth != NULL) {
            n->parent = node->parent;
            n = n->rbroth;
        }
        n->rbroth = node->rbroth;
        node->rbroth = node2;
    }
    else if (attr == CFG_NODE_ATTR_CHILD1) {
        /* make node a child1 */
        n = node2;
        n->parent = node;
        while (n->rbroth != NULL) {
            n->parent = node;
            n = n->rbroth;
        }
        n->rbroth = node->child1;
        node->child1 = node2;
    }
    /* FIXME more linkage possibilities */
    else
        return CFG_ERR_ARG;
    return CFG_OK;
}

/* unlink a nodes from others */
cfg_rc_t
cfg_node_unlink(
    cfg_t *cfg,
    cfg_node_t *node)
{
    cfg_node_t *n;

    /* argument sanity checking */
    if (node == NULL)
        return CFG_ERR_ARG;

    if (node->parent == NULL)
        return CFG_OK;
    if (node->parent->child1 == node) {
        /* node was a child1 */
        node->parent->child1 = node->rbroth;
    }
    else {
        /* node was a rbroth */
        n = node->parent->child1;
        while (n->rbroth != node)
            n = n->rbroth;
        n->rbroth = node->rbroth;
    }
    return CFG_OK;
}

