/* dirtools.c - reads and creates a list of directory entries
   Copyright (C) 1996-2000 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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <my_string.h>
#include "stringtools.h"
#include <sys/types.h>

#include <my_string.h>
#include <sys/stat.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "coolwidget.h"
#include "loadfile.h"
#include "pool.h"
#include "mad.h"

char *dname (struct dirent *directentry)
{
    int l;
    static char t[MAX_PATH_LEN];
    l = NAMLEN (directentry);
    if (l >= MAX_PATH_LEN)
	l = MAX_PATH_LEN - 1;
    strncpy (t, directentry->d_name, l);
    t[l] = 0;
    return t;
}

/* 
   Returns a \n separate list of directories or files in the
   directory *directory. The files are sorted alphabetacally.
   The list is malloc'ed and must be free'd.
   If f is FILELIST_DIRECTORIES_ONLY then only directories are returned.
   If f & FILELIST_ALL_FILES all files are returned.
   If f & FILELIST_FILES_ONLY, only files are returned.
 */
char *get_file_list (const char *directory, unsigned long f, char *filter)
{
    struct dirent *directentry;
    struct stat stats;
    DIR *dir;
    char *list;
    int numentries = 0;
    long listsize;
    char path_fname[MAX_PATH_LEN];
    POOL *p;

    p = pool_init ();

    if (filter) {
	if (!*filter)
	    filter = "*";
    } else
	filter = "*";

    if ((dir = opendir (directory)) == NULL)
/* No spaces here */
	return (char *) strdup (_("Error: Cannot open directory.\n"));

    while ((directentry = readdir (dir))) {
	strcpy (path_fname, directory);
	strcat (path_fname, "/");
	strcat (path_fname, dname (directentry));
	if (!stat (path_fname, &stats) && strcmp (dname (directentry), ".")) {
	    if (S_ISDIR (stats.st_mode)) {
		if (f & FILELIST_DIRECTORIES_ONLY) {
		    if (regexp_match (filter, dname (directentry), match_file) == 1) {
			if (!pool_printf (p, "/%s\n", dname (directentry))) {
			    closedir (dir);
			    return 0;
			}
			numentries++;
		    }
		}
	    } else {
		if (f & FILELIST_FILES_ONLY) {
		    if (regexp_match (filter, dname (directentry), match_file) == 1) {
			if (!pool_printf (p, "%s\n", dname (directentry))) {
			    closedir (dir);
			    return 0;
			}
			numentries++;
		    }
		}
	    }
	}
    }
/*
   Now do a bubble sort on the list. (a directory list isn't long enough
   to warrant a quick sort) and the qsort command won't work on unevenly
   sized entries.
 */
    pool_null (p);
    listsize = pool_length (p);
    list = (char *) pool_break (p);

    f = 1;
    if (numentries) {
	char *firststr, *secondstr;
	unsigned long i, r, q;
	while (f) {
	    numentries--;
	    r = 0;
	    f = 0;
	    for (i = 0; i < numentries; i++) {
		char *t;
		t = strchr (list + r, '\n');
		if (t) {
		    q = (unsigned long) t - (unsigned long) list + 1;
		    if (strcmp (firststr = strline (list, r), secondstr = strline (list, q)) > 0) {
			strcpy (list + r, secondstr);
			r += strlen (secondstr);
			*(list + r++) = '\n';
			memcpy (list + r, firststr, strlen (firststr));
			f = 1;
		    } else
			r = q;
		} else
		    break;
	    }
	}
	list[listsize - 1] = 0;	/* remove the last \n */
    }
    closedir (dir);
    return list;
}


int compare_fileentries (struct file_entry *file_entry1, struct file_entry *file_entry2)
{
#if 0
    if (file_entry->options & FILELIST_SORT_...);
#endif
    return (strcmp (file_entry1->name, file_entry2->name));
}

struct file_entry *get_file_entry_list (const char *directory, unsigned long options, char *filter)
{
    struct file_entry entry;
    struct file_entry *list;
    struct dirent *directentry;
    struct stat stats;
    DIR *dir;
    int numentries = 0;
    char path_fname[MAX_PATH_LEN];
    POOL *p;

    p = pool_init ();

    if (filter) {
	if (!*filter)
	    filter = "*";
    } else
	filter = "*";

    if ((dir = opendir (directory)) == NULL) {
	pool_free (p);
	return 0;
    }

    while ((directentry = readdir (dir))) {
	strcpy (path_fname, directory);
	strcat (path_fname, "/");
	strcat (path_fname, dname (directentry));
	if (!stat (path_fname, &stats) && strcmp (dname (directentry), ".")) {
	    if (S_ISDIR (stats.st_mode)) {
		if (options & FILELIST_DIRECTORIES_ONLY) {
		    if (regexp_match (filter, dname (directentry), match_file) == 1) {
			lstat (path_fname, &entry.stat);
			strcpy (entry.name, dname (directentry));
			entry.options = options;
			if (!pool_write (p, (unsigned char *) &entry, sizeof (entry))) {
			    pool_free (p);
			    closedir (dir);
			    return 0;
			}
			numentries++;
		    }
		}
	    } else {
		if (options & FILELIST_FILES_ONLY) {
		    if (regexp_match (filter, dname (directentry), match_file) == 1) {
			lstat (path_fname, &entry.stat);
			strcpy (entry.name, dname (directentry));
			entry.options = options;
			if (!pool_write (p, (unsigned char *) &entry, sizeof (entry))) {
			    pool_free (p);
			    closedir (dir);
			    return 0;
			}
			numentries++;
		    }
		}
	    }
	}
    }

