
/*
 * folder(s).c -- set/list the current message and/or folder
 *             -- push/pop a folder onto/from the folder stack
 *             -- list the folder stack
 *
 * $Id$
 */

#include <h/mh.h>
#include <errno.h>

static struct swit switches[] = {
#define	ALLSW           0
    { "all", 0 },
#define	CREATSW         1
    { "create", 0 },
#define	NCREATSW        2
    { "nocreate", 0 },
#define	FASTSW          3
    { "fast", 0 },
#define	NFASTSW         4
    { "nofast", 0 },
#define	HDRSW           5
    { "header", 0 },
#define	NHDRSW          6
    { "noheader", 0 },
#define	PACKSW          7
    { "pack", 0 },
#define	NPACKSW         8
    { "nopack", 0 },
#define	VERBSW          9
    { "verbose", 0 },
#define	NVERBSW        10
    { "noverbose", 0 },
#define	RECURSW        11
    { "recurse", 0 },
#define	NRECRSW        12
    { "norecurse", 0 },
#define	TOTALSW        13
    { "total", 0 },
#define	NTOTLSW        14
    { "nototal", 0 },
#define	PRNTSW         15
    { "print", 0 },
#define	NPRNTSW        16
    { "noprint", -4 },
#define	LISTSW         17
    { "list", 0 },
#define	NLISTSW        18
    { "nolist", 0 },
#define	PUSHSW         19
    { "push", 0 },
#define	POPSW          20
    { "pop", 0 },
#define VERSIONSW      21
    { "version", 0 },
#define	HELPSW         22
    { "help", 4 },
    { NULL, 0 }
};

extern int errno;

static int fshort        = 0;	/* output only folder names                 */
static int fcreat        = 0;	/* should we ask to create new folders?     */
static int fpack         = 0;	/* are we packing the folder?               */
static int fverb         = 0;	/* print actions taken while packing folder */
static int fheader       = 0;	/* should we output a header?               */
static int frecurse      = 0;	/* recurse through subfolders               */
static int ftotonly      = 0;	/* should we only output the totals?        */
static int total_msgs    = 0;	/* total number of messages                 */
static int total_folders = 0;	/* total number of folders                  */

static int start    = 0;
static int foldp    = 0;

static char *mhdir;
static char *stack = "Folder-Stack";
static char folder[BUFSIZ];

#define NUMFOLDERS 256

/*
 * This is how many folders we currently can hold in the array
 * `folds'.  We increase this amount by NUMFOLDERS at a time.
 */
static int maxfolders;
static char **folds;

struct Folder {
    char *name;
    int nummsg;
    int curmsg;
    int lowmsg;
    int hghmsg;
    int others;		/* others == 1 if other files in folder */
    int error;		/* error == 1 for unreadable folder     */
};

static struct Folder fi;

/*
 * static prototypes
 */
static void dodir (char *);
static int get_folder_info (char *, char *);
static void print_header (void);
static void print_folder_info (struct Folder *, char *);
static void print_totals (void);
static int sfold (struct msgs *, char *);
static void addir (char *);
static void addfold (char *);
static int compare (char *, char *);
static void do_readonly (void);


