/*
   Copyright (c) 2010-2012 Red Hat, Inc. <http://www.redhat.com>
   This file is part of GlusterFS.

   This file is licensed to you under your choice of the GNU Lesser
   General Public License, version 3 or any later version (LGPLv3 or
   later), or the GNU General Public License, version 2 (GPLv2), in all
   cases as published by the Free Software Foundation.
*/

#ifndef _CONFIG_H
#define _CONFIG_H
#include "config.h"
#endif

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

#include "cli.h"
#include "cli-cmd.h"


static int
__is_spc (int ch)
{
        if (ch == ' ')
                return 1;
        return 0;
}


static int
__is_div (int ch)
{
        switch (ch) {
        case '(':
        case ')':
        case '<':
        case '>':
        case '[':
        case ']':
        case '{':
        case '}':
        case '|':
                return 1;
        }

        return 0;
}


static int
__is_word (const char *word)
{
        return (!__is_div (*word) && !__is_spc (*word));
}


int
counter_char (int ch)
{
        switch (ch) {
        case '(':
                return ')';
        case '<':
                return '>';
        case '[':
                return ']';
        case '{':
                return '}';
        }

        return -1;
}


const char *
__is_template_balanced (const char *template)
{
        const char *trav = NULL;
        int   ch = 0;

        trav = template;

        while (*trav) {
                ch = *trav;

                switch (ch) {
                case '<':
                case '(':
                case '[':
                        trav = __is_template_balanced (trav+1);
                        if (!trav)
                                return NULL;
                        if (*trav != counter_char (ch))
                                return NULL;
                        break;
                case '>':
                case ')':
                case ']':
                        return trav;
                }

                trav++;
        }

        return trav;
}


int
is_template_balanced (const char *template)
{
        const char *trav = NULL;

        trav = __is_template_balanced (template);
        if (!trav || *trav)
                return -1;

        return 0;
}


int
cli_cmd_token_count (const char *template)
{
        int         count = 0;
        const char *trav = NULL;
        int         is_alnum = 0;

        for (trav = template; *trav; trav++) {
                switch (*trav) {
                case '<':
                case '>':
                case '(':
                case ')':
                case '[':
                case ']':
                case '{':
                case '}':
                case '|':
                        count++;
                        /* fall through */
                case ' ':
                        is_alnum = 0;
                        break;
                default:
                        if (!is_alnum) {
                                is_alnum = 1;
                                count++;
                        }
                }
        }

        return count + 1;
}


void
cli_cmd_tokens_destroy (char **tokens)
{
        char **tokenp = NULL;

        if (!tokens)
                return;

        tokenp = tokens;
        while (*tokenp) {
                free (*tokenp);
                tokenp++;
        }

        free (tokens);
}


int
cli_cmd_tokens_fill (char **tokens, const char *template)
{
        const char  *trav = NULL;
        char       **tokenp = NULL;
        char        *token = NULL;
        int          ret = 0;
        int          ch = 0;

        tokenp = tokens;

        for (trav = template; *trav; trav++) {
                ch = *trav;

                if (__is_spc (ch))
                        continue;

                if (__is_div (ch)) {
                        token = calloc (2, 1);
                        if (!token)
                                return -1;
                        token[0] = ch;

                        *tokenp = token;
                        tokenp++;

                        continue;
                }

                token = strdup (trav);
                *tokenp = token;
                tokenp++;

                for (token++; *token; token++) {
                        if (__is_spc (*token) || __is_div (*token)) {
                                *token = 0;
                                break;
                        }
                        trav++;
                }
        }

        return ret;
}


char **
cli_cmd_tokenize (const char *template)
{
        char **tokens = NULL;
        int    ret = 0;
        int    count = 0;

        ret = is_template_balanced (template);
        if (ret)
                return NULL;

        count = cli_cmd_token_count (template);
        if (count <= 0)
                return NULL;

        tokens = calloc (count + 1, sizeof (char *));
        if (!tokens)
                return NULL;

        ret = cli_cmd_tokens_fill (tokens, template);
        if (ret)
                goto err;

        return tokens;
err:
        cli_cmd_tokens_destroy (tokens);
        return NULL;
}