    memset (&entry, 0, sizeof (entry));
    entry.options = FILELIST_LAST_ENTRY;
    if (!pool_write (p, (unsigned char *) &entry, sizeof (entry))) {
	pool_free (p);
	closedir (dir);
	return 0;
    }
    list = (struct file_entry *) pool_break (p);

    qsort((void *) list, numentries, sizeof (struct file_entry), (int (*) (const void *, const void *)) compare_fileentries);

    closedir (dir);
    return list;
}


static char *get_a_line (void *data, int line)
{
    char **s;
    s = (char **) data;
    return s[line];
}

/* generate a list of search results, and query the user if the list is
longer that one: */
static char *do_user_file_list_search (Window parent, int x, int y, int lines, int columns, char *file_list, const char *base_name)
{
    char *p = file_list, *ret = NULL;
    char **l = NULL;
    int list_len = 0, item, i;
    if (!file_list)
	return NULL;
    while ((p = strstr (p, base_name))) {
	char left_word_border, right_word_border;
	left_word_border = (p > file_list) ? *(p - 1) : '\n';
	right_word_border = *(p + strlen (base_name));
	if (left_word_border == '/' && (right_word_border == '\n' || right_word_border == '\0')) {
	    char *eol, *bol, *r;
	    eol = p + strlen (base_name);
	    for (bol = p; bol > file_list && *(bol - 1) != '\n'; bol--);
	    r = (char *) malloc ((int) (eol - bol + 1));
	    strncpy (r, bol, (int) (eol - bol));
	    r[(int) (eol - bol)] = '\0';
	    list_len++;
	    l = (char **) realloc (l, sizeof (char *) * (list_len + 1));
	    l[list_len - 1] = r;
	    l[list_len] = NULL;
	    p = eol;
	    if (!*p)
		break;
	}
	p++;
	if (!*p)
	    break;
    }
    if (!list_len)
	return NULL;
    if (list_len == 1)
	item = 0;
    else
	item = CListboxDialog (parent, 20, 20, 60, list_len >= 15 ? 14 : list_len + 1,
			       _("Multiple Files Found - Please Select"), 0, 0, list_len, get_a_line,
			       (void *) l);
/* free all list entries except the one we are returning: */
    for (i = 0; i < list_len; i++) {
	if (i == item)
	    ret = l[i];
	else
	    free (l[i]);
    }
    free (l);
    return ret;
}

/* generate a list of search results, and query the user if the list is
longer that one: */
static char *do_user_file_list_complete (Window parent, int x, int y, int lines, int columns, char *file_list,
					 const char *search_str)
{
    POOL *pool;
    int c;
    char *p, *t, *r;
    pool = pool_init ();
    if (!file_list)
	return NULL;
    if (strlen (search_str) < 2)
	return NULL;
/* list files starting with the text string first */
    for (c = 0; c < 2; c++) {
	p = file_list;
	while ((p = strstr (p, search_str))) {
	    char *eol, *bol;
	    char left_word_border, right_word_border;
	    left_word_border = (p > file_list) ? *(p - 1) : '\n';
	    right_word_border = *(p + strcspn (p, "/\n"));
	    eol = p + strcspn (p, "\n");
	    for (bol = p; bol > file_list && *(bol - 1) != '\n'; bol--);
	    if ((left_word_border == '\n' || (left_word_border == '/' && right_word_border != '/')) ^ c) {
		pool_write (pool, (unsigned char *) bol, (int) (eol - bol));
		pool_write (pool, "\n", 1);
	    }
	    p = eol;
	    if (!*p)
		break;
	    p++;
	    if (!*p)
		break;
	}
    }
    pool_null (pool);
    t = (char *) pool_break (pool);
    r = CTrivialSelectionDialog (parent, x, y, lines, columns, t, 0, 0);
    free (t);
    return r;
}

static char *_user_file_list_search (Window parent, int x, int y, int lines, int columns,
				     const char *base_name, char *(*do_dialog) (Window, int, int, int, int,
										char *, const char *))
{
    static time_t last_stat_time = 0;
    static time_t last_change_time = 0;
    static char *whole_file = NULL;
    time_t t;
    struct stat st;
    if (!base_name)
	return NULL;
    time (&t);
    if (last_stat_time < t) {
	char *p;
	last_stat_time = t;
	p = (char *) malloc (strlen (home_dir) + strlen (FILELIST_FILE) + 2);
	strcpy (p, home_dir);
	strcat (p, FILELIST_FILE);
	if (stat (p, &st)) {
	    CErrorDialog (0, 0, 0, _(" Open Personal File List "),
			  get_sys_error (catstrs (_(" Error trying stat "), p, 0)));
	    free (p);
	    if (whole_file) {
		free (whole_file);
		whole_file = NULL;
	    }
	    return NULL;
	}
	if (last_change_time && last_change_time == st.st_mtime) {
	    free (p);
	    return (*do_dialog) (parent, x, y, lines, columns, whole_file, base_name);
	}
	last_change_time = st.st_mtime;
	if (whole_file)
	    free (whole_file);
	whole_file = loadfile (p, NULL);
	free (p);
	if (!whole_file)
	    return NULL;
    }
    return (*do_dialog) (parent, x, y, lines, columns, whole_file, base_name);
}

char *user_file_list_search (Window parent, int x, int y, const char *base_name)
{
    return _user_file_list_search (parent, x, y, 0, 0, base_name, do_user_file_list_search);
}

char *user_file_list_complete (Window parent, int x, int y, int lines, int columns, const char *search_str)
{
    return _user_file_list_search (parent, x, y, lines, columns, search_str, do_user_file_list_complete);
}
