/* $Cambridge: hermes/src/prayer/session/msgmap.c,v 1.3 2008/09/16 09:59:58 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

#include "prayer_session.h"

/* msgmap is general abstraction for range of messages
 *   1 -> msgmap_size(zm), encompassing zoom and sort options.
 *
 * We borrow a couple of spare bits from c-client:
 *
 *  elt->spare   is used to record persistent mark
 *  elt->spare2  is used to record transient mark if use_mark_persist disabled
 *
 */

/* msgmap_create() *****************************************************
 *
 * Create fresh msgmap structure
 ***********************************************************************/

struct msgmap *msgmap_create(struct pool *pool)
{
    struct msgmap *z = pool_alloc(pool, sizeof(struct msgmap));

    z->session = NIL;
    z->stream = NIL;
    z->nmsgs = NIL;
    z->uid_last = NIL;
    z->marked = 0;
    z->tmp_marked = 0;
    z->sorted = memblock_create(pool, 64 * sizeof(unsigned long));
    z->depths = memblock_create(pool, 64 * sizeof(unsigned long));
    z->subset = memblock_create(pool, 64 * sizeof(unsigned long));
    z->sort_mode = ARRIVAL;
    z->sort_reverse = NIL;
    z->valid = NIL;
    z->zoom = NIL;

    return (z);
}

/* msgmap_free() ********************************************************
 *
 * Free msgmap
 ************************************************************************/

void msgmap_free(struct msgmap *z)
{
    memblock_free(z->sorted);
    memblock_free(z->depths);
    memblock_free(z->subset);
}

/* msgmap_enable_zoom() ************************************************
 *
 * Enable zoom in msgmap
 ***********************************************************************/

void msgmap_enable_zoom(struct msgmap *z)
{
    z->zoom = T;
    msgmap_invalidate_zoom(z);  /* Need to construct zoomed map */
}

/* msgmap_disable_zoom() ***********************************************
 *
 * Disable zoom in msgmap
 ***********************************************************************/

void msgmap_disable_zoom(struct msgmap *z)
{
    z->zoom = NIL;
    msgmap_invalidate_zoom(z);  /* Need to construct unzoomed map */
}

/* msgmap_have_zoom() **************************************************
 *
 * Check whether zoom currently enabled.
 ***********************************************************************/

BOOL msgmap_have_zoom(struct msgmap *z)
{
    return (z->zoom);
}

/* ====================================================================== */

/* msgmap_sort_mode() **************************************************
 *
 * Change current sort mode for msgmap.
 *         z: Zoommap
 * sort_mode: Sort mode (e.g: FROM, TO)
 *            Full list defined by c-client library
 ***********************************************************************/

void msgmap_sort_mode(struct msgmap *z, SORTMODE sort_mode)
{
    z->sort_mode = sort_mode;
    msgmap_invalidate(z);
}

/* msgmap_current_sort_mode() *******************************************
 *
 * Check current sort mode.
 ***********************************************************************/

SORTMODE msgmap_current_sort_mode(struct msgmap *z)
{
    return (z->sort_mode);
}

/* msgmap_sort_reverse_enable() ****************************************
 *
 * Enable reverse sort
 ***********************************************************************/

void msgmap_sort_reverse_enable(struct msgmap *z)
{
    z->sort_reverse = T;
    msgmap_invalidate(z);
}

/* msgmap_sort_reverse_disable() ***************************************
 *
 * Disable reverse sort
 ***********************************************************************/

void msgmap_sort_reverse_disable(struct msgmap *z)
{
    z->sort_reverse = NIL;
    msgmap_invalidate(z);
}

/* msgmap_sort_reverse() ***********************************************
 *
 * Check whether reverse currently enabled.
 ***********************************************************************/

BOOL msgmap_sort_reverse(struct msgmap *z)
{
    return (z->sort_reverse);
}

/* ====================================================================== */

/* msgmap_associate() **************************************************
 *
 * Associate msgmap with stream. Important that this happen whenever
 * current MAILSTREAM changes.
 ***********************************************************************/

BOOL
msgmap_associate(struct msgmap * z,
                 struct session * session, MAILSTREAM * stream)
{
    z->session = session;
    z->stream = stream;
    z->nmsgs = stream->nmsgs;
    z->uid_last = stream->uid_last;
    z->zoom = NIL;
    z->sort_mode = session->options->prefs->sort_mode;
    z->sort_reverse = session->options->prefs->sort_reverse;

