/*
 *
  ***** BEGIN LICENSE BLOCK *****
 
  Copyright (C) 2021 Rubicon Communications, LLC(Netgate)

  This file is part of CLIXON.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  Alternatively, the contents of this file may be used under the terms of
  the GNU General Public License Version 3 or later (the "GPL"),
  in which case the provisions of the GPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of the GPL, and not to allow others to
  use your version of this file under the terms of Apache License version 2, 
  indicate your decision by deleting the provisions above and replace them with
  the  notice and other provisions required by the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the Apache License version 2 or the GPL.

  ***** END LICENSE BLOCK *****

 * @see https://github.com/dcornejo/dispatcher
 */

/*
 * we start with a series of dispatcher_definitions, which are a
 * path and handler.
 *
 * we break the path up into elements and build a tree out of them
 * example:
 *
 * we start with two paths /a/b/c and /a/d with handler_c() and
 * handler_d() as their handlers respectively.
 *
 * this produces a tree like this:
 *
 * [/] root_handler()
 *     [a] NULL
 *         [b] NULL
 *             [c] handler_c()
 *         [d] handler_d()
 *
 * NULL means that there is no handler defined - if the terminal
 * element of the path has a NULL handler then you look for the
 * closest ancestor that does.
 *
 * for example, if I lookup /a/b I get back a pointer to root_handler()
 * if i lookup /a/d, I get handler_d().
 *
 * if a element has a key (/a/b=c) then the list element is
 * marked with an = sign and without the key
 * so /a/b=c creates multiple entries:
 *
 *  [/]
 *      [a]
 *          [b=]
 *          [b]
 *
 * NOTE 2: there is no attempt to optimize list searching here, sorry. I
 * do not think that the known use cases will get big enough to make the
 * tree get too large. I do not recommend that you encode every possible
 * path, just top level key handlers.
 *
 * there are 2 functions to the API:
 * clixon_register_handler(): build the dispatcher table
 * clixon_call_handlers(): query the dispatcher table
 */

/*
 * Important for writing handlers: a handler must return a complete
 * valid response. It must operate in isolation, it must not expect
 * any ordering in the calls and [under review] it should not call
 * another handler directly or indirectly. Responses must me bound
 * to a yang model and properly sorted and indexed.
 */

#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>

#include "clixon_dispatcher.h"

/* ===== utility routines ==== */

#define PATH_CHUNKS 32

/*! Spilt a path into elements
 *
 * given an api-path, break it up into chunks separated by '/'
 * characters. it is expected that api-paths are URI encoded, so no need
 * to deal with unescaped special characters like ', ", and /
 *
 * @param[in]  path      Path string
 * @param[in]  plist     Pointer to split path array
 * @param[out] plist_len Pointer to storage space for path array length
 * @retval     0         OK
 * @retval    -1         Error
 * XXX consider using clixon_strsep
 */
static int
split_path(char   *path,
           char ***plist,
           size_t *plist_len)
{
    int    retval = -1;
    size_t allocated = PATH_CHUNKS;
    char  *work;     /* don't modify the original copy */
    char **list;
    size_t len = 0;
    char  *ptr;
    char  *new_element;

    if ((work = strdup(path)) == NULL)
        goto done;
    if ((list = malloc(allocated * sizeof(char *))) == NULL)
        goto done;
    memset(list, 0, allocated * sizeof(char *));
    ptr = work;
    if (*ptr == '/') {
        if ((new_element = strdup("/")) == NULL)
            goto done;
        list[len++] = new_element;
        ptr++;
    }

    ptr = strtok(ptr, "/");

    while (ptr != NULL) {
        if (len > allocated) {
            /* we've run out of space, allocate a bigger list */
            allocated += PATH_CHUNKS;
            if ((list = realloc(list, allocated * sizeof(char *))) == NULL)
                goto done;
        }

        if ((new_element = strdup(ptr)) == NULL)
            goto done;
        list[len++] = new_element;

        ptr = strtok(NULL, "/");
    }

    *plist = list;
    *plist_len = len;

    free(work);
    retval = 0;
 done:
    return retval;
}

/*! Free a split path structure
 *
 * @param[in] list pointer to split path array
 * @param[in] len length of split path array
 */
