/* $Cambridge: hermes/src/prayer/cmd/cmd_list.c,v 1.6 2011/04/26 09:33:14 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"

/* Add template_vals used to navigate to other pages */

static BOOL
cmd_list_addsort(struct session *session)
{
    struct template_vals *tvals = session->template_vals;
    struct msgmap *zm = session->zm;

    switch (zm->sort_mode) {
    case ARRIVAL:
        template_vals_string(tvals, "$sort_mode", "arrival");
        break;
    case DATE:
        template_vals_string(tvals, "$sort_mode", "date");
        break;
    case FROM:
        template_vals_string(tvals, "$sort_mode", "from");
        break;
    case SUBJECT:
        template_vals_string(tvals, "$sort_mode", "subject");
        break;
    case TO:
        template_vals_string(tvals, "$sort_mode", "to");
        break;
    case CC:
        template_vals_string(tvals, "$sort_mode", "cc");
        break;
    case SIZE:
        template_vals_string(tvals, "$sort_mode", "size");
        break;
    case REFERENCES:
        template_vals_string(tvals, "$sort_mode", "references");
        break;
    case ORDEREDSUBJECT:
        template_vals_string(tvals, "$sort_mode", "orderedsubject");
        break;
    }
    if (zm->sort_reverse)
        template_vals_ulong(tvals, "$sort_reverse", 1);

    return(T);
}

static BOOL
cmd_list_addnav(struct session *session, unsigned long zm_offset)
{
    struct template_vals *tvals = session->template_vals;
    struct prefs *prefs = session->options->prefs;
    struct msgmap *zm = session->zm;
    MAILSTREAM *stream = session->stream;
    unsigned long count = msgmap_size(session->zm);
    unsigned long per_page = prefs->msgs_per_page;
    unsigned long page = 0;
    unsigned long pages = 0;
    unsigned msgno;
    unsigned uid;

    if (count > 0) {
        page = ((zm_offset - 1) / prefs->msgs_per_page) + 1;
        pages = ((count - 1) / prefs->msgs_per_page) + 1;
    }

    template_vals_hash_ulong(tvals, "$nav", "msg_count",    count);
    template_vals_hash_ulong(tvals, "$nav", "page_current", page);
    template_vals_hash_ulong(tvals, "$nav", "page_count",   pages);

    if (page > 1) {
        msgno = msgmap_value(zm, 1);
        uid   = ml_uid(session, stream, msgno);
        template_vals_hash_ulong(tvals, "$nav", "first_msg", msgno);
        template_vals_hash_ulong(tvals, "$nav", "first_uid", uid);

        msgno = msgmap_value(zm, zm_offset - per_page);
        uid   = ml_uid(session, stream, msgno);
        template_vals_hash_ulong(tvals, "$nav", "prev_msg", msgno);
        template_vals_hash_ulong(tvals, "$nav", "prev_uid", uid);
    }
    if (page < pages) {
        if ((msgno = (zm_offset + per_page)) > count)
            msgno = count;

        msgno = msgmap_value(zm, msgno);
        uid   = ml_uid(session, stream, msgno);
        template_vals_hash_ulong(tvals, "$nav", "next_msg", msgno);
        template_vals_hash_ulong(tvals, "$nav", "next_uid", uid);

        msgno = msgmap_value(zm, count);
        uid   = ml_uid(session, stream, msgno);
        template_vals_hash_ulong(tvals, "$nav", "last_msg", msgno);
        template_vals_hash_ulong(tvals, "$nav", "last_uid", uid);
    }
    return(T);
}

/* Add template_vals for a single message */