    msgmap_invalidate(z);
    msgmap_unmark_all(z);       /* Clear out persistent marks? */

    return (T);
}

/* ====================================================================== */

/* Local data structure, used for to flatten threads */

struct msgmap_thread {
    unsigned long *msgs;
    unsigned long *depths;
    BOOL *dups;
    unsigned long offset;
    unsigned long max_offset;
};

/* sort_thread_flatten() *************************************************
 *
 * Convert c-client thread output into pair of lists: message numbers and
 * depths. Recursive routine which pipes to preassigned output structure.
 *
 *    node: tree to flatten
 *  output: Output arrays from flatten procedure
 *   depth: Current tree depth.
 ************************************************************************/

static BOOL
sort_thread_flatten(THREADNODE * node,
                    struct msgmap_thread *output, unsigned long depth)
{
    if (!node)
        return (T);

    /* Check for buffer overrun */
    if (output->offset > output->max_offset)
        return (NIL);

    /* Check for holes and duplicates */
    if (node->num && !output->dups[node->num]) {
        unsigned long offset = output->offset++;

        output->dups[node->num] = T;
        output->msgs[offset] = node->num;
        output->depths[offset] = depth;
    }

    if (node->next) {
        if (!sort_thread_flatten(node->next, output, depth + 1))
            return (NIL);
    }

    if (node->branch) {
        if (!sort_thread_flatten(node->branch, output, depth))
            return (NIL);
    }

    return (T);
}

/* ====================================================================== */

/* msgmap_make_threaded() **********************************************
 *
 * Update msgmap with zoom mode disabled. (Static utility function).
 ***********************************************************************/

static BOOL msgmap_make_threaded(struct msgmap *z)
{
    MAILSTREAM *stream = z->stream;
    struct session *session = z->session;
    unsigned long nmsgs = z->nmsgs;
    struct request *request = session->request;
    struct pool *pool = request->pool;
    unsigned long i;
    unsigned long *sorted, *depths;
    SEARCHPGM *search_pgm;
    THREADNODE *thread;
    unsigned long msgno;
    char *type;
    struct msgmap_thread *output;

    memblock_resize(z->sorted, (nmsgs + 1) * sizeof(unsigned long));
    sorted = (unsigned long *) memblock_data(z->sorted);

    memblock_resize(z->depths, (nmsgs + 1) * sizeof(unsigned long));
    depths = (unsigned long *) memblock_data(z->depths);

    /* Special case for empty mailbox */
    if (nmsgs == 0)
        return (T);

    if (z->sort_mode == REFERENCES)
        type = "REFERENCES";
    else
        type = "ORDEREDSUBJECT";

    search_pgm = mail_newsearchpgm();   /* make a basic search program */

    /* XXX Charset */
    if (!(thread = mail_thread(stream, type, NIL, search_pgm, SE_FREE))) {
        z->valid = NIL;
        return (NIL);
    }

    /* Setup output structure for thread */
    output = pool_alloc(pool, sizeof(struct msgmap_thread));
    output->dups = pool_alloc(pool, (1 + nmsgs) * sizeof(unsigned long));
    output->offset = 1;
    output->max_offset = nmsgs;

    for (msgno = 1; msgno <= nmsgs; msgno++)
        output->dups[msgno] = NIL;

    if (z->sort_reverse) {
        /* Need temporary buffers for reverse case */
        output->msgs =
            pool_alloc(pool, (1 + nmsgs) * sizeof(unsigned long));
        output->depths =
            pool_alloc(pool, (1 + nmsgs) * sizeof(unsigned long));

        (void) sort_thread_flatten(thread, output, 0);
        mail_free_threadnode(&thread);

        if (z->sort_reverse) {
            for (i = 1; i <= nmsgs; i++) {
                sorted[i] = output->msgs[1 + nmsgs - i];
                depths[i] = output->depths[1 + nmsgs - i];
            }
        }
        return (T);
    }

    /* non-reverse: Flatten straight into memblocks */
    output->msgs = sorted;
    output->depths = depths;

    (void) sort_thread_flatten(thread, output, 0);
    mail_free_threadnode(&thread);
    return (T);
}