static void
split_path_free(char **list,
                size_t len)
{
    size_t i;

    for (i = 0; i < len; i++) {
        free(list[i]);
    }
    free(list);
}

/*! Find a peer of this node by name
 * search through the list pointed at by peer
 *
 * @param[in] node       Pointer to a node in the peer list
 * @param[in] node_name  Name of node we're looking for
 * @retval    pointer    Pointer to found node or NULL
 * @retval    NULL
 */
static dispatcher_entry_t *
find_peer(dispatcher_entry_t *node, char *node_name)
{
    dispatcher_entry_t *i;
    
    if ((node == NULL) || (node_name == NULL)) {
        /*  protect against idiot users */
        return NULL;
    }

    i = node->peer_head;

    while (i != NULL) {
        if (strcmp(node_name, i->node_name) == 0) {
            break;
        }
        i = i->peer;
    }

    return i;
}

/*! Add a node as the last node in peer list
 *
 * @param[in]  node    Pointer to an element of the peer list
 * @param[in]  name    Name of new node
 * @retval     pointer Pointer to added/existing node
 * @retval     NULL    Error
 */
static dispatcher_entry_t *
add_peer_node(dispatcher_entry_t *node,
              char               *name)
{
    dispatcher_entry_t *new_node = NULL;
    dispatcher_entry_t *eptr;

    if ((new_node = malloc(sizeof(dispatcher_entry_t))) == NULL)
        return NULL;
        
    memset(new_node, 0, sizeof(dispatcher_entry_t));
    if (node == NULL) {
        /* this is a new node */

        new_node->node_name = strdup(name);
        new_node->peer = NULL;
        new_node->children = NULL;
        new_node->peer_head = new_node;

        return new_node;
    } else {
        /* possibly adding to the list */

        /* search for existing, or get tail end of list */
        eptr = node->peer_head;
        while (eptr->peer != NULL) {
            if (strcmp(eptr->node_name, name) == 0) {
                return eptr;
            }
            eptr = eptr->peer;
        }

        // if eptr->node_name == name, we done
        if (strcmp(eptr->node_name, name) == 0) {
            return eptr;
        }

        new_node->node_name = strdup(name);
        new_node->peer = NULL;
        new_node->children = NULL;
        new_node->peer_head = node->peer_head;

        eptr->peer = new_node;

        return new_node;
    }
}

/*! Add a node as a child of this node
 *
 * this is different from add_peer_node() in that it returns a
 * pointer to the head_peer of the children list where the node was
 * added.
 *
 * @param[in] node    Pointer to parent node of children list
 * @param[in] name    Name of child node
 * @retval    pointer Pointer to head of children list
 * @retval    NULL    Error
 */

static dispatcher_entry_t *
add_child_node(dispatcher_entry_t *node,
               char               *name)
{
    dispatcher_entry_t *child_ptr;

    if ((child_ptr = add_peer_node(node->children, name)) == NULL)
        return NULL;
    node->children = child_ptr->peer_head;

    return child_ptr;
}

/**
 *
 * @param root
 * @param path
 * @retval
 * @retval NULL Error
 */
static dispatcher_entry_t *
get_entry(dispatcher_entry_t *root,
          char               *path)
{
    char              **split_path_list = NULL;
    size_t              split_path_len = 0;
    dispatcher_entry_t *ptr = root;
    dispatcher_entry_t *best = root;

    /* cut the path up into individual elements */
    if (split_path(path, &split_path_list, &split_path_len) < 0)
        return NULL;

    /* some elements may have keys defined, strip them off */
    for (int i = 0; i < split_path_len; i++) {
        char *kptr = split_path_list[i];
        strsep(&kptr, "=[]");
    }

    /* search down the tree */
    for (int i = 0; i < split_path_len; i++) {

        char *query = split_path_list[i];
        if ((ptr = find_peer(ptr, query)) == NULL) {
            split_path_free(split_path_list, split_path_len);
            /* we ran out of matches, use last found handler */
            return best;
        }
        if (ptr->handler != NULL) {
            /* if handler is defined, save it */
            best = ptr;
        }

        /* skip to next element */
        ptr = ptr->children;
    }

    /* clean up */
    split_path_free(split_path_list, split_path_len);
    
    return best;
}