void *
cli_getunamb (const char *tok, void **choices, cli_selector_t sel)
{
        void  **wcon = NULL;
        char      *w = NULL;
        unsigned  mn = 0;
        void    *ret = NULL;

        if (!choices || !tok || !*tok)
                return NULL;

        for (wcon = choices; *wcon; wcon++) {
                w = strtail ((char *)sel (*wcon), tok);
                if (!w)
                        /* no match */
                        continue;
                if (!*w)
                        /* exact match */
                        return *wcon;

                ret = *wcon;
                mn++;
        }

#ifdef FORCE_MATCH_EXACT
        return NULL;
#else
        return (mn == 1) ? ret : NULL;
#endif
}

static const char *
sel_cmd_word (void *wcon)
{
        return ((struct cli_cmd_word *)wcon)->word;
}

struct cli_cmd_word *
cli_cmd_nextword (struct cli_cmd_word *word, const char *token)
{
        return (struct cli_cmd_word *)cli_getunamb (token,
                                                    (void **)word->nextwords,
                                                    sel_cmd_word);
}


struct cli_cmd_word *
cli_cmd_newword (struct cli_cmd_word *word, const char *token)
{
        struct cli_cmd_word **nextwords = NULL;
        struct cli_cmd_word  *nextword = NULL;

        nextwords = realloc (word->nextwords,
                             (word->nextwords_cnt + 2) * sizeof (*nextwords));
        if (!nextwords)
                return NULL;

        word->nextwords = nextwords;

        nextword = calloc (1, sizeof (*nextword));
        if (!nextword)
                return NULL;

        nextword->word = strdup (token);
        if (!nextword->word) {
                free (nextword);
                return NULL;
        }

        nextword->tree = word->tree;
        nextwords[word->nextwords_cnt++] = nextword;
        nextwords[word->nextwords_cnt] = NULL;

        return nextword;
}


int
cli_cmd_ingest (struct cli_cmd_tree *tree, char **tokens, cli_cmd_cbk_t *cbkfn,
                const char *desc, const char *pattern)
{
        int                    ret = 0;
        char                 **tokenp = NULL;
        char                  *token = NULL;
        struct cli_cmd_word   *word = NULL;
        struct cli_cmd_word   *next = NULL;

        word = &tree->root;

        for (tokenp = tokens; (token = *tokenp); tokenp++) {
                if (!__is_word (token))
                        break;

                next = cli_cmd_nextword (word, token);
                if (!next)
                        next = cli_cmd_newword (word, token);

                word = next;
                if (!word)
                        break;
        }

        if (!word)
                return -1;

        if (word->cbkfn) {
                /* warning - command already registered */
        }

        word->cbkfn = cbkfn;
        word->desc  = desc;
        word->pattern = pattern;

        /* end of static strings in command template */

        /* TODO: autocompletion beyond this point is just "nice to have" */

        return ret;
}


int
cli_cmd_register (struct cli_cmd_tree *tree, struct cli_cmd *cmd)
{
        char **tokens = NULL;
        int    ret = 0;

        GF_ASSERT (cmd);

        if (cmd->reg_cbk)
                cmd->reg_cbk (cmd);

        if (cmd->disable) {
                ret = 0;
                goto out;
        }

        tokens = cli_cmd_tokenize (cmd->pattern);
        if (!tokens) {
                ret = -1;
                goto out;
        }

        ret = cli_cmd_ingest (tree, tokens, cmd->cbk, cmd->desc, cmd->pattern);
        if (ret) {
                ret = -1;
                goto out;
        }

        ret = 0;

out:
        if (tokens)
                cli_cmd_tokens_destroy (tokens);

        gf_log ("cli", GF_LOG_DEBUG, "Returning %d", ret);
        return ret;
}