/* msgmap_make_sorted() ************************************************
 *
 * Update msgmap with zoom mode disabled. (Static utility function).
 ***********************************************************************/

static BOOL msgmap_make_sorted(struct msgmap *z)
{
    unsigned long i;
    unsigned long *sorted, *tmp, *depths;
    MAILSTREAM *stream = z->stream;
    unsigned long nmsgs = z->nmsgs;
    SEARCHPGM *search_pgm;
    SORTPGM *sort_pgm;

    /* Special case optimisation for ARRIVAL */
    if ((z->sort_mode == ARRIVAL) && (z->sort_reverse == NIL))
        return (T);

    /* Special case optimisation for reverse ARRIVAL */
    if ((z->sort_mode == ARRIVAL) && z->sort_reverse) {
        memblock_resize(z->sorted, (nmsgs + 1) * sizeof(unsigned long));
        sorted = (unsigned long *) memblock_data(z->sorted);

        memblock_resize(z->depths, (nmsgs + 1) * sizeof(unsigned long));
        depths = (unsigned long *) memblock_data(z->depths);

        for (i = 1; i <= nmsgs; i++)    /* Reverse order */
            sorted[i] = 1 + nmsgs - i;
        return (T);
    }

    search_pgm = mail_newsearchpgm();   /* Basic search program */
    sort_pgm = mail_newsortpgm();

    switch (z->sort_mode) {
    case ARRIVAL:
        sort_pgm->function = SORTARRIVAL;
        break;
    case DATE:
        sort_pgm->function = SORTDATE;
        break;
    case FROM:
        sort_pgm->function = SORTFROM;
        break;
    case SUBJECT:
        sort_pgm->function = SORTSUBJECT;
        break;
    case TO:
        sort_pgm->function = TO;
        break;
    case CC:
        sort_pgm->function = CC;
        break;
    case SIZE:
        sort_pgm->function = SIZE;
        break;
    default:
        sort_pgm->function = SORTARRIVAL;
        break;
    }

    /* XXX Charset */
    if (!
        (tmp =
         mail_sort(stream, NIL, search_pgm, sort_pgm, SO_FREE | SE_FREE)))
    {
        z->valid = NIL;
        return (NIL);
    }

    memblock_resize(z->sorted, (nmsgs + 1) * sizeof(unsigned long));
    sorted = (unsigned long *) memblock_data(z->sorted);

    memblock_resize(z->depths, (nmsgs + 1) * sizeof(unsigned long));
    depths = (unsigned long *) memblock_data(z->depths);

    if (z->sort_reverse) {
        for (i = 1; i <= nmsgs; i++)
            sorted[i] = tmp[nmsgs - i];
    } else {
        for (i = 1; i <= nmsgs; i++)
            sorted[i] = tmp[i - 1];
    }
    fs_give((void **) &tmp);
    return (T);
}

/* ====================================================================== */

/* msgmap_make_subset() ************************************************
 *
 * Update msgmap with zoom mode enabled. (Static utility function).
 ***********************************************************************/

static BOOL msgmap_make_subset(struct msgmap *z)
{
    struct session *session = z->session;
    MAILSTREAM *stream = session->stream;
    MESSAGECACHE *elt;
    unsigned long *sorted, *subset;
    unsigned long i, offset, msgno;
    BOOL use_sorted = ((z->sort_mode != ARRIVAL) || (z->sort_reverse));

    /* Make sure that target memblocks large enough for msgmap */
    memblock_resize(z->subset, (1 + z->marked) * sizeof(unsigned long));

    sorted = (unsigned long *) memblock_data(z->sorted);
    subset = (unsigned long *) memblock_data(z->subset);

    offset = 0;
    for (i = 1; i <= z->nmsgs; i++) {
        msgno = (use_sorted) ? sorted[i] : i;

        if (!(elt = ml_elt(session, stream, msgno))) {
            session_message(session,
                            "Couldn't fetch msg when building msgmap");
            session_log(session,
                        ("[msgmap_update_subset] "
                         "Couldn't fetch msg when building msgmap"));
            return (NIL);
        }
        if (elt && elt->spare) {
            subset[++offset] = msgno;
            if (offset > z->marked)
                break;
        }
        elt->searched = NIL;
    }
    if (offset != z->marked) {
        session_message(session, "ASSERT: Zoom map size incorrect");
        session_log(session,
                    "[msgmap_update_subset] ASSERT: Zoom map size incorrect");
        return (NIL);
    }

    return (T);
}