/**
 * given a pointer to an entry, call the handler and all
 * descendant and peer handlers.
 *
 * @param entry
 * @param path
 * @retval
 */
static int
call_handler_helper(dispatcher_entry_t *entry,
                    void               *handle,
                    char               *path,
                    void               *user_args)
{
    if (entry->children != NULL) {
        call_handler_helper(entry->children, handle, path, user_args);
    }
    if (entry->peer != NULL) {
        call_handler_helper(entry->peer, handle, path, user_args);
    }
    if (entry->handler != NULL) {
        (entry->handler)(handle, path, user_args, entry->arg);
    }

    return 1;
}

/*
 * ===== PUBLIC API FUNCTIONS =====
 */

/*! Register a dispatcher handler
 *
 * called from initialization code to build a dispatcher tree
 *
 * @param[in]  root Pointer to pointer to dispatch tree
 * @param[in]  x    Handler to registration data
 * @retval     0    OK
 * @retval    -1    Error
 */
int
dispatcher_register_handler(dispatcher_entry_t   **root,
                            dispatcher_definition *x)
{
    char              **split_path_list = NULL;
    size_t              split_path_len = 0;
    dispatcher_entry_t *ptr;

    if (*x->dd_path != '/') {
        errno = EINVAL;
        return -1;
    }

    /*
     * get the path from the dispatcher_definition, break it
     * up to create the elements of the dispatcher table
     */
    if (split_path(x->dd_path, &split_path_list, &split_path_len) < 0)
        return -1;

    /*
     * the first element is always a peer to the top level
     */
    ptr = *root;

    if ((ptr = add_peer_node(ptr, split_path_list[0])) == NULL)
        return -1;
    if (*root == NULL) {
        *root = ptr;
    }

    for (size_t i = 1; i < split_path_len; i++) {
        if ((ptr = add_child_node(ptr, split_path_list[i])) == NULL)
            return -1;
    }

    /* when we get here, ptr points at last entry added */
    ptr->handler = x->dd_handler;
    ptr->arg = x->dd_arg;

    /* clean up */
    split_path_free(split_path_list, split_path_len);

    return 0;
}

/*! Call the handler and all its descendant handlers
 *
 * NOTE: There is no guarantee of the order in which handlers
 * are called! Any handler must assume that it is called in
 * isolation, even if this duplicates work. The right to
 * reorder calls by this code is reserved.
 *
 * @param[in]  handle
 * @param[in]  root
 * @param[in]  path   Note must be on the form: /a/b (no keys)
 * @retval     1      OK
 * @retval     0      Invalid
 * @retval    -1      Error
 */
int
dispatcher_call_handlers(dispatcher_entry_t *root,
                         void               *handle,
                         char               *path,
                         void               *user_args)
{
    int                 ret = 0;
    dispatcher_entry_t *best;

    if ((best = get_entry(root, path)) == NULL){
        errno = ENOENT;
        return -1;
    }
    if (best->children != NULL) {
        call_handler_helper(best->children, handle, path, user_args);
    }
    if (best->handler != NULL) {
        ret = (*best->handler)(handle, path, user_args, best->arg);
    }
    return ret;
}

/*! Free a dispatcher tree
 */
int
dispatcher_free(dispatcher_entry_t *root)
{
    if (root == NULL)
        return 0;
    if (root->children)
        dispatcher_free(root->children);
    if (root->peer)
        dispatcher_free(root->peer);
    if (root->node_name)
        free(root->node_name);
    free(root);
    return 0;
}

/*! Pretty-print dispatcher tree
 */
#define INDENT 3
int
dispatcher_print(FILE               *f,
                 int                 level,
                 dispatcher_entry_t *de)
{
    fprintf(f, "%*s%s", level*INDENT, "", de->node_name);
    if (de->handler)
        fprintf(f, " %p", de->handler);
    if (de->arg)
        fprintf(f, " (%p)", de->arg);
    fprintf(f, "\n");
    if (de->children)
        dispatcher_print(f, level+1, de->children);
    if (de->peer)
        dispatcher_print(f, level, de->peer);
    return 0;
}