int
main (int argc, char **argv)
{
    int all = 0, printsw = 0, listsw = 0;
    int pushsw = 0, popsw = 0;
    char *cp, *dp, *msg = NULL, *argfolder = NULL;
    char **ap, **argp, buf[100], *arguments[MAXARGS];
    struct stat st;

#ifdef LOCALE
    setlocale(LC_ALL, "");
#endif
    invo_name = r1bindex (argv[0], '/');
    if (argv[0][strlen (argv[0]) - 1] == 's')
	all = 1;
    if ((cp = m_find (invo_name))) {
	ap = brkstring (cp = getcpy (cp), " ", "\n");
	ap = copyip (ap, arguments);
    }
    else
	ap = arguments;
    copyip (argv + 1, ap);
    argp = arguments;

    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 [+folder] [msg] [switches]", invo_name);
		    print_help (buf, switches);
		    done (1);
		case VERSIONSW:
		    print_version(invo_name);
		    done (1);

		case ALLSW: 
		    all = 1;
		    continue;

		case CREATSW: 
		    fcreat = 1;
		    continue;
		case NCREATSW: 
		    fcreat = -1;
		    continue;

		case FASTSW: 
		    fshort++;
		    continue;
		case NFASTSW: 
		    fshort = 0;
		    continue;

		case HDRSW: 
		    fheader = 1;
		    continue;
		case NHDRSW: 
		    fheader = -1;
		    continue;

		case PACKSW: 
		    fpack++;
		    continue;
		case NPACKSW: 
		    fpack = 0;
		    continue;

		case VERBSW:
		    fverb++;
		    continue;
		case NVERBSW:
		    fverb = 0;
		    continue;

		case RECURSW: 
		    frecurse++;
		    continue;
		case NRECRSW: 
		    frecurse = 0;
		    continue;

		case TOTALSW: 
		    ftotonly = 1;
		    all = 1;
		    continue;
		case NTOTLSW: 
		    ftotonly = 0;
		    continue;

		case PRNTSW: 
		    printsw = 1;
		    continue;
		case NPRNTSW: 
		    printsw = 0;
		    continue;

		case LISTSW: 
		    listsw = 1;
		    continue;
		case NLISTSW: 
		    listsw = 0;
		    continue;

		case PUSHSW: 
		    pushsw = 1;
		    listsw = 1;
		    popsw  = 0;
		    continue;
		case POPSW: 
		    popsw  = 1;
		    listsw = 1;
		    pushsw = 0;
		    continue;
	    }
	if (*cp == '+' || *cp == '@')
	    if (argfolder)
		adios (NULL, "only one folder at a time!");
	    else
		argfolder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
	else
	    if (msg)
		adios (NULL, "only one (current) message at a time!");
	    else
		msg = cp;
    }

    if (!m_find ("path"))
	free (path ("./", TFOLDER));
    mhdir = concat (m_maildir (""), "/", NULL);

    /*
     * If we aren't working with the folder stack
     * (-push, -pop, -list) then the default is to print.
     */
    if (pushsw == 0 && popsw == 0 && listsw == 0)
	printsw++;

    /* Pushing a folder onto the folder stack */
    if (pushsw) {
	if (!argfolder) {
	    /* If no folder is given, the current folder and */
	    /* the top of the folder stack are swapped.      */
	    if ((cp = m_find (stack))) {
		dp = getcpy (cp);
		ap = brkstring (dp, " ", "\n");
		argfolder = getcpy(*ap++);
	    } else {
		adios (NULL, "no other folder");
	    }
	    for (cp = getcpy (m_getfolder ()); *ap; ap++)
		cp = add (*ap, add (" ", cp));
	    free (dp);
	    m_replace (stack, cp);	/* update folder stack */
	} else {
	    /* update folder stack */
	    m_replace (stack,
		    (cp = m_find (stack))
		    ? concat (m_getfolder (), " ", cp, NULL)
		    : getcpy (m_getfolder ()));
	}
    }

    /* Popping a folder off of the folder stack */
    if (popsw) {
	if (argfolder)
	    adios (NULL, "sorry, no folders allowed with -pop");
	if ((cp = m_find (stack))) {
	    dp = getcpy (cp);
	    ap = brkstring (dp, " ", "\n");
	    argfolder = getcpy(*ap++);
	} else {
	    adios (NULL, "folder stack empty");
	}
	if (*ap) {
	    /* if there's anything left in the stack */
	    cp = getcpy (*ap++);
	    for (; *ap; ap++)
		cp = add (*ap, add (" ", cp));
	    m_replace (stack, cp);		/* update folder stack */
	} else {
	    m_delete (stack);	/* delete folder stack entry from context */
	}
	free (dp);
    }
    if (pushsw || popsw) {
	cp = m_maildir(argfolder);
	if (access (cp, F_OK) == NOTOK)
	    adios (cp, "unable to find folder");
	m_replace (pfolder, argfolder);	/* update current folder   */
	m_update ();			/* update the context file */
	argfolder = NULL;
    }

    /* Listing the folder stack */
    if (listsw) {
	printf ("%s", argfolder ? argfolder : m_getfolder ());
	if ((cp = m_find (stack))) {
	    dp = getcpy (cp);
	    for (ap = brkstring (dp, " ", "\n"); *ap; ap++)
		printf (" %s", *ap);
	    free (dp);
	}
	printf ("\n");

	if (!printsw)
	    done (0);
    }

    /* Allocate initial space to record folder names */
    maxfolders = NUMFOLDERS;
    if ((folds = malloc (maxfolders * sizeof(char *))) == NULL)
	adios (NULL, "unable to allocate storage for folder names");

    /* Do all folders at either top or first level */
    if (all) {
	if (!fshort && !ftotonly && (fheader >= 0))
	    print_header();
	if (argfolder) {
	    strcpy (folder, argfolder);
	    if (get_folder_info (argfolder, msg)) {
		m_replace (pfolder, argfolder);	/* update current folder   */
		m_update ();			/* update the context file */
	    }
	    if (!frecurse)	/* recurse wasn't done in get_folder_info(), */
		dodir (folder);	/* so just list all level-1 sub-folders      */
	} else {
	    /* no folder given, so do them all */
	    if (msg)
		admonish (NULL, "no folder given for message %s", msg);
	    do_readonly ();
	    strcpy (folder, (cp = m_find (pfolder)) ? cp : "");
	    dodir (".");
	}
	if (!fshort)
	    print_totals();	/* print folder/message totals */

    } else {
	if (!fshort && (fheader > 0))
	    print_header();
	strcpy (folder, argfolder ? argfolder : m_getfolder ());

	/*
	 * Check if folder exists.  If not, then see if
	 * we should create it, or just exit.
	 */
	if (stat (strcpy (buf, m_maildir (folder)), &st) == NOTOK) {
	    if (errno != ENOENT)
		adios (buf, "error on folder");
	    if (fcreat == 0) {
		/* ask before creating folder */
		cp = concat ("Create folder \"", buf, "\"? ", NULL);
		if (!getanswer (cp))
		    done (1);
		free (cp);
	    } else if (fcreat == -1) {
		/* do not create, so exit */
		done (1);
	    }
	    if (!makedir (buf))
		adios (NULL, "unable to create folder %s", buf);
	}

	if (get_folder_info (folder, msg) && argfolder)
	    m_replace (pfolder, argfolder);	/* update current folder */
    }

    m_update ();	/* update the context file */
    done (0);
}