/* ====================================================================== */

/* msgmap_update() *****************************************************
 *
 * Update msgmap: calls relevant internal function depending on whether
 * zoom mode is currently enabled.
 ***********************************************************************/

BOOL msgmap_update(struct msgmap * z)
{
    MAILSTREAM *stream = z->stream;

    /* following just a msgmap_check() */
    if ((stream->nmsgs != z->nmsgs) ||
        (stream->uid_last != z->uid_last) || ml_mailbox_update()) {
        z->valid = NIL;

        ml_clear_mailbox_update();
    }

    if (z->valid && z->valid_zoom)
        return (T);

    /* Stage 1: Record current message count and UIDlast in msgmap
     *          (as sort may add messages to the underlying MAILSTREAM */

    z->nmsgs = stream->nmsgs;
    z->uid_last = stream->uid_last;

    /* expunge event can reduce message count */
    msgmap_recalculate(z);

    /* Cancel zoom if nothing marked. */
    if (z->marked == 0)
        z->zoom = NIL;

    /* Stage 2: generate sorted map if required */
    if (!z->valid) {
        z->valid_zoom = NIL;
        if ((z->sort_mode == REFERENCES)
            || (z->sort_mode == ORDEREDSUBJECT)) {
            if (!msgmap_make_threaded(z))
                return (NIL);
        } else {
            if (!msgmap_make_sorted(z))
                return (NIL);
        }
    }

    /* Stage 3: Generate marked map from msgmap if required */
    if (!z->valid_zoom && z->zoom) {
        if (!msgmap_make_subset(z))
            return (NIL);
    }

    z->valid = T;
    z->valid_zoom = T;
    return (T);
}

/* ====================================================================== */

/* msgmap_find() ******************************************************
 *
 * Find message in msgmap.
 ***********************************************************************/

unsigned long msgmap_find(struct msgmap *z, unsigned long msgno)
{
    unsigned long *sorted = memblock_data(z->sorted);
    unsigned long *subset = memblock_data(z->subset);
    unsigned long i;

    if (z->zoom == NIL) {
        if ((z->sort_mode == ARRIVAL) && (z->sort_reverse == NIL))
            return (msgno);     /* Special case optimisation */

        for (i = 1; i <= z->nmsgs; i++)
            if (sorted[i] == msgno)
                return (i);

        return (z->nmsgs);
    }

    /* Look for message in subset */
    for (i = 1; i < z->marked; i++)
        if (subset[i] == msgno)
            return (i);

    /* Not found */
    return (z->marked);
}

/* msgmap_value() ******************************************************
 *
 * Zoommap lookup (with optimisation for ARRIVAL case)
 * 
 ***********************************************************************/

unsigned long msgmap_value(struct msgmap *z, unsigned long offset)
{
    unsigned long *sorted = memblock_data(z->sorted);
    unsigned long *marked = memblock_data(z->subset);

    if (offset == 0)
        return (0);             /* Should throw exception */

    if (z->zoom)
        return (marked[offset]);

    if ((z->sort_mode == ARRIVAL) && (z->sort_reverse == NIL))
        return (offset);

    return (sorted[offset]);
}

/* msgmap_find_undeleted() **********************************************
 *
 * Find "closest" undeleted message to msgno in msgmap.
 ***********************************************************************/

unsigned long msgmap_find_undeleted(struct msgmap *z, unsigned long msgno)
{
    struct session *session = z->session;
    MAILSTREAM *stream = session->stream;
    struct prefs *prefs = session->options->prefs;
    unsigned long zm_size = msgmap_size(z);
    unsigned long zm_offset;
    unsigned long first;
    MESSAGECACHE *elt;
    unsigned long i;

    /* Part 1: Is msgno out of range? */

    if (msgno > zm_size)
        msgno = zm_size;

    if (msgno == 0)
        return (0);

    zm_offset = msgmap_find(z, msgno);

    /* Part 2: Is msgno deleted? */
    if (!(elt = ml_elt(session, stream, msgmap_value(z, zm_offset))))
        fatal("ml_elt failed");

    /* No => return current */
    if (!elt->deleted)
        return (zm_offset);

    /* Part 3: Find first undeleted message starting at current page */
    first = zm_offset - ((zm_offset - 1) % prefs->msgs_per_page);

    for (i = first; i < zm_size; i++) {
        if (!(elt = ml_elt(session, stream, msgmap_value(z, i))))
            fatal("ml_elt failed");

        if (!elt->deleted)
            return (i);
    }

    /* Part 3: Final message in folder */
    return (zm_size);
}

