/* History and file completion functions for editline library.
 *
 * Copyright (c) 1992, 1993  Simmule Turner and Rich Salz
 * All rights reserved.
 *
 * This software is not subject to any license of the American Telephone
 * and Telegraph Company or of the Regents of the University of California.
 *
 * Permission is granted to anyone to use this software for any purpose on
 * any computer system, and to alter it and redistribute it freely, subject
 * to the following restrictions:
 * 1. The authors are not responsible for the consequences of use of this
 *    software, no matter how awful, even if they arise from flaws in it.
 * 2. The origin of this software must not be misrepresented, either by
 *    explicit claim or by omission.  Since few users ever read sources,
 *    credits must appear in the documentation.
 * 3. Altered versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.  Since few users
 *    ever read sources, credits must appear in the documentation.
 * 4. This notice may not be removed or altered.
 */

#include <ctype.h>
#include "editline.h"

#define MAX_TOTAL_MATCHES (256 << sizeof(char *))

int rl_attempted_completion_over = 0;
rl_completion_func_t *rl_attempted_completion_function = NULL;
rl_compentry_func_t *rl_completion_entry_function = NULL;

/* Wrap strcmp() for qsort() -- weird construct to pass -Wcast-qual */
static int compare(const void *p1, const void *p2)
{
    char *const *v1 = (char *const *)p1;
    char *const *v2 = (char *const *)p2;

    return strcmp(*v1, *v2);
}

/* Fill in *avp with an array of names that match file, up to its length.
 * Ignore . and .. . */
static int FindMatches(char *dir, char *file, char ***avp)
{
    char        **av;
    char        **word;
    char        *p;
    DIR         *dp;
    DIRENTRY    *ep;
    size_t      ac;
    size_t      len;
    size_t      choices;
    size_t      total;

    if ((dp = opendir(dir)) == NULL)
        return 0;

    av = NULL;
    ac = 0;
    len = strlen(file);
    choices = 0;
    total = 0;
    while ((ep = readdir(dp)) != NULL) {
        p = ep->d_name;
        if (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))
            continue;
        if (len && strncmp(p, file, len) != 0)
            continue;

        choices++;
        if ((total += strlen(p)) > MAX_TOTAL_MATCHES) {
            /* This is a bit too much. */
            while (ac > 0) free(av[--ac]);
            continue;
        }

        if ((ac % MEM_INC) == 0) {
	    word = malloc(sizeof(char *) * (ac + MEM_INC));
            if (!word) {
                total = 0;
                break;
            }

            if (ac) {
                memcpy(word, av, ac * sizeof(char *));
                free(av);
            }
            *avp = av = word;
        }

        if ((av[ac] = strdup(p)) == NULL) {
            if (ac == 0)
                free(av);
            total = 0;
            break;
        }
        ac++;
    }

    /* Clean up and return. */
    closedir(dp);
    if (total > MAX_TOTAL_MATCHES) {
        char many[sizeof(total) * 3];

        p = many + sizeof(many);
        *--p = '\0';
        while (choices > 0) {
	    *--p = '0' + choices % 10;
	    choices /= 10;
        }

	while (p > many + sizeof(many) - 8)
	    *--p = ' ';

        if ((p = strdup(p)) != NULL)
	    av[ac++] = p;

        if ((p = strdup("choices")) != NULL)
	    av[ac++] = p;
    } else {
        if (ac)
            qsort(av, ac, sizeof(char *), compare);
    }

    return ac;
}

/* Split a pathname into allocated directory and trailing filename parts. */
static int SplitPath(const char *path, char **dirpart, char **filepart)
{
    static char DOT[] = ".";
    char        *dpart;
    char        *fpart;

    if ((fpart = strrchr(path, '/')) == NULL) {
        if ((dpart = strdup(DOT)) == NULL)
            return -1;

        if ((fpart = strdup(path)) == NULL) {
            free(dpart);
            return -1;
        }
    } else {
        if ((dpart = strdup(path)) == NULL)
            return -1;

        dpart[fpart - path + 1] = '\0';
        if ((fpart = strdup(fpart + 1)) == NULL) {
            free(dpart);
            return -1;
        }
    }
    *dirpart = dpart;
    *filepart = fpart;

    return 0;
}

static rl_complete_func_t *el_complete_func = NULL;

/* For compatibility with the Heimdal project. */
rl_complete_func_t *rl_set_complete_func(rl_complete_func_t *func)
{
    rl_complete_func_t *old = el_complete_func;
    el_complete_func = func;

    return old;
}

/* Attempt to complete the pathname, returning an allocated copy.
 * Fill in *match if we completed it, or set it to 0 if ambiguous. */