static void
dodir (char *dir)
{
    int i;
    int os = start;
    int of = foldp;
    char buffer[BUFSIZ];

    start = foldp;
    if (chdir (mhdir) == NOTOK)
	adios (mhdir, "unable to change directory to");

    addir (strcpy (buffer, dir));
    for (i = start; i < foldp; i++) {
	get_folder_info (folds[i], NULL);
	fflush (stdout);
    }

    start = os;
    foldp = of;
}

static int
get_folder_info (char *fold, char *msg)
{
    int	retval = 1;
    register char *mailfile;
    register struct msgs *mp = NULL;

    fi.name = fold;
    fi.error = 0;

    mailfile = m_maildir (fold);
    if (chdir (mailfile) == 0) {
	if (!fshort || msg || fpack) {
	    /*
	     * create message structure and get folder info
	     */
	    if (!(mp = m_readfolder (fold))) {
		admonish (NULL, "unable to read folder %s", fold);
		return 0;
	    }

	    if (msg && !sfold (mp, msg))
		retval = 0;

	    if (fpack) {
		mp = m_packfolder (mp, fverb);
		seq_save (mp);	/* synchronize the sequences */
		m_update ();	/* update the context file   */
	    }

	    if (!fshort) {
		total_folders++;
		total_msgs += mp->nummsg;

		fi.nummsg = mp->nummsg;
		fi.curmsg = mp->curmsg;
		fi.lowmsg = mp->lowmsg;
		fi.hghmsg = mp->hghmsg;
		fi.others = other_files (mp);
	    }

	    /* free folder/message structure */
	    m_freefolder (mp);
	}
    } else {
	fi.error = 1;
    }

    if (!ftotonly || fshort || fi.error) {
	/* print summary of folder */
	print_folder_info (&fi, fold);
    }

    if (frecurse && (fshort || fi.others) && (fi.error == 0))
	dodir (fold);
    return retval;
}


/*
 * Print the header
 */

static void
print_header (void)
{
    printf ("\t\tFolder  %*s# of messages (%*srange%*s); cur%*smsg  (other files)\n",
	DMAXFOLDER, "", DMAXFOLDER - 2, "", DMAXFOLDER - 2, "", DMAXFOLDER - 2, "");
}

/*
 * Print summary of folder information
 */