/* msgmap_depth() ******************************************************
 *
 * Zoommap lookup depth (with optimisation for ARRIVAL case)
 * 
 ***********************************************************************/

unsigned long msgmap_depth(struct msgmap *z, unsigned long offset)
{
    unsigned long *sorted = memblock_data(z->sorted);
    unsigned long *depths = memblock_data(z->depths);

    if ((z->sort_mode != REFERENCES) && (z->sort_mode != ORDEREDSUBJECT))
        return (0);

    if (z->zoom)
        return (0);

    if ((z->sort_mode == ARRIVAL) && (z->sort_reverse == NIL))
        return (depths[offset]);
    else
        return (depths[sorted[offset]]);
}

/* msgmap_size() *******************************************************
 *
 * Return size of current msgmap.
 * 
 ***********************************************************************/

unsigned long msgmap_size(struct msgmap *z)
{
    return ((z->zoom) ? z->marked : z->nmsgs);
}

/* ====================================================================== */

/* msgmap_invalidate() *************************************************
 *
 * Mark msgmap as invalid.
 * 
 ***********************************************************************/

void msgmap_invalidate(struct msgmap *z)
{
    z->valid = NIL;
}

/* msgmap_invalidate_zoom() ********************************************
 *
 * Mark msgmap subset as invalid.
 ***********************************************************************/

void msgmap_invalidate_zoom(struct msgmap *z)
{
    z->valid_zoom = NIL;
}

/* msgmap_check() ******************************************************
 *
 * Mark msgmap as invalid if underlying stream has changed
 * 
 ***********************************************************************/

void msgmap_check(struct msgmap *z)
{
    if (z->valid == NIL)
        return;

    if ((z->stream->nmsgs != z->nmsgs) ||
        (z->stream->uid_last != z->uid_last) || ml_mailbox_update())
        z->valid = NIL;

    ml_clear_mailbox_update();
}

/* ====================================================================== */

/* msgmap_mark() *******************************************************
 *
 * Mark message in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_mark(struct msgmap *z, unsigned long msgno)
{
    MESSAGECACHE *elt;

    if (!z->session || !z->stream)
        return (NIL);

    if (!(elt = ml_elt(z->session, z->stream, msgno)))
        return (NIL);

    if (!elt->spare) {
        elt->spare = T;
        z->marked++;
        msgmap_invalidate(z);
        return (T);
    }

    return (NIL);
}

/* msgmap_unmark() ******************************************************
 *
 * Unmark message in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_unmark(struct msgmap * z, unsigned long msgno)
{
    MESSAGECACHE *elt;

    if (!z->session || !z->stream)
        return (NIL);

    if (!(elt = ml_elt(z->session, z->stream, msgno)))
        return (NIL);

    if (elt->spare) {
        elt->spare = NIL;
        z->marked--;
        msgmap_invalidate(z);
        return (T);
    }

    return (NIL);
}

/* msgmap_has_mark() *****************************************************
 *
 * Check whether nominated message is marked
 *     zm: zoomap
 *  msgno: Message to test
 ************************************************************************/

BOOL msgmap_has_mark(struct msgmap * z, unsigned long msgno)
{
    MESSAGECACHE *elt;

    if (!z->session || !z->stream)
        return (NIL);

    if (!(elt = ml_elt(z->session, z->stream, msgno)))
        return (NIL);

    return ((elt->spare) ? T : NIL);
}

