/* wildexpand.c

   Unix-style conventions:

	'\' stops the interpretation of the following character.
	"~/" at the front of an argument is replaced by the value of $HOME/.
	'*' match anything (between '/'), except '.'
	'?' match a single character, except '.'
	"[xyza-g]" match anything in the character set.
		(Note: "[]abc]" is an error , use: "[\]abc]")

	'.' at the front of a path element matches only a '.' at the front of
	    a wildcard.  '*' and '?' don't match an initiial '.'

	An argument without wildcards, or with wildcards that don't match
	any files, is printed verbatim.

   Departure:
	There is only one kind of error possible, a missing ']' in a
	character set.  Instead of printing an error, we just print the
	argument verbatim like a nomatch wildcard.  It serves much the same
	purpose.  If you try to open it you'll get an error.
*/

#include <string.h>
#include "stat.h"
#include "dir.h"

static char wildlist[] = "\\~*?[";	/* List of chars we want to look at */

static char	**listp = NULL;
static int	curcount = 0, maxcount = 0;
static char	*workpath, *curp;

wildfree(char **freelist)
{
    char **cpp;

    if (!freelist) return;
    for (cpp = freelist; *cpp; cpp++)
	free (*cpp);

    free (freelist);
}

static int indircmp(char **l, char **r)
{
    return (strcmp(*l, *r));
}

static AddDirFile(char *dir, char *file)
{
    int i;
    char *cp;

    if (curcount + 1 >= maxcount) {	/* Need room for final NULL */
	maxcount += 32;
	listp = (char **)realloc(listp, maxcount * sizeof (char *));
	/* listp is null at start, which realloc takes to mean "malloc" */
    }
    i = strlen(dir) + strlen(file) + 1;
    cp = listp[curcount++] = (char *)malloc(strlen(dir) + strlen(file) + 1);
    strcpy(cp, dir);
    if (*file) strcat(cp, file);
}

char **wildexpand(char *w)
{
    int i;
    char *cp, *getenv();

    curcount = maxcount = 0;
    listp = NULL;
    curp = workpath = (char *)malloc(1024);/* Bigger than AmigaDOS path */

    if (*w == '~' && *(w+1) == '/') {
	if (cp = getenv ("HOME")) {
	    strcpy (workpath, cp);
	    i = strlen(cp);
	    if (workpath[i-1] != '/' && workpath[i-1] != ':')
		workpath[i++] = '/';
	    curp += i;
	    w += 2;
	}
    }
    recursive_expand(w);

    free(workpath);

    if (listp)
	qsort(listp, curcount, sizeof(char *), indircmp);
    else
	AddDirFile(w, "");

    listp[curcount] = NULL;
    return(listp);
}

static recursive_expand(char *w)
{
    char *cp, *hold_curp = curp;
    struct direct *d;
    DIR *dp;

    if (!(cp = strpbrk(w, wildlist))) {
	*curp = '\0';
	return;
    }
    for (; cp > w && *cp != '/' && *cp != ':'; cp--) ;
    if (*cp == '/' || *cp == ':') cp++;

    if (cp > w) {
	strncpy(curp, w, cp - w);
	curp += cp - w;
    }
    *curp = '\0';

    if (!(dp = opendir(workpath))) return;
    while (d = readdir(dp)) {
	if (!(d->d_ino)) continue;
	if (*(d->d_name) == '.' && *cp != '.') continue;
	if (wildmatch(d->d_name, cp))
	    AddDirFile(workpath, d->d_name);
    }
    closedir(dp);
    curp = hold_curp;
    *curp = '\0';
}

static int wildmatch(char *str, char *pat)
{
    char *cp, *cp2, *sp = str, *hold_curp = curp;
    struct stat st;
    int c, c2, patc, not;

    while (patc = *pat++) {
	switch (patc) {
	case '?':
		if (!*str) return (0);
		str++;
		break;

	case '\\':
		if (*pat++ != *str++) return(0);
		break;

	case '[':
		/* All the tests for '\0' mean: "if bad pattern, no match." */
		if (!(c = *str++)) return(0);
		if (not = (*pat == '^')) pat++;

		if (!(patc = *pat++)) return (0);
	        if (patc == ']') return(0);
		for (;;) {
		    if (patc == ']') break;
		    if (patc == '\\') if (!(patc = *pat++)) return (0);

		    if (c != patc) {
			if (!(c2 = *pat)) return (0);
			if (c2 == '\\') { patc = c2; continue; }
			pat++;

			if (c2 == '-') {
			    if (!(c2 = *pat++)) return (0);
			    if (c2 == '\\') if (!(c2 = *pat++)) return (0);

			} else {
			    patc = c2;
			    continue;
			}
			if (c < patc || c > c2) {
			    if (!(patc = *pat++)) return (0);
			    continue;
			}
		    }
		    /* We wind up here on a match */
		    if (not) return(0);
		    while (patc && patc != ']') {
			patc = *pat++;
			if (patc == '\\') pat++;
		    }
		    if (!patc) return (0);
		    not = 1;  /* "*hack*" to get a "break" outside loop */
		    break;
		}
		if (not) break;	/* no match, but didn't want to (or "*hack*")*/
	        return (0);	/* no match  and wanted to */

	case '*':
		if (!*pat) return (1);
		if (*pat != '/') {
		    for (cp = str; *cp; cp++)
			if (wildmatch(cp, pat))
			    return (1);
		    return (0);
		}
		/* fall through */
	case '/':
		if (*str) return (0);	/* If str(ing) continues, no match */
		cp = sp;		/* Otherwise copy final element into */
		hold_curp = curp;	/*  workpath and go one level deeper */
		while (*cp) *curp++ = *cp++;
		*curp++ = '/';
		*curp = 0;
		if (!stat(workpath, &st) && (st.st_mode & S_IFMT) == S_IFDIR)
		    if (!*pat) {
			AddDirFile(workpath, "");
		    } else
			recursive_expand(pat);

		curp = hold_curp;
		*curp = 0;
		return (0);

	default:
		if (patc != *str++) return (0);
		break;
	}
    }
    return (!*str);
}
