/* $Cambridge: hermes/src/prayer/cmd/cmd_search.c,v 1.5 2008/09/17 17:20:25 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"

static BOOL search_clear_all(struct session *session)
{
    return (msgmap_unmark_all(session->zm));
}

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


static void template_dates(struct template_vals *tvals)
{
    static char *date_month[12] = {
        "January", "February", "March", "April",
        "May", "June", "July", "August",
        "September", "October", "November", "December"
    };
    unsigned long i;
    time_t now = time(0);
    struct tm *date = localtime(&now);
    unsigned long count = 0;

    count = 0;
    for (i = 1; i <= 31; i++) {
        template_vals_foreach_init(tvals, "@day", count);
        template_vals_foreach_ulong(tvals, "@day", count, "day", i);
        if (i == date->tm_mday)
            template_vals_foreach_ulong(tvals, "@day", count, "selected", 1);
        count++;
    }

    count = 0;
    for (i = 1; i <= 12; i++) {
        template_vals_foreach_init(tvals, "@month", count);
        template_vals_foreach_ulong(tvals, "@month", count,
                                     "value", i);
        template_vals_foreach_string(tvals, "@month", count,
                                     "name", date_month[i - 1]);
        if (i == (1 + date->tm_mon))
            template_vals_foreach_ulong(tvals, "@month", count,
                                        "selected", 1);
        count++;
    }

    template_vals_ulong(tvals, "year", 1900 + date->tm_year);
}

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

/* Various fields that we can search */

#define SEARCH_FIELD_NONE        (0)
#define SEARCH_FIELD_FROM        (1)
#define SEARCH_FIELD_TO          (2)
#define SEARCH_FIELD_CC          (3)
#define SEARCH_FIELD_RECIPIENT   (4)
#define SEARCH_FIELD_PARTICIPANT (5)
#define SEARCH_FIELD_SUBJECT     (6)
#define SEARCH_FIELD_TEXT        (7)

static BOOL
search_text_apply(struct session *session, BOOL narrow,
                  int field, BOOL match, char *value)
{
    MAILSTREAM *stream = session->stream;
    char *charset = "UTF-8";
    SEARCHPGM *pgm, *pgm2;

    pgm = mail_newsearchpgm();

    switch (field) {
    case SEARCH_FIELD_FROM:
        pgm->from = mail_newstringlist();
        pgm->from->text.data = (unsigned char *) cpystr(value);
        pgm->from->text.size = strlen(value);
        break;
    case SEARCH_FIELD_CC:
        pgm->cc = mail_newstringlist();
        pgm->cc->text.data = (unsigned char *) cpystr(value);
        pgm->cc->text.size = strlen(value);
        break;
    case SEARCH_FIELD_TO:
        pgm->to = mail_newstringlist();
        pgm->to->text.data = (unsigned char *) cpystr(value);
        pgm->to->text.size = strlen(value);
        break;
    case SEARCH_FIELD_SUBJECT:
        pgm->subject = mail_newstringlist();
        pgm->subject->text.data = (unsigned char *) cpystr(value);
        pgm->subject->text.size = strlen(value);
        break;
    case SEARCH_FIELD_TEXT:
        pgm->text = mail_newstringlist();
        pgm->text->text.data = (unsigned char *) cpystr(value);
        pgm->text->text.size = strlen(value);
        break;
    case SEARCH_FIELD_RECIPIENT:
        pgm->or = mail_newsearchor();
        pgm->or->first->to = mail_newstringlist();
        pgm->or->first->to->text.data = (unsigned char *) cpystr(value);
        pgm->or->first->to->text.size = strlen(value);
        pgm->or->second->cc = mail_newstringlist();
        pgm->or->second->cc->text.data = (unsigned char *) cpystr(value);
        pgm->or->second->cc->text.size = strlen(value);
        break;
    case SEARCH_FIELD_PARTICIPANT:
        /* Participant match */
        pgm->or = mail_newsearchor();
        pgm->or->first->to = mail_newstringlist();
        pgm->or->first->to->text.data = (unsigned char *) cpystr(value);
        pgm->or->first->to->text.size = strlen(value);

        pgm->or->second->or = mail_newsearchor();
        pgm->or->second->or->first->cc = mail_newstringlist();
        pgm->or->second->or->first->cc->text.data =
            (unsigned char *) cpystr(value);
        pgm->or->second->or->first->cc->text.size = strlen(value);
        pgm->or->second->or->second->from = mail_newstringlist();
        pgm->or->second->or->second->from->text.data =
            (unsigned char *) cpystr(value);
        pgm->or->second->or->second->from->text.size = strlen(value);
        break;
    }

    if (!match) {
        /* Negate test */
        pgm2 = mail_newsearchpgm();
        pgm2->not = mail_newsearchpgmlist();
        pgm2->not->pgm = pgm;
        pgm2->not->next = NIL;
        pgm = pgm2;
    }

    /* Apply this search pattern */
    if (!ml_search_full(session, stream, charset, pgm,
                        (SE_NOPREFETCH | SE_FREE)))
        return (NIL);

    return (msgmap_mark_searched(session->zm, narrow));
}