/* msgmap_mark_all() ***************************************************
 *
 * Mark all messages in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_mark_all(struct msgmap * z)
{
    MESSAGECACHE *elt;
    unsigned long msgno;

    if (!z->session || !z->stream)
        return (NIL);

    for (msgno = 1; msgno <= z->nmsgs; msgno++) {
        if (!(elt = ml_elt(z->session, z->stream, msgno)))
            return (NIL);
        elt->spare = T;
    }

    z->marked = z->nmsgs;
    msgmap_invalidate(z);
    return (T);
}

/* msgmap_unmark_all() *************************************************
 *
 * Unmark all messages in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_unmark_all(struct msgmap * z)
{
    MESSAGECACHE *elt;
    unsigned long msgno;

    if (!z->session || !z->stream)
        return (NIL);

    for (msgno = 1; msgno <= z->nmsgs; msgno++) {
        if (!(elt = ml_elt(z->session, z->stream, msgno)))
            return (NIL);
        elt->spare = NIL;
    }

    z->marked = 0;
    msgmap_invalidate(z);
    return (T);
}

/* msgmap_marked_set() *************************************************
 *
 * Set current marked message count.
 * 
 ***********************************************************************/

void msgmap_marked_set(struct msgmap *z, unsigned long count)
{
    z->marked = count;
}

/* msgmap_marked_count() ***********************************************
 *
 * Return current marked message count.
 * 
 ***********************************************************************/

unsigned long msgmap_marked_count(struct msgmap *z)
{
    return (z->marked);
}

/* msgmap_mark_sequence() ***********************************************
 *
 * Generate a c-client sequence string for marked messages in a folder
 *
 * Returns: Sequence string
 ***********************************************************************/

char *msgmap_mark_sequence(struct msgmap *z)
{
    struct session *session = z->session;
    struct pool *pool = session->request->pool;
    MAILSTREAM *stream = z->stream;
    char *seq, *p;
    unsigned long current;
    MESSAGECACHE *elt;

    /* Need to generate a c-client sequence string for any messages which
     * need overview fetch. Following code stolen from Pine 4 (pine/mailindx.c)
     *
     * How big a buf do we need?  How about eleven per -- 10 for
     * decimal representation of 32bit value plus a trailing
     * comma or terminating null...
     */
    seq = pool_alloc(pool, ((z->nmsgs + 1) * 11) * sizeof(char));
    p = seq;
    *p = '\0';

    for (current = 1; current <= z->nmsgs; current++) {
        if (!(elt = ml_elt(session, stream, current)))
            return (NIL);

        if (elt->spare) {
            if (p != seq)
                *p++ = ',';
            sprintf(p, "%lu", current);
            p += strlen(p);
        }
    }

    return (seq);
}

/* ====================================================================== */

/* Temporary mark stuff */

/* msgmap_tmp_mark() *****************************************************
 *
 * Mark message in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_tmp_mark(struct msgmap * z, unsigned long msgno)
{
    MESSAGECACHE *elt;

    if (!z->session || !z->stream)
        return (NIL);

    if (!(elt = ml_elt(z->session, z->stream, msgno)))
        return (NIL);

    if (!elt->spare2) {
        elt->spare2 = T;
        z->tmp_marked++;
        return (T);
    }
    return (NIL);
}

/* msgmap_tmp_unmark() **************************************************
 *
 * Tmp Unmark message in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_tmp_unmark(struct msgmap * z, unsigned long msgno)
{
    MESSAGECACHE *elt;

    if (!z->session || !z->stream)
        return (NIL);

    if (!(elt = ml_elt(z->session, z->stream, msgno)))
        return (NIL);

    if (elt->spare2) {
        elt->spare2 = NIL;
        z->tmp_marked--;
        return (T);
    }

    return (NIL);
}

/* msgmap_has_tmp_mark() *************************************************
 *
 * Check whether nominated message is marked
 *     zm: zoomap
 *  msgno: Message to test
 ************************************************************************/

BOOL msgmap_has_tmp_mark(struct msgmap * z, unsigned long msgno)
{
    MESSAGECACHE *elt;

    if (!z->session || !z->stream)
        return (NIL);

    if (!(elt = ml_elt(z->session, z->stream, msgno)))
        return (NIL);

    return ((elt->spare2) ? T : NIL);
}