/* Look for non-text leaf */
static BOOL
body_has_attachments(BODY *body)
{
    PART *part;
    int i;

    switch (body->type) {
    case TYPEMULTIPART:
        for (i = 1, part = body->nested.part; part != NIL;
             part = part->next, i++) {
            if (body_has_attachments(&part->body))
                return(T);
        }
        break;
    case TYPEMESSAGE:
        if (body->type != TYPEMULTIPART)
            return(NIL);

        /* Nested multipart message */
        for (i = 1, part = body->nested.part; part != NIL;
             part = part->next, i++) {
            if (body_has_attachments(&part->body))
                return(T);
        }
        break;
    case TYPETEXT:
        return(NIL);
    default:
        return(T);
    }
    return(NIL);
}

static BOOL
cmd_list_msg(struct session *session,
             MAILSTREAM * stream,
             unsigned long msgno,
             ADDRESS * alt_addr,
             int offset)
{
    struct template_vals *tvals = session->template_vals;
    struct config *config = session->config;
    struct prefs *prefs = session->options->prefs;
    struct request *request = session->request;
    struct pool *pool = request->pool;
    MESSAGECACHE *elt;
    ENVELOPE *env;
    BODY *body = NIL;
    ADDRESS *addr;
    BOOL use_to;
    char *string;
    char *pstring;

    if (!((elt = ml_elt(session, stream, msgno)) &&
          (env = ml_fetch_structure(session, stream, msgno, &body, 0))))
        return (NIL);

    template_vals_foreach_init(tvals, "@list_msg", offset);
    template_vals_foreach_ulong(tvals, "@list_msg", offset, "num", msgno);
    template_vals_foreach_ulong(tvals, "@list_msg", offset, "uid",
                                ml_uid(session, stream, msgno));
    if (elt->deleted)
        template_vals_foreach_ulong(tvals, "@list_msg", offset,
                                    "is_deleted", 1);
    if (elt->answered)
        template_vals_foreach_ulong(tvals, "@list_msg", offset,
                                    "is_answered", 1);
    if (elt->seen)
        template_vals_foreach_ulong(tvals, "@list_msg", offset,
                                    "is_seen", 1);

    if (prefs->use_mark_persist && msgmap_has_mark(session->zm, msgno))
        template_vals_foreach_ulong(tvals, "@list_msg", offset,
                                    "is_marked", 1);
    if (offset % 2 == 0)
        template_vals_foreach_ulong(tvals, "@list_msg", offset,
                                    "even_row", 1);

    if (body && body_has_attachments(body)) 
        template_vals_foreach_ulong(tvals, "@list_msg", offset,
                                    "has_attach", 1);

    /* Message Date */
    if (env->date) {
        MESSAGECACHE tmp;
        mail_parse_date(&tmp, env->date);

        string = mc_date_to_string_full(&tmp);
    } else
        string = "(No date)";

    if (string && string[0])
        template_vals_foreach_string(tvals, "@list_msg", offset,
                                     "date", string);

    /* Try and work out if user sent this message
     * Yes => show "To:" rather than from address in header.
     */
    use_to = NIL;
    if ((addr = env->from) && addr->host) {
        while (alt_addr) {
            if (!strcasecmp(addr->mailbox, alt_addr->mailbox) &&
                !strcasecmp(addr->host, alt_addr->host)) {
                use_to = T;
                break;
            }
            alt_addr = alt_addr->next;
        }

        if (!use_to &&
            !strcasecmp(addr->mailbox, session->username) &&
            config->local_domain_list) {
            /* Find out if address is local use of CRSid
             *  e.g: dpc22@cam.ac.uk, dpc22@hermes.cam.ac.uk */

            struct list_item *li;
            /* Check for username@[username_domain]. */
            for (li = config->local_domain_list->head; li; li = li->next) {
                if (!strcasecmp(li->name, addr->host)) {
                    use_to = T;
                    break;
                }
            }
        }
    }

    /* From or To address depending on context */
    addr = (use_to) ? env->to : env->from;

    if (addr && addr->personal)
        string = addr->personal;
    else if (addr && addr->mailbox && addr->host)
        string =
            pool_printf(pool, "%s@%s", addr->mailbox, addr->host);
    else
        string = "(unknown)";

    string = (char *) rfc1522_decode(pool_alloc(pool, strlen(string)),
                                     strlen(string), string, NIL);

    pstring = utf8_prune(pool, string, config->list_addr_maxlen);
    if (use_to) {
        string  = pool_strcat(pool, "To: ", string);
        pstring = pool_strcat(pool, "To: ", pstring);
    }

    template_vals_foreach_string(tvals, "@list_msg", offset,
                                 "full_dname", string);
    template_vals_foreach_string(tvals, "@list_msg", offset,
                                 "dname", pstring);
    /* Message size */
    if (elt->rfc822_size > 1024)
        string = pool_printf(pool, "%luK", elt->rfc822_size / 1024);
    else
        string = pool_printf(pool, "%lu", elt->rfc822_size);

    template_vals_foreach_string(tvals, "@list_msg", offset,
                                 "size", string);

    /* Message subject */
    if (env->subject && env->subject[0]) {
        string = env->subject;
        string = (char *)
            rfc1522_decode(pool_alloc(pool, strlen(string)),
                           strlen(string), string, NIL);
        pstring = utf8_prune(pool, string, config->list_subject_maxlen);
    } else
        pstring = string = "(No subject)";

    template_vals_foreach_string(tvals, "@list_msg", offset,
                                 "full_subject", string);

    template_vals_foreach_string(tvals, "@list_msg", offset,
                                 "subject", pstring);
    return (T);
}

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