static BOOL process_text_form(struct session *session)
{
    struct request *request = session->request;
    struct assoc *h = request->form;    /* Already processed */
    char *key, *value, *s;
    BOOL match, narrow;
    static struct {
        char *text;
        int value;
    } *l, lookup_table[] = {
        {
        "from", SEARCH_FIELD_FROM}
        , {
        "to", SEARCH_FIELD_TO}
        , {
        "cc", SEARCH_FIELD_CC}
        , {
        "recipient", SEARCH_FIELD_RECIPIENT}
        , {
        "participant", SEARCH_FIELD_PARTICIPANT}
        , {
        "subject", SEARCH_FIELD_SUBJECT}
        , {
        "text", SEARCH_FIELD_TEXT}
        , {
        NIL, SEARCH_FIELD_NONE}
    };

    if (!((key = assoc_lookup(h, "text_key")) &&
          (value = assoc_lookup(h, "text_value")) && key[0] && value[0])) {
        session_message(session, "No search criteria provided");
        session_redirect(session, request, "list");
        return (NIL);
    }

    match = ((s = assoc_lookup(h, "text_op")) && !strcmp(s, "match"));

    /* Find field value in table */
    for (l = lookup_table; l->text; l++)
        if (!strcmp(l->text, key))
            break;

    narrow = NIL;
    if (assoc_lookup(h, "sub_fresh")) {
        if (!search_clear_all(session)) {
            session_redirect(session, request, "restart");
            return (NIL);
        }
    } else if (assoc_lookup(h, "sub_narrow"))
        narrow = T;

    if (!search_text_apply(session, narrow, l->value, match, value)) {
        session_redirect(session, request, "restart");
        return (NIL);;
    }

    if (msgmap_marked_count(session->zm) != 1)
        session_message(session,
                        "Search criteria applied: %lu messages match",
                        msgmap_marked_count(session->zm));
    else
        session_message(session,
                        "Search criteria applied: 1 message matches");
    return (T);
}

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

/* Various fields that we can search */

static BOOL
search_date_apply(struct session *session, BOOL narrow, char *op,
                  unsigned long day, unsigned long month,
                  unsigned long year)
{
    MAILSTREAM *stream = session->stream;
    SEARCHPGM *pgm;
    unsigned short date = ((year - BASEYEAR) << 9) + (month << 5) + day;

    pgm = mail_newsearchpgm();

    if (!strcmp(op, "before_exc"))
        pgm->sentbefore = date;
    else if (!strcmp(op, "before_inc"))
        pgm->sentbefore = date + 1;
    else if (!strcmp(op, "on"))
        pgm->senton = date;
    else if (!strcmp(op, "since_inc"))
        pgm->sentsince = date;
    else if (!strcmp(op, "since_exc"))
        pgm->sentsince = date + 1;

    /* Apply this search pattern */
    if (!ml_search_full
        (session, stream, NIL, pgm, (SE_NOPREFETCH | SE_FREE)))
        return (NIL);

    return (msgmap_mark_searched(session->zm, narrow));
}

