#include <assert.h>
#include "cargs.h"
#include <memory.h>
#include <stdio.h>
#include <string.h>

#define CAG_OPTION_PRINT_DISTANCE 4
#define CAG_OPTION_PRINT_MIN_INDENTION 20

static void cag_option_print_value(const cag_option *option,
                                   size_t *accessor_length, FILE *destination)
{
    if (option->value_name != NULL) {
        *accessor_length += fprintf(destination, "=%s", option->value_name);
    }
}

static void cag_option_print_letters(const cag_option *option, bool *first,
                                     size_t *accessor_length, FILE *destination)
{
    const char *access_letter;
    access_letter = option->access_letters;
    if (access_letter != NULL) {
        while (*access_letter) {
            if (*first) {
                *accessor_length += fprintf(destination, "-%c", *access_letter);
                *first = false;
            } else {
                *accessor_length += fprintf(destination, ", -%c", *access_letter);
            }
            ++access_letter;
        }
    }
}

static void cag_option_print_name(const cag_option *option, bool *first,
                                  size_t *accessor_length, FILE *destination)
{
    if (option->access_name != NULL) {
        if (*first) {
            *accessor_length += fprintf(destination, "--%s", option->access_name);
        } else {
            *accessor_length += fprintf(destination, ", --%s", option->access_name);
        }
    }
}

static size_t cag_option_get_print_indention(const cag_option *options,
                                             size_t option_count)
{
    size_t option_index, indention, result;
    const cag_option *option;

    result = CAG_OPTION_PRINT_MIN_INDENTION;

    for (option_index = 0; option_index < option_count; ++option_index) {
        indention = CAG_OPTION_PRINT_DISTANCE;
        option = &options[option_index];
        if (option->access_letters != NULL && *option->access_letters) {
            indention += strlen(option->access_letters) * 4 - 2;
            if (option->access_name != NULL) {
                indention += strlen(option->access_name) + 4;
            }
        } else if (option->access_name != NULL) {
            indention += strlen(option->access_name) + 2;
        }

        if (option->value_name != NULL) {
            indention += strlen(option->value_name) + 1;
        }

        if (indention > result) {
            result = indention;
        }
    }

    return result;
}

void cag_option_print(const cag_option *options, size_t option_count,
                      FILE *destination)
{
    size_t option_index, indention, i, accessor_length;
    const cag_option *option;
    bool first;

    indention = cag_option_get_print_indention(options, option_count);

    for (option_index = 0; option_index < option_count; ++option_index) {
        option = &options[option_index];
        accessor_length = 0;
        first = true;

        fputs("  ", destination);

        cag_option_print_letters(option, &first, &accessor_length, destination);
        cag_option_print_name(option, &first, &accessor_length, destination);
        cag_option_print_value(option, &accessor_length, destination);

        for (i = accessor_length; i < indention; ++i) {
            fputs(" ", destination);
        }

        fputs(" ", destination);
        fputs(option->description, destination);

        fprintf(destination, "\n");
    }
}

void cag_option_prepare(cag_option_context *context, const cag_option *options,
                        size_t option_count, int argc, char **argv)
{
    // This just initialized the values to the beginning of all the arguments.
    context->options = options;
    context->option_count = option_count;
    context->argc = argc;
    context->argv = argv;
    context->index = 1;
    context->inner_index = 0;
    context->forced_end = false;
}

static const cag_option *cag_option_find_by_name(cag_option_context *context,
                                                 char *name, size_t name_size)
{
    const cag_option *option;
    size_t i;

    // We loop over all the available options and stop as soon as we have found
    // one. We don't use any hash map table, since there won't be that many
    // arguments anyway.
    for (i = 0; i < context->option_count; ++i) {
        option = &context->options[i];

        // The option might not have an item name, we can just skip those.
        if (option->access_name == NULL) {
            continue;
        }

        // Try to compare the name of the access name. We can use the name_size or
        // this comparison, since we are guaranteed to have null-terminated access
        // names.
        if (strncmp(option->access_name, name, name_size) == 0) {
            return option;
        }
    }

    return NULL;
}

