/* -*- coding: utf-8 -*-
 * ----------------------------------------------------------------------
 * Copyright © 2012, RedJack, LLC.
 * All rights reserved.
 *
 * Please see the COPYING file in this distribution for license
 * details.
 * ----------------------------------------------------------------------
 */

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

#include "libcork/cli.h"
#include "libcork/core.h"
#include "libcork/ds.h"


#define streq(a,b) (strcmp((a), (b)) == 0)

static struct cork_buffer  breadcrumbs_buf = CORK_BUFFER_INIT();

static void
cork_command_add_breadcrumb(struct cork_command *command)
{
    cork_buffer_append_printf(&breadcrumbs_buf, " %s", command->name);
}

#define cork_command_breadcrumbs() ((char *) breadcrumbs_buf.buf)

static void
cork_command_run(struct cork_command *command, int argc, char **argv);

static struct cork_command *
cork_command_set_get_subcommand(struct cork_command *command,
                                const char *command_name)
{
    struct cork_command  **curr;
    for (curr = command->set; *curr != NULL; curr++) {
        if (streq(command_name, (*curr)->name)) {
            return *curr;
        }
    }
    return NULL;
}

static void
cork_command_set_show_help(struct cork_command *command)
{
    size_t  max_length = 0;
    struct cork_command  **curr;

    /* Calculate the length of the longest command name. */
    for (curr = command->set; *curr != NULL; curr++) {
        size_t  len = strlen((*curr)->name);
        if (len > max_length) {
            max_length = len;
        }
    }

    /* Then print out the available commands. */
    printf("Usage:%s <command> [<options>]\n"
           "\nAvailable commands:\n",
           cork_command_breadcrumbs());

    for (curr = command->set; *curr != NULL; curr++) {
        printf("  %*s", (int) -max_length, (*curr)->name);
        if ((*curr)->short_desc != NULL) {
            printf("  %s\n", (*curr)->short_desc);
        } else {
            printf("\n");
        }
    }
}

static void
cork_command_leaf_show_help(struct cork_command *command)
{
    printf("Usage:%s", cork_command_breadcrumbs());
    if (command->usage_suffix != NULL) {
        printf(" %s", command->usage_suffix);
    }
    if (command->full_help != NULL) {
        printf("\n\n%s", command->full_help);
    } else {
        printf("\n");
    }
}

void
cork_command_show_help(struct cork_command *command, const char *message)
{
    if (message != NULL) {
        printf("%s\n", message);
    }

    if (command->type == CORK_COMMAND_SET) {
        cork_command_set_show_help(command);
    } else if (command->type == CORK_LEAF_COMMAND) {
        cork_command_leaf_show_help(command);
    }
}

static void
cork_command_set_run_help(struct cork_command *command, int argc, char **argv)
{
    /* When we see the help command when processing a command set, we use any
     * remaining arguments to identifity which subcommand the user wants help
     * with. */

    /* Skip over the name of the command set */
    argc--;
    argv++;

    while (argc > 0 && command->type == CORK_COMMAND_SET) {
        struct cork_command  *subcommand =
            cork_command_set_get_subcommand(command, argv[0]);
        if (subcommand == NULL) {
            printf("Unknown command \"%s\".\n"
                   "Usage:%s <command> [<options>]\n",
                   argv[0], cork_command_breadcrumbs());
            exit(EXIT_FAILURE);
        }

        cork_command_add_breadcrumb(subcommand);
        command = subcommand;
        argc--;
        argv++;
    }

    cork_command_show_help(command, NULL);
}

static void
cork_command_set_run(struct cork_command *command, int argc, char **argv)
{
    const char  *command_name;
    struct cork_command  *subcommand;

    if (argc == 0) {
        printf("No command given.\n");
        cork_command_set_show_help(command);
        exit(EXIT_FAILURE);
    }

    command_name = argv[0];

    /* The "help" command is special. */
    if (streq(command_name, "help")) {
        cork_command_set_run_help(command, argc, argv);
        return;
    }

    /* Otherwise look for a real subcommand with this name. */
    subcommand = cork_command_set_get_subcommand(command, command_name);
    if (subcommand == NULL) {
        printf("Unknown command \"%s\".\n"
               "Usage:%s <command> [<options>]\n",
               command_name, cork_command_breadcrumbs());
        exit(EXIT_FAILURE);
    } else {
        cork_command_run(subcommand, argc, argv);
    }
}

static void
cork_command_leaf_run(struct cork_command *command, int argc, char **argv)
{
    command->run(argc, argv);
}

static void
cork_command_cleanup(void)
{
    cork_buffer_done(&breadcrumbs_buf);
}

static void
cork_command_run(struct cork_command *command, int argc, char **argv)
{
    cork_command_add_breadcrumb(command);

    /* If the gives the --help option at this point, describe the current
     * command. */
    if (argc >= 2 && (streq(argv[1], "--help") || streq(argv[1], "-h"))) {
        cork_command_show_help(command, NULL);
        return;
    }

    /* Otherwise let the command parse any options that occur here. */
    if (command->parse_options != NULL) {
        int  option_count = command->parse_options(argc, argv);
        argc -= option_count;
        argv += option_count;
    } else {
        argc--;
        argv++;
    }

    switch (command->type) {
        case CORK_COMMAND_SET:
            cork_command_set_run(command, argc, argv);
            return;

        case CORK_LEAF_COMMAND:
            cork_command_leaf_run(command, argc, argv);
            return;

        default:
            cork_unreachable();
    }
}


int
cork_command_main(struct cork_command *root, int argc, char **argv)
{
    /* Clean up after ourselves when the command finishes. */
    atexit(cork_command_cleanup);

    /* Run the root command. */
    cork_command_run(root, argc, argv);
    return EXIT_SUCCESS;
}