static BOOL process_date_form(struct session *session)
{
    struct request *request = session->request;
    struct assoc *h = request->form;    /* Already processed */
    char *op, *tmp;
    unsigned long day, month, year;
    BOOL narrow;

    if (!((op = assoc_lookup(h, "date_op")) && op[0] &&
          (tmp = assoc_lookup(h, "date_day")) && tmp[0] &&
          (tmp = assoc_lookup(h, "date_month")) && tmp[0] &&
          (tmp = assoc_lookup(h, "date_year")) && tmp[0])) {
        session_message(session, "No search criteria provided");
        session_redirect(session, request, "list");
        return (NIL);
    }

    day = atoi(assoc_lookup(h, "date_day"));
    month = atoi(assoc_lookup(h, "date_month"));
    year = atoi(assoc_lookup(h, "date_year"));

    if (!((day >= 1) && (month <= 31) &&
          (month >= 1) && (month <= 12) && (year >= 1970)
          && (year <= 2098))) {
        session_alert(session, "Invalid search criteria provided");
        session_redirect(session, request, "list");
        return (NIL);
    }

    narrow = NIL;
    if (assoc_lookup(h, "sub_fresh")) {
        if (!search_clear_all(session)) {
            session_redirect(session, request, "restart");
            return (NIL);
        }
    } else if (assoc_lookup(h, "sub_narrow"))
        narrow = T;

    if (!search_date_apply(session, narrow, op, day, month, year)) {
        session_redirect(session, request, "restart");
        return (NIL);
    }

    if (msgmap_marked_count(session->zm) != 1)
        session_message(session,
                        "Search criteria applied: %lu messages match",
                        msgmap_marked_count(session->zm));
    else
        session_message(session,
                        "Search criteria applied: 1 message matches");

    return (T);
}

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

static BOOL
search_status_apply(struct session *session, BOOL narrow, char *op)
{
    MAILSTREAM *stream = session->stream;
    SEARCHPGM *pgm;

    pgm = mail_newsearchpgm();

    if (!strcmp(op, "seen"))
        pgm->seen = T;
    else if (!strcmp(op, "unseen"))
        pgm->unseen = T;
    else if (!strcmp(op, "deleted"))
        pgm->deleted = T;
    else if (!strcmp(op, "undeleted"))
        pgm->undeleted = T;
    else if (!strcmp(op, "answered"))
        pgm->answered = T;
    else if (!strcmp(op, "unanswered"))
        pgm->unanswered = T;

    /* Apply this search pattern */
    if (!ml_search_full
        (session, stream, NIL, pgm, (SE_NOPREFETCH | SE_FREE)))
        return (NIL);

    return (msgmap_mark_searched(session->zm, narrow));
}

static BOOL process_status_form(struct session *session)
{
    struct request *request = session->request;
    struct assoc *h = request->form;    /* Already processed */
    char *op;
    BOOL narrow;

    if (!((op = assoc_lookup(h, "status_op")) && op[0])) {
        session_alert(session, "No search criteria provided");
        session_redirect(session, request, "list");
        return (NIL);
    }

    narrow = NIL;
    if (assoc_lookup(h, "sub_fresh")) {
        if (!search_clear_all(session)) {
            session_redirect(session, request, "restart");
            return (NIL);
        }
    } else if (assoc_lookup(h, "sub_narrow"))
        narrow = T;

    if (!search_status_apply(session, narrow, op)) {
        session_redirect(session, request, "restart");
        return (NIL);
    }

    if (msgmap_marked_count(session->zm) != 1)
        session_message(session,
                        "Search criteria applied: %lu messages match",
                        msgmap_marked_count(session->zm));
    else
        session_message(session,
                        "Search criteria applied: 1 message matches");

    return (T);
}

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

static BOOL
search_size_apply(struct session *session, BOOL narrow, char *op,
                  unsigned long size)
{
    MAILSTREAM *stream = session->stream;
    SEARCHPGM *pgm;

    pgm = mail_newsearchpgm();

    if (!strcmp(op, "larger"))
        pgm->larger = size * 1024;
    else if (!strcmp(op, "smaller"))
        pgm->smaller = size * 1024;

    /* Apply this search pattern */
    if (!ml_search_full
        (session, stream, NIL, pgm, (SE_NOPREFETCH | SE_FREE)))
        return (NIL);

    return (msgmap_mark_searched(session->zm, narrow));
}

