/*
 * flist.c -- list nmh folders containg messages in a given sequence
 *
 * originally by
 * David Nichols, Xerox-PARC, November, 1992
 *
 * Copyright (c) 1994 Xerox Corporation.
 * Use and copying of this software and preparation of derivative works based
 * upon this software are permitted. Any distribution of this software or
 * derivative works must comply with all applicable United States export
 * control laws. This software is made available AS IS, and Xerox Corporation
 * makes no warranty about the software, its performance or its conformity to
 * any specification.
 *
 *  $Id$
 */

#include <h/mh.h>

#define FALSE   0
#define TRUE    1

static struct swit switches[] = {
#define SEQSW           0
    { "sequence name", 0 },
#define ALLSW           1
    { "all", 0 },
#define NOALLSW         2
    { "noall", 0 },
#define ALPHASW         3
    { "alpha", 0 },
#define NOALPHASW       4
    { "noalpha", 0 },
#define TOTAL           5
    { "total", 0 },
#define NOTOTAL         6
    { "nototal", 0 },
#define RECURSE         7
    { "recurse", 0 },
#define NORECURSE       8
    { "norecurse", 0 },
#define VERSIONSW	9
    { "version", 0 },
#define HELPSW          10
    { "help", 4 },
    { NULL, NULL }
};

struct Folder {
    char *name;		/* name of folder */
    int priority;
    int nMsgs;		/* number of messages in folder         */
    int nSeq;		/* number of messages in given sequence */
};

static struct Folder *orders = NULL;
static int nOrders = 0;
static int nOrdersAlloced = 0;
static struct Folder *folders = NULL;
static int nFolders = 0;
static int nFoldersAlloced = 0;

static char **foldersToDo;
static int nFoldersToDo;

static int allFolders = FALSE;	/* show all folders or just with given seq? */
static int alphaOrder = FALSE;	/* want alphabetical order only             */
static int recurse    = FALSE;	/* show nested folders?                     */
static int Total      = TRUE;	/* display info on number of messages in    *
				 * sequence found, and total num messages   */

static char *sequence;		/* name of sequence we are searching for    */

/*
 * Type for a compare function for qsort.  This keeps
 * the compiler happy.
 */
typedef int (*qsort_comp) (const void *, const void *);

/*
 * prototypes
 */
int CompareFolders(struct Folder *, struct Folder *);
void GetFolderOrder(void);
void ScanMailDir(void);
void AddFolder(char *, int);
void BuildFolderList(char *);
void PrintFolders(void);
void AllocFolders(struct Folder **, int *, int);


main(int argc, char **argv)
{
    char *cp;
    char **ap;
    char **argp;
    char **lastArg;
    char *arguments[MAXARGS];
    char buf[100];

    invo_name = r1bindex(argv[0], '/');
    if ((cp = m_find(invo_name)) != NULL) {
	ap = brkstring(cp = getcpy(cp), " ", "\n");
	ap = copyip(ap, arguments);
    } else
	ap = arguments;
    lastArg = copyip(argv + 1, ap);
    argp = arguments;
    argc = lastArg - argp;
    foldersToDo = (char **) malloc(argc * sizeof(char *));
    nFoldersToDo = 0;

    while ((cp = *argp++)) {
	if (*cp == '-') {
	    switch (smatch(++cp, switches)) {
	    case AMBIGSW:
		ambigsw(cp, switches);
		done(1);
	    case UNKWNSW:
		adios(NULL, "-%s unknown", cp);

	    case HELPSW:
		sprintf(buf, "%s [+folder1 [+folder2 ...]][switches]", invo_name);
		print_help(buf, switches);
		done(1);
	    case VERSIONSW:
		print_version(invo_name);
		done (1);

	    case SEQSW:
		if (!(cp = *argp++) || *cp == '-')
		    adios (NULL, "missing argument to %s", argp[-2]);
		sequence = cp;
		break;
	    case ALLSW:
		allFolders = TRUE;
		break;
	    case NOALLSW:
		allFolders = FALSE;
		break;
	    case ALPHASW:
		alphaOrder = TRUE;
		break;
	    case NOALPHASW:
		alphaOrder = FALSE;
		break;
	    case TOTAL:
		Total = TRUE;
		break;
	    case NOTOTAL:
		Total = FALSE;
		break;
	    case RECURSE:
		recurse = TRUE;
		break;
	    case NORECURSE:
		recurse = FALSE;
		break;
	    }
	} else {
	    if (*cp == '+')
		++cp;
	    foldersToDo[nFoldersToDo++] = cp;
	}
    }

    /*
     * If we didn't specify a sequence, we search
     * for the `unseen' sequence.
     */
    if (!sequence)
	sequence = m_find("Unseen-Sequence");

    GetFolderOrder();
    ScanMailDir();
    qsort(folders, nFolders, sizeof(struct Folder), (qsort_comp) CompareFolders);
    PrintFolders();
}

/*
 * Read the Flist-Order profile entry to determine
 * how to sort folders for output.
 */

void
GetFolderOrder(void)
{
    char *p, *s;
    int priority = 1;
    struct Folder *o;

    if (!(p = m_find("Flist-Order")))
	return;
    for (;;) {
	while (isspace(*p))
	    ++p;
	s = p;
	while (*p && !isspace(*p))
	    ++p;
	if (p != s) {
	    /* Found one. */
	    AllocFolders(&orders, &nOrdersAlloced, nOrders + 1);
	    o = &orders[nOrders++];
	    o->priority = priority++;
	    o->name = (char *) malloc(p - s + 1);
	    strncpy(o->name, s, p - s);
	    o->name[p - s] = 0;
	} else
	    break;
    }
}