static void
print_folder_info (struct Folder *fi, char *fold)
{
    int hack = 0;

    if (fi->error) {
	printf ("%22s%c is unreadable\n", fold, strcmp (folder, fold) ? ' ' : '+');
	return;
    }

    if (fshort) {
	printf ("%s\n", fold);
	return;
    }

    printf ("%22s%c ", fold, strcmp (folder, fold) ? ' ' : '+');

    if (fi->hghmsg == 0)
	printf ("has   no messages%*s",
		fi->others ? DMAXFOLDER * 2 + 4 : 0, "");
    else {
	printf ("has %*d message%s (%*d-%*d)",
		DMAXFOLDER, fi->nummsg, (fi->nummsg == 1) ? " " : "s",
		DMAXFOLDER, fi->lowmsg, DMAXFOLDER, fi->hghmsg);
	if (fi->curmsg >= fi->lowmsg && fi->curmsg <= fi->hghmsg)
	    printf ("; cur=%*d", DMAXFOLDER, hack = fi->curmsg);
    }

    if (fi->others)
	printf (";%*s (others)", hack ? 0 : DMAXFOLDER + 6, "");
    printf (".\n");
}

/*
 * Print folder/message totals
 */

static void
print_totals (void)
{
    if (!ftotonly)
	printf ("\n\t\t     ");
    printf ("TOTAL= %*d message%c in %d folder%s.\n",
	DMAXFOLDER, total_msgs, total_msgs != 1 ? 's' : ' ',
	total_folders, total_folders != 1 ? "s" : "");
}

/*
 * Set the current message and sychronize sequences
 */

static int
sfold (struct msgs *mp, char *msg)
{
    /* parse the message range/sequence/name and set SELECTED */
    if (!m_convert (mp, msg))
	return 0;

    if (mp->numsel > 1) {
	admonish (NULL, "only one message at a time!");
	return 0;
    }
    seq_setprev (mp);		/* set the previous-sequence     */
    seq_setcur (mp, mp->lowsel);/* set current message           */
    seq_save (mp);		/* synchronize message sequences */
    m_update ();		/* update the context file       */

    return 1;
}


static void
addir (char *name)
{
    register char *base, *cp;
    struct stat st;
    register struct dirent *dp;
    register DIR * dd;

    cp = name + strlen (name);
    *cp++ = '/';
    *cp = '\0';

    base = strcmp (name, "./") ? name : name + 2; /* hack */

   /* short-cut to see if directory has any sub-directories */
    if (stat (name, &st) != NOTOK && st.st_nlink == 2)
        return;
 
    if (!(dd = opendir (name))) {
	admonish (name, "unable to read directory ");
	return;
    }
    while ((dp = readdir (dd)))
	if (strcmp (dp->d_name, ".") && strcmp (dp->d_name, "..")) {
	    if (cp + NLENGTH(dp) + 2 >= name + BUFSIZ)
		continue;
	    strcpy (cp, dp->d_name);
	    if (stat (name, &st) != NOTOK && S_ISDIR(st.st_mode))
		addfold (base);
	}
    closedir (dd);

    *--cp = '\0';
}


static void
addfold (char *fold)
{
    register int i, j;
    register char *cp;

    /* if necessary, reallocate the space for folder names */
    if (foldp >= maxfolders) {
	maxfolders += NUMFOLDERS;
	if ((folds = realloc (folds, maxfolders * sizeof(char *))) == NULL)
	    adios (NULL, "unable to re-allocate storage for folder names");
    }

    cp = getcpy (fold);
    for (i = start; i < foldp; i++)
	if (compare (cp, folds[i]) < 0) {
	    for (j = foldp - 1; j >= i; j--)
		folds[j + 1] = folds[j];
	    foldp++;
	    folds[i] = cp;
	    return;
	}

    folds[foldp++] = cp;
}


static int
compare (char *s1, char *s2)
{
    register int i;

    while (*s1 || *s2)
	if ((i = *s1++ - *s2++))
	    return i;

    return 0;
}

/*
 * Do the read only folders
 */

static void
do_readonly (void)
{
    int	atrlen;
    char atrcur[BUFSIZ];
    register struct node *np;

    sprintf (atrcur, "atr-%s-", current);
    atrlen = strlen (atrcur);

    m_getdefs ();
    for (np = m_defs; np; np = np->n_next)
	if (ssequal (atrcur, np->n_name)
		&& !ssequal (mhdir, np->n_name + atrlen))
	    get_folder_info (np->n_name + atrlen, NULL);
}