static BOOL process_size_form(struct session *session)
{
    struct request *request = session->request;
    struct assoc *h = request->form;    /* Already processed */
    char *op, *value;
    BOOL narrow;
    unsigned long size;

    if (!((op = assoc_lookup(h, "size_op")) && op[0] &&
          (value = (assoc_lookup(h, "value"))) && value[0])) {
        session_alert(session, "No search criteria provided");
        session_redirect(session, request, "list");
        return (NIL);
    }

    size = atoi(value);

    narrow = NIL;
    if (assoc_lookup(h, "sub_fresh")) {
        if (!search_clear_all(session)) {
            session_redirect(session, request, "restart");
            return (NIL);
        }
    } else if (assoc_lookup(h, "sub_narrow"))
        narrow = T;

    if (!search_size_apply(session, narrow, op, size)) {
        session_redirect(session, request, "restart");
        return (NIL);
    }

    if (msgmap_marked_count(session->zm) != 1)
        session_message(session,
                        "Search criteria applied: %lu messages match",
                        msgmap_marked_count(session->zm));
    else
        session_message(session,
                        "Search criteria applied: 1 message matches");

    return (T);
}

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

void cmd_search(struct session *session)
{
    struct template_vals *tvals = session->template_vals;
    struct request *request = session->request;
    struct buffer *b = request->write_buffer;
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    struct assoc *h = NIL;
    unsigned long selected = msgmap_marked_count(session->zm);
    unsigned long count    = session->zm->nmsgs;
    char *page;
    char *sent_mail_name;

    if (request->method != POST) {
        template_vals_ulong(tvals, "selected", selected); 
        template_vals_ulong(tvals, "count", count); 
        
        if (prefs->maildir && prefs->maildir[0])
            sent_mail_name =
                pool_strcat3(request->pool, prefs->maildir, session->hiersep,
                             prefs->sent_mail_folder);
        else
            sent_mail_name = prefs->sent_mail_folder;

        if (!strcmp(session->foldername, sent_mail_name))
            template_vals_ulong(tvals, "is_sent_mail", 1); 

        if ((request->argc > 1) && (!strcmp(request->argv[1], "date"))) {
            template_dates(tvals);
            page = "search_date";
        } else if ((request->argc > 1)
                   && (!strcmp(request->argv[1], "status"))) {
            page = "search_status";
        } else if ((request->argc > 1)
                   && (!strcmp(request->argv[1], "size"))) {
            page = "search_size";
        } else {
            page = "search_text";
        }
        session_seed_template(session, tvals);
        template_expand(page, tvals, b);
        response_html(request, 200);
        return;
    }

    /* POST request */
    request_decode_form(request);
    h = request->form;

    if (assoc_lookup(h, "sub_cancel")) {
        session_message(session, "Search operation cancelled");
        session_redirect(session, request, "list");
        return;
    }

    if (assoc_lookup(h, "sub_text")) {
        session_redirect(session, request, "search/text");
        return;
    }

    if (assoc_lookup(h, "sub_date")) {
        session_redirect(session, request, "search/date");
        return;
    }

    if (assoc_lookup(h, "sub_status")) {
        session_redirect(session, request, "search/status");
        return;
    }

    if (assoc_lookup(h, "sub_size")) {
        session_redirect(session, request, "search/size");
        return;
    }

    if (assoc_lookup(h, "text_op")) {
        if (!process_text_form(session))
            return;
    } else if (assoc_lookup(h, "date_op")) {
        if (!process_date_form(session))
            return;
    } else if (assoc_lookup(h, "status_op")) {
        if (!process_status_form(session))
            return;
    } else if (assoc_lookup(h, "size_op")) {
        if (!process_size_form(session))
            return;
    }

    count = msgmap_marked_count(session->zm);

    if (prefs->use_search_zoom && (count > 0)
        && (count < session->zm->nmsgs))
        msgmap_enable_zoom(session->zm);

    session_redirect(session, request, "list");
}