static const cag_option *cag_option_find_by_letter(cag_option_context *context,
                                                   char letter)
{
    const cag_option *option;
    size_t i;

    // We loop over all the available options and stop as soon as we have found
    // one. We don't use any look up table, since there won't be that many
    // arguments anyway.
    for (i = 0; i < context->option_count; ++i) {
        option = &context->options[i];

        // If this option doesn't have any access letters we will skip them.
        if (option->access_letters == NULL) {
            continue;
        }

        // Verify whether this option has the access letter in it's access letter
        // string. If it does, then this is our option.
        if (strchr(option->access_letters, letter) != NULL) {
            return option;
        }
    }

    return NULL;
}

static void cag_option_parse_value(cag_option_context *context,
                                   const cag_option *option, char **c)
{
    // And now let's check whether this option is supposed to have a value, which
    // is the case if there is a value name set. The value can be either submitted
    // with a '=' sign or a space, which means we would have to jump over to the
    // next argv index. This is somewhat ugly, but we do it to behave the same as
    // the other option parsers.
    if (option->value_name != NULL) {
        if (**c == '=') {
            context->value = ++(*c);
        } else {
            // If the next index is larger or equal to the argument count, then the
            // parameter for this option is missing. The user will know about this,
            // since the value pointer of the context will be NULL because we don't
            // set it here in that case.
            if (context->argc > context->index + 1) {
                // We consider this argv to be the value, no matter what the contents
                // are.
                ++context->index;
                *c = context->argv[context->index];
                context->value = *c;
            }
        }

        // Move c to the end of the value, to not confuse the caller about our
        // position.
        while (**c) {
            ++(*c);
        }
    }
}

static void cag_option_parse_access_name(cag_option_context *context, char **c)
{
    const cag_option *option;
    char *n;

    // Now we need to extract the access name, which is any symbol up to a '=' or
    // a '\0'.
    n = *c;
    while (**c && **c != '=') {
        ++*c;
    }

    // Now this will obviously always be true, but we are paranoid. Sometimes. It
    // doesn't hurt to check.
    assert(*c >= n);

    // Figure out which option this name belongs to. This might return NULL if the
    // name is not registered, which means the user supplied an unknown option. In
    // that case we return true to indicate that we finished with this option. We
    // have to skip the value parsing since we don't know whether the user thinks
    // this option has one or not. Since we don't set any identifier specifically,
    // it will remain '?' within the context.
    option = cag_option_find_by_name(context, n, (size_t)(*c - n));
    if (option == NULL) {
        // Since this option is invalid, we will move on to the next index. There is
        // nothing we can do about this.
        ++context->index;
        return;
    }

    // We found an option and now we can specify the identifier within the
    // context.
    context->identifier = option->identifier;

    // And now we try to parse the value. This function will also check whether
    // this option is actually supposed to have a value.
    cag_option_parse_value(context, option, c);

    // And finally we move on to the next index.
    ++context->index;
}

static void cag_option_parse_access_letter(cag_option_context *context,
                                           char **c)
{
    const cag_option *option;
    char *n = *c;
    char *v;

    // Figure out which option this letter belongs to. This might return NULL if
    // the letter is not registered, which means the user supplied an unknown
    // option. In that case we return true to indicate that we finished with this
    // option. We have to skip the value parsing since we don't know whether the
    // user thinks this option has one or not. Since we don't set any identifier
    // specifically, it will remain '?' within the context.
    option = cag_option_find_by_letter(context, n[context->inner_index]);
    if (option == NULL) {
        ++context->index;
        context->inner_index = 0;
        return;
    }

    // We found an option and now we can specify the identifier within the
    // context.
    context->identifier = option->identifier;

    // And now we try to parse the value. This function will also check whether
    // this option is actually supposed to have a value.
    v = &n[++context->inner_index];
    cag_option_parse_value(context, option, &v);

    // Check whether we reached the end of this option argument.
    if (*v == '\0') {
        ++context->index;
        context->inner_index = 0;
    }
}