/* Couple of small support routines for alt_addr stuff */

static ADDRESS *cmd_list_alt_addr_generate(struct session *session,
                                           struct pool *pool,
                                           struct prefs *prefs)
{
    ADDRESS *alt_addr = NIL;
    char *text;

    if (prefs->from_address && prefs->from_address[0]) {
        if (prefs->alt_addr && prefs->alt_addr[0])
            text = pool_printf(pool, "%s, %s",
                               prefs->from_address, prefs->alt_addr);
        else
            text = pool_strdup(pool, prefs->from_address);
    } else
        text = pool_strdup(pool, prefs->alt_addr);

    if (text && text[0] && !(alt_addr=addr_parse_destructive(text, ""))) {
        session_alert(session, "Alt-address invalid - %s", ml_errmsg());
        return(NIL);
    }

    return (alt_addr);
}

static void cmd_list_alt_addr_free(ADDRESS * alt_addr)
{
    mail_free_address(&alt_addr);
}

/* cmd_list_work() *******************************************************
 *
 * Generate list screen. Parent routine will clean up after error.
 ************************************************************************/

static BOOL cmd_list_work(struct session *session)
{
    struct request *request = session->request;
    struct prefs *prefs = session->options->prefs;
    unsigned long first, current, last;
    MAILSTREAM *stream = session->stream;
    struct buffer *b = request->write_buffer;
    struct msgmap *zm = session->zm;
    unsigned long zm_offset;
    ADDRESS *alt_addr;
    unsigned long count;
    struct template_vals *tvals = session->template_vals;
    int offset = 0;
    BOOL mark_page   = NIL;
    BOOL unmark_page = NIL;

    if (!msgmap_update(zm))
        return (NIL);

    if (request->argc == 3) {
        unsigned long msgno = atoi(request->argv[1]);
        unsigned long msguid = atoi(request->argv[2]);

        if ((msgno == 0) || (msgno > zm->nmsgs) ||
            (!(msgno = stream_check_uid(session, stream, msgno, msguid))))
            msgno = zm->nmsgs;

        session->current = msgno;
    } else if (request->argc == 2) {
        if (!strcmp(request->argv[1], "mark")) {
            mark_page = T;
        } else if (!strcmp(request->argv[1], "unmark")) {
            unmark_page = T;
        }
    }

    /* Expunge event => session->current may no longer be valid? */
    if ((session->current == 0) || (session->current > zm->nmsgs))
        session->current = zm->nmsgs;

    zm_offset = msgmap_find(zm, session->current);
    count = msgmap_size(session->zm);

    if (zm_offset > prefs->msgs_per_page)
        first = zm_offset - ((zm_offset - 1) % prefs->msgs_per_page);
    else
        first = 1;

    last = (((first + prefs->msgs_per_page - 1) < count)
            ? (first + prefs->msgs_per_page - 1) : count);

    if (prefs->use_mark_persist)
        template_vals_ulong(tvals, "use_persist", 1);

    if (msgmap_marked_count(session->zm) > 0)
        template_vals_ulong(tvals, "have_marked", 1);

    if (msgmap_have_zoom(session->zm))
        template_vals_ulong(tvals, "have_zoom", 1);

    cmd_list_addsort(session);
    cmd_list_addnav(session, zm_offset);

    alt_addr = cmd_list_alt_addr_generate(session, request->pool, prefs);

    if (prefs->use_mark_persist) {
        int msgs_on_page   = 0;
        int marked_on_page = 0;
        int change_count  = 0;

        if (mark_page || unmark_page) {
            /* Mark or unmark all message on the page */
            for (current = first; current <= last; current++) {
                if (mark_page && !msgmap_has_mark(zm, current)) {
                    msgmap_mark(session->zm, current); 
                    change_count++;
                } else if (unmark_page && msgmap_has_mark(zm, current)) {
                    msgmap_unmark(session->zm, current); 
                    change_count++;
                }
            }
            session_message(session, "%s %lu %s",
                            (mark_page) ? "Marked" : "Unmarked",
                            (change_count),
                            ((change_count) != 1) ? "messages" : "message");
        }

        /* Work out sensible state for top link */
        for (current = first; current <= last; current++) {
            msgs_on_page++;
            if (msgmap_has_mark(zm, current))
                marked_on_page++;
        }
        if (marked_on_page == msgs_on_page)
            template_vals_ulong(tvals, "page_all_marked", 1);
    } else {
        /* !prefs->use_mark_persist */
        if (mark_page)
            template_vals_ulong(tvals, "page_all_marked", 1);
    }

    offset = 0;
    for (current = first; current <= last; current++) {
        if (!cmd_list_msg(session, stream, msgmap_value(zm, current),
                          alt_addr, offset))
            return (NIL);
        offset++;
    }
    cmd_list_alt_addr_free(alt_addr);

    template_vals_string(tvals, "foldername", session->foldername);
    session_seed_template(session, tvals);
    template_expand("list", tvals, b);
    return (T);
}

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