/* msgmap_tmp_mark_all() ************************************************
 *
 * Tmp Mark all messages in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_tmp_mark_all(struct msgmap * z)
{
    MESSAGECACHE *elt;
    unsigned long msgno;

    if (!z->session || !z->stream)
        return (NIL);

    for (msgno = 1; msgno <= z->nmsgs; msgno++) {
        if (!(elt = ml_elt(z->session, z->stream, msgno)))
            return (NIL);
        elt->spare2 = T;
    }

    z->tmp_marked = z->nmsgs;
    return (T);
}

/* msgmap_tmp_unmark_all() **********************************************
 *
 * Tmp Unmark all messages in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_tmp_unmark_all(struct msgmap * z)
{
    MESSAGECACHE *elt;
    unsigned long msgno;

    if (!z->session || !z->stream)
        return (NIL);

    for (msgno = 1; msgno <= z->nmsgs; msgno++) {
        if (!(elt = ml_elt(z->session, z->stream, msgno)))
            return (NIL);
        elt->spare2 = NIL;
    }

    z->tmp_marked = NIL;
    return (T);
}

/* msgmap_tmp_marked_set() ***********************************************
 *
 * Set current tmp marked message count.
 * 
 ***********************************************************************/

void msgmap_tmp_marked_set(struct msgmap *z, unsigned long count)
{
    z->tmp_marked = count;
}


/* msgmap_tmp_marked_count() *********************************************
 *
 * Return current temporary marked message count.
 * 
 ***********************************************************************/

unsigned long msgmap_tmp_marked_count(struct msgmap *z)
{
    return (z->tmp_marked);
}

/* msgmap_tmp_mark_sequence() ********************************************
 *
 * Generate a c-client sequence string for marked messages in a folder
 *  session:
 *     pool: Scratch pool (typically request->pool)
 *   stream:
 *
 * Returns: Sequence string
 ***********************************************************************/

char *msgmap_tmp_mark_sequence(struct msgmap *z)
{
    struct session *session = z->session;
    struct pool *pool = session->request->pool;
    MAILSTREAM *stream = z->stream;
    char *seq, *p;
    unsigned long current;
    MESSAGECACHE *elt;

    /* Need to generate a c-client sequence string for any messages which
     * need overview fetch. Following code stolen from Pine 4 (pine/mailindx.c)
     *
     * How big a buf do we need?  How about eleven per -- 10 for
     * decimal representation of 32bit value plus a trailing
     * comma or terminating null...
     */
    seq = pool_alloc(pool, ((z->nmsgs + 1) * 11) * sizeof(char));
    p = seq;
    *p = '\0';

    for (current = 1; current <= z->nmsgs; current++) {
        if (!(elt = ml_elt(session, stream, current)))
            return (NIL);

        if (elt->spare2) {
            if (p != seq)
                *p++ = ',';
            sprintf(p, "%lu", current);
            p += strlen(p);
        }
    }

    return (seq);
}

/* ====================================================================== */

/* msgmap_recalculate() ************************************************
 *
 * Recalculate number of marked messages in msgmap.
 * 
 ***********************************************************************/

BOOL msgmap_recalculate(struct msgmap * z)
{
    struct session *session = z->session;
    MAILSTREAM *stream = session->stream;
    MESSAGECACHE *elt;
    unsigned long i;

    /* Count marked messages from stream */
    z->marked = 0;
    for (i = 1; i <= z->nmsgs; i++) {
        if (!(elt = ml_elt(session, stream, i)))
            return (NIL);

        if ((elt->spare))
            z->marked++;
    }
    return (T);
}

/* ====================================================================== */

/* msgmap_mark_searched() **********************************************
 *
 * Tranfer searched bits into marked map.
 *   narrow: Only mark matched message which were previously marked.
 * 
 ***********************************************************************/

BOOL msgmap_mark_searched(struct msgmap * z, BOOL narrow)
{
    struct session *session = z->session;
    MAILSTREAM *stream = z->stream;
    unsigned long i, count;
    MESSAGECACHE *elt;

    count = 0L;
    for (i = 1; i <= z->nmsgs; i++) {
        if (!(elt = ml_elt(session, stream, i)))
            return (NIL);

        if (narrow) {
            if (!elt->searched) /* Narrow  */
                elt->spare = NIL;
        } else {
            if (elt->searched)  /* Broaden */
                elt->spare = T;
        }

        if (elt->spare)
            count++;
    }

    /* Zoommap has changed: invalidate */
    if (count != z->marked)
        msgmap_invalidate(z);

    z->marked = count;

    return (T);
}