static void cag_option_shift(cag_option_context *context, int start, int option,
                             int end)
{
    char *tmp;
    int a_index, shift_index, shift_count, left_index, right_index;

    shift_count = option - start;

    // There is no shift is required if the start and the option have the same
    // index.
    if (shift_count == 0) {
        return;
    }

    // Lets loop through the option strings first, which we will move towards the
    // beginning.
    for (a_index = option; a_index < end; ++a_index) {
        // First remember the current option value, because we will have to save
        // that later at the beginning.
        tmp = context->argv[a_index];

        // Let's loop over all option values and shift them one towards the end.
        // This will override the option value we just stored temporarily.
        for (shift_index = 0; shift_index < shift_count; ++shift_index) {
            left_index = a_index - shift_index;
            right_index = a_index - shift_index - 1;
            context->argv[left_index] = context->argv[right_index];
        }

        // Now restore the saved option value at the beginning.
        context->argv[a_index - shift_count] = tmp;
    }

    // The new index will be before all non-option values, in such a way that they
    // all will be moved again in the next fetch call.
    context->index = end - shift_count;
}

static bool cag_option_is_argument_string(const char *c)
{
    return *c == '-' && *(c + 1) != '\0';
}

static int cag_option_find_next(cag_option_context *context)
{
    int next_index, next_option_index;
    char *c;

    // Prepare to search the next option at the next index.
    next_index = context->index;
    next_option_index = next_index;

    // Grab a pointer to the string and verify that it is not the end. If it is
    // the end, we have to return false to indicate that we finished.
    c = context->argv[next_option_index];
    if (context->forced_end || c == NULL) {
        return -1;
    }

    // Check whether it is a '-'. We need to find the next option - and an option
    // always starts with a '-'. If there is a string "-\0", we don't consider it
    // as an option neither.
    while (!cag_option_is_argument_string(c)) {
        c = context->argv[++next_option_index];
        if (c == NULL) {
            // We reached the end and did not find any argument anymore. Let's tell
            // our caller that we reached the end.
            return -1;
        }
    }

    // Indicate that we found an option which can be processed. The index of the
    // next option will be returned.
    return next_option_index;
}

bool cag_option_fetch(cag_option_context *context)
{
    char *c;
    int old_index, new_index;

    // Reset our identifier to a question mark, which indicates an "unknown"
    // option. The value is set to NULL, to make sure we are not carrying the
    // parameter from the previous option to this one.
    context->identifier = '?';
    context->value = NULL;

    // Check whether there are any options left to parse and remember the old
    // index as well as the new index. In the end we will move the option junk to
    // the beginning, so that non option arguments can be read.
    old_index = context->index;
    new_index = cag_option_find_next(context);
    if (new_index >= 0) {
        context->index = new_index;
    } else {
        return false;
    }

    // Grab a pointer to the beginning of the option. At this point, the next
    // character must be a '-', since if it was not the prepare function would
    // have returned false. We will skip that symbol and proceed.
    c = context->argv[context->index];
    assert(*c == '-');
    ++c;

    // Check whether this is a long option, starting with a double "--".
    if (*c == '-') {
        ++c;

        // This might be a double "--" which indicates the end of options. If this
        // is the case, we will not move to the next index. That ensures that
        // another call to the fetch function will not skip the "--".
        if (*c == '\0') {
            context->forced_end = true;
        } else {
            // We parse now the access name. All information about it will be written
            // to the context.
            cag_option_parse_access_name(context, &c);
        }
    } else {
        // This is no long option, so we can just parse an access letter.
        cag_option_parse_access_letter(context, &c);
    }

    // Move the items so that the options come first followed by non-option
    // arguments.
    cag_option_shift(context, old_index, new_index, context->index);

    return context->forced_end == false;
}

char cag_option_get(const cag_option_context *context)
{
    // We just return the identifier here.
    return context->identifier;
}

const char *cag_option_get_value(const cag_option_context *context)
{
    // We just return the internal value pointer of the context.
    return context->value;
}

int cag_option_get_index(const cag_option_context *context)
{
    // Either we point to a value item,
    return context->index;
}