char *el_filename_complete(char *pathname, int *match)
{
    char        **av;
    char        *dir;
    char        *file;
    char        *path;
    char        *p;
    size_t      ac;
    size_t      end;
    size_t      i;
    size_t      j;
    size_t      len;

    if (SplitPath((const char *)pathname, &dir, &file) < 0)
        return NULL;

    if ((ac = FindMatches(dir, file, &av)) == 0) {
        free(dir);
        free(file);

        return NULL;
    }

    p = NULL;
    len = strlen(file);
    if (ac == 1) {
        /* Exactly one match -- finish it off. */
        *match = 1;
        j = strlen(av[0]) - len + 2;
	p = malloc(sizeof(char) * (j + 1));
        if (p) {
            memcpy(p, av[0] + len, j);
	    len = strlen(dir) + strlen(av[0]) + 2;
	    path = malloc(sizeof(char) * len);
            if (path) {
		snprintf(path, len, "%s/%s", dir, av[0]);
                rl_add_slash(path, p);
                free(path);
            }
        }
    } else {
        *match = 0;
        if (len) {
            /* Find largest matching substring. */
            for (i = len, end = strlen(av[0]); i < end; i++) {
                for (j = 1; j < ac; j++) {
                    if (av[0][i] != av[j][i])
                        goto breakout;
		}
	    }
	breakout:
            if (i > len) {
                j = i - len + 1;
		p = malloc(sizeof(char) * j);
                if (p) {
                    memcpy(p, av[0] + len, j);
                    p[j - 1] = '\0';
                }
            }
        }
    }

    /* Clean up and return. */
    free(dir);
    free(file);
    for (i = 0; i < ac; i++)
        free(av[i]);
    free(av);

    return p;
}

char *rl_filename_completion_function(const char *text, int state)
{
    char        *dir;
    char        *file;
    static char   **av;
    static size_t i, ac;

    if (!state) {
	if (SplitPath(text, &dir, &file) < 0)
	    return NULL;

	ac = FindMatches(dir, file, &av);
	free(dir);
	free(file);
	if (!ac)
	    return NULL;

	i = 0;
    }

    if (i < ac)
	return av[i++];

    do {
	free(av[--i]);
    } while (i > 0);

    return NULL;
}

/* Similar to el_find_word(), but used by GNU Readline API */
static char *rl_find_token(size_t *len)
{
    char *ptr;
    int pos;

    for (pos = rl_point; pos < rl_end; pos++) {
	if (isspace(rl_line_buffer[pos])) {
	    if (pos > 0)
		pos--;
	    break;
	}
    }

    ptr = &rl_line_buffer[pos];
    while (pos >= 0 && !isspace(rl_line_buffer[pos])) {
	if (pos == 0)
	    break;

	pos--;
    }

    if (ptr != &rl_line_buffer[pos]) {
	*len = (size_t)(ptr - &rl_line_buffer[pos]);
	return &rl_line_buffer[pos];
    }

    return NULL;
}

/*
 * "uses an application-supplied generator function to generate the list
 * of possible matches, and then returns the array of these matches. The
 * caller should place the address of its generator function in
 * rl_completion_entry_function"
 */
char **rl_completion_matches(const char *token, rl_compentry_func_t *generator)
{
    int state = 0, num = 0;
    char **array, *entry;

    if (!generator) {
	generator = rl_completion_entry_function;
	if (!generator)
	    generator = rl_filename_completion_function;
    }

    if (!generator)
	return NULL;

    array = malloc(512 * sizeof(char *));
    if (!array)
	return NULL;

    while (num < 511 && (entry = generator(token, state))) {
	state = 1;
	array[num++] = entry;
    }
    array[num] = NULL;

    if (!num) {
	free(array);
	return NULL;
    }

    return array;
}

static char *complete(char *token, int *match)
{
    size_t len = 0;
    char *word, **words = NULL;
    int start, end;

    word = rl_find_token(&len);
    if (!word)
	goto fallback;

    start = word - rl_line_buffer;
    end  = start + len;

    word = strndup(word, len);
    if (!word)
	goto fallback;

    rl_attempted_completion_over = 0;
    words = rl_attempted_completion_function(word, start, end);

    if (!rl_attempted_completion_over && !words)
	words = rl_completion_matches(word, NULL);

    if (words) {
	int i = 0;

	free(word);
	word = NULL;
	if (words[0])
	    word = strdup(words[0] + len);

	while (words[i])
	    free(words[i++]);
	free(words);

	if (word)
	    return word;
    }

fallback:
    return el_filename_complete(token, match);
}

/*
 * First check for editline specific custom completion function, then
 * for any GNU Readline compat, then fallback to filename completion.
 */
char *rl_complete(char *token, int *match)
{
    if (el_complete_func)
	return el_complete_func(token, match);

    if (rl_attempted_completion_function)
	return complete(token, match);

    return el_filename_complete(token, match);
}

static rl_list_possib_func_t *el_list_possib_func = NULL;

/* For compatibility with the Heimdal project. */
rl_list_possib_func_t *rl_set_list_possib_func(rl_list_possib_func_t *func)
{
    rl_list_possib_func_t *old = el_list_possib_func;
    el_list_possib_func = func;
    return old;
}

/* Default possible completions. */
int el_filename_list_possib(char *pathname, char ***av)
{
    char        *dir;
    char        *file;
    int         ac;

    if (SplitPath(pathname, &dir, &file) < 0)
        return 0;

    ac = FindMatches(dir, file, av);
    free(dir);
    free(file);

    return ac;
}

/* Return all possible completions. */
int rl_list_possib(char *token, char ***av)
{
    if (el_list_possib_func)
	return el_list_possib_func(token, av);

    return el_filename_list_possib(token, av);
}


/**
 * Local Variables:
 *  c-file-style: "k&r"
 *  c-basic-offset: 4
 * End:
 */
