/*
   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.
*/

#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;
}