/*
 * Scan the given list of folders, or the top
 * level of our Mail directory.
 */

void
ScanMailDir(void)
{
    int i;
    char *home, *path;
    DIR *dir;
    struct dirent *dp;

    /*
     * Did we specify which folders to check?
     */
    if (nFoldersToDo > 0) {
	for (i = 0; i < nFoldersToDo; ++i)
	    BuildFolderList(foldersToDo[i]);
	return;
    }

    /*
     * Else, scan the top level of the Mail directory
     */
    if (!(home = getenv("HOME")) || (chdir(home) < 0))
	adios(NULL, "Can't change to home directory.");
    if (!(path = m_find("path")))
	adios(NULL, "Can't find Mail directory.");
    if (!(dir = opendir(path)))
	adios(NULL, "Can't open Path");

    while ((dp = readdir(dir))) {
	if (dp->d_name[0] == '.')
	    continue;
	BuildFolderList(dp->d_name);
    }
}

/*
 * Build list of folders
 */

void
BuildFolderList(char *dirName)
{
    DIR *dir;
    struct dirent *dp;
    struct stat s;
    char name[PATH_MAX];

    if (stat(m_mailpath(dirName), &s) == -1)
	return;

    /*
     * If we have a directory, then add it to
     * our list of folders, else we return.
     */
    if (S_ISDIR(s.st_mode))
	AddFolder(dirName, allFolders);
    else
	return;

    /*
     * If we are not recursing, or directory has no
     * subdirectories, then we can return.
     */
    if (!recurse || s.st_nlink == 2)
	return;

    if (!(dir = opendir(m_mailpath(dirName))))
	adios(NULL, "Error while scanning Mail directory");

    while ((dp = readdir(dir))) {
	if (dp->d_name[0] == '.')
	    continue;
	strcpy(name, dirName);
	if (*dirName)
	    strcat(name, "/");
	strcat(name, dp->d_name);
	BuildFolderList(name);
    }
    closedir(dir);
}

/*
 * Add this folder to our list, counting the
 * number of messages in given sequence and
 * total number of messages.
 */

void
AddFolder(char *name, int force)
{
    char buf[1024];
    int i, flags, nSeq;
    struct stat s;
    struct Folder *f;
    struct msgs *mp;

#if 0
    /*
     * Attempt a shortcut to avoid reading this directory
     * if we're only looking for unseen messages, by
     * checking if a sequence file exists in this folder.
     */
    if (!force) {
	sprintf(buf, "%s/%s", name, mh_seq);
	if (stat(buf, &s) < 0)
	    return;
    }
#endif

    nSeq = 0;
    if (!(mp = m_gmsg(name))) {
	advise(NULL, "Can't read folder %s.", name);
	return;
    }

    /*
     * Get the bit flag corresponding to the
     * sequence we want to search for.
     */
    if (sequence)
	flags = m_seqflag(mp, sequence);
    else
	flags = 0;

    if (flags) {
	for (i = mp->lowmsg; i <= mp->hghmsg; i++) {
	    if (mp->msgstats[i] & flags)
		nSeq++;
	}
    }

    /*
     * Save information in Folder struct.
     */
    if (nSeq > 0 || force) {
	AllocFolders(&folders, &nFoldersAlloced, nFolders + 1);
	f = &folders[nFolders++];
	f->name = getcpy(name);
	f->nMsgs = mp->nummsg;
	f->nSeq = nSeq;
	f->priority = AssignPriority(f->name);
    }
}

void
PrintFolders(void)
{
    int i;

    for (i = 0; i < nFolders; ++i) {
	if (Total) {
	    printf("+%-12s %3d in sequence %s, out of %3d\n", folders[i].name,
		   folders[i].nSeq, sequence, folders[i].nMsgs);
	} else {
	    printf("%s\n", folders[i].name);
	}
    }
}

/*
 * Put them in priority order.
 */

int
CompareFolders(struct Folder *f1, struct Folder *f2)
{
    if (!alphaOrder && f1->priority != f2->priority)
	return f1->priority - f2->priority;
    else
	return strcmp(f1->name, f2->name);
}

/*
 * Make sure we have at least n folders allocated.
 */

void
AllocFolders(struct Folder **f, int *nfa, int n)
{
    if (n <= *nfa)
	return;
    if (*f == NULL) {
	*nfa = 10;
	*f = (struct Folder *) malloc(*nfa * (sizeof(struct Folder)));
    } else {
	*nfa *= 2;
	*f = (struct Folder *) realloc(*f, *nfa * (sizeof(struct Folder)));
    }
}

/*
 * Return the priority for a name.  The highest comes from an exact match.
 * After that, the longest match (then first) assigns the priority.
 */
int
AssignPriority(char *name)
{
    int i, ol, nl;
    int best = nOrders;
    int bestLen = 0;
    struct Folder *o;

    nl = strlen(name);
    for (i = 0; i < nOrders; ++i) {
	o = &orders[i];
	if (!strcmp(name, o->name))
	    return o->priority;
	ol = strlen(o->name);
	if (nl < ol - 1)
	    continue;
	if (ol < bestLen)
	    continue;
	if (o->name[0] == '*' && !strcmp(o->name + 1, name + (nl - ol + 1))) {
	    best = o->priority;
	    bestLen = ol;
	} else if (o->name[ol - 1] == '*' && strncmp(o->name, name, ol - 1) == 0) {
	    best = o->priority;
	    bestLen = ol;
	}
    }
    return best;
}
