/* expand.c - does comprehensive glob expansion on a filename
   This has nothing to do with cryptography.
   Copyright (C) 1999 Paul Sheer

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307, USA.
 */

#include "mostincludes.h"
#include "mirrordir.h"
#include "regex.h"
#include "diffie/compat.h"
#include "mad.h"

struct glob_list {
    char *path;
    struct glob_list *next;
};

int is_special_prefix (char *path);

static char **expand_breakup_path (char *p)
{
    char **c, *q;
    int i, done = 0;
    for (i = 0, q = p; q; q = (char *) strchr (q + 1, PATH_SEP), i++);
    c = malloc ((i + 3) * sizeof (char *));
    i = 0;
    if (is_special_prefix (p)) {
	c[i] = (char *) strdup (p);
	strstr (c[i], PATH_SEP_STR PATH_SEP_STR)[2] = '\0';
	i++;
	p = strstr (p, PATH_SEP_STR PATH_SEP_STR) + 2;
    } else if (*p == PATH_SEP) {
	c[i++] = (char *) strdup (PATH_SEP_STR);
	p++;
    }
    while (!done) {
	q = strchr (p, PATH_SEP);
	if (!q) {
	    q = p + strlen (p);
	    done = 1;
	}
	c[i] = malloc ((unsigned long) q - (unsigned long) p + 1);
	memcpy (c[i], p, (unsigned long) q - (unsigned long) p);
	c[i][(unsigned long) q - (unsigned long) p] = '\0';
	i++;
	p = q + 1;
    }
    c[i] = 0;
    return c;
}

extern void progmess (char *s, char *par);
extern char *glob_translate (char *s);

static int expand_match (char *glob, char *s)
{
    static regex_t regexp;
    static char *g = 0;
    if (!g)
	goto comp;
    if (strcmp (g, glob)) {
	char *p;
      comp:
	if (g)
	    regfree (&regexp);
	g = (char *) strdup (glob);
	p = glob_translate (g);
	if (regcomp (&regexp, p, REG_EXTENDED | REG_NOSUB)) {
	    progmess ("error converting glob expression to regular expression", s);
	    free (p);
	    return 0;
	}
	free (p);
    }
    return !regexec (&regexp, s, 0, NULL, 0);
}

static void expand_append_list (struct glob_list **l, char *q)
{
    struct glob_list *j;
    j = malloc (sizeof (struct glob_list));
    j->next = *l;
    j->path = q;
    *l = j;
}

static char *expand_concat (char *p, char *c)
{
    char *q;
    q = malloc (strlen (p) + strlen (c) + 2);
    strcpy (q, p);
/* Nothings must not get a /
   Things with a slash must not get and extra slash: */
    if (*p && p[strlen (p) - 1] != PATH_SEP)
	strcat (q, PATH_SEP_STR);
    strcat (q, c);
    free (p);
    return q;
}

/* returns non-zero on match */
/* This behaves slightly differently to bash.
   bash reports a file even if its super directories do not exist.
   This doesn't.
 */
static int expand_add_path (struct glob_list **l, char *p, char **c)
{
    int r = 0;
    struct directory_list *k, *ka;
    p = (char *) strdup (p);
    while (*c && strcspn (*c, "{,}*[]") == strlen (*c))
	/* concat together stuff that is not glob */
	p = expand_concat (p, *c++);
    if (!*c) {
/* we've reached the end of the path */
	expand_append_list (l, p);
	return 1;
    }
    ka = read_list (*p ? p : ".");
    for (k = ka; k; k = k->next) {
	if (!k->name)
	    continue;
	if (expand_match (*c, k->name)) {
	    char *q = 0;
	    q = expand_concat ((char *) strdup (p), k->name);
	    if (c[1]) {
		if (S_ISDIR (k->stat.st_mode))
		    r |= expand_add_path (l, q, c + 1);
/*              else
 *                  the user thinks something is a directory that isn't */
		free (q);
	    } else {
		expand_append_list (l, q);
		r = 1;
	    }
	}
    }
    free_list (ka);
    free (p);
    return r;
}

/* returns non-zero on non-match or error */
static int expand_path (struct glob_list **l, char *p)
{
    char **c;
    int i;
    int r = 0;
    c = expand_breakup_path (p);
    if (!c)
	return 1;
    r = expand_add_path (l, "", c);
    for (i = 0; c[i]; i++)
	free (c[i]);
    free (c);
    return r;
}

static int expand_sort (const void *a, const void *b)
{
    return strcmp (*((char **) a), *((char **) b));
}

/* takes paths and modifies it, result must be free'd */
/* returns zero if there were no matches - in this case,
   paths will not have been modified */
int expand_glob (char ***paths)
{
    struct glob_list *l = 0, *k, *j;
    char **p;
    int n, r = 0;
    for (p = *paths; *p; p++)
	r |= expand_path (&l, *p);
    if (!r)
	return 0;
    for (n = 0, k = l; k; k = k->next, n++);
    p = malloc (sizeof (char *) * (n + 1));
    for (n = 0, k = l; k; k = j, n++) {
	j = k->next;
	p[n] = k->path;
	free (k);
    }
    p[n] = 0;
    qsort (p, n, sizeof (char *), expand_sort);
    *paths = p;
    return 1;
}