void cmd_list(struct session *session)
{
    struct options *options = session->options;
    struct msgmap *zm = session->zm;
    struct request *request = session->request;
    char *cmd, *folder;

    /* Handle POST requests: folder change requests */
    if (request->method == POST) {
        request_decode_form(request);

        if (assoc_lookup(request->form, "sub_folder_dialogue") &&
            (folder = assoc_lookup(request->form, "folder")) && folder[0])
        {

            if ((cmd = assoc_lookup(request->form, "command")) &&
                !strcmp(cmd, "copy")) {
                session->aggregate = T;
                session_redirect(session, request,
                                 pool_strcat(request->pool, "copy_msg/",
                                             folder));
            } else
                session_redirect(session, request,
                                 pool_strcat(request->pool, "change/",
                                             folder));
            return;
        }
    }

    /* Save options if anything changed */
    if (options->save)
        session_streams_save_options(session);

    /* Record last command for compose/cancel and friends */
    if (!session->draft->have_draft)
        session->compose_parent_cmd = "list";

    /* Record last command for copy/cancel */
    session->copy_parent_cmd = "list";

    if (msgmap_have_zoom(zm) && (msgmap_marked_count(zm) == 0)) {
        msgmap_unmark_all(zm);
        msgmap_disable_zoom(zm);
        session_message(session,
                        "No messages marked: disabling zoom mode");
    }

    if (!cmd_list_work(session)) {
        session_redirect(session, request, "restart");
        return;
    }

    /* Send out HTML */
    response_html(request, 200);
}
