/*
 * Copyright (c) 1994-2017 Carnegie Mellon University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Carnegie Mellon University
 *      Center for Technology Transfer and Enterprise Creation
 *      4615 Forbes Avenue
 *      Suite 302
 *      Pittsburgh, PA  15213
 *      (412) 268-7393, fax: (412) 268-7395
 *      innovation@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <getopt.h>
#include <libgen.h>
#include <stdlib.h>
#include <stdio.h>
#include <sysexits.h>
#include <syslog.h>
#include <string.h>

/* cyrus includes */
#include "assert.h"
#include "global.h"
#include "index.h"
#include "imapurl.h"
#include "mailbox.h"
#include "mboxlist.h"
#include "util.h"
#include "xmalloc.h"
#include "xstrlcpy.h"

/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"

static int verbose = 0;
static const char *progname = NULL;

static int dump_me(struct findall_data *data, void *rock);
static void print_seq(const char *tag, const char *attrib,
                      unsigned *seq, int n);
static int usage(void);

struct incremental_record {
    unsigned incruid;
};

int main(int argc, char *argv[])
{
    int opt;
    int i;
    char *alt_config = NULL;
    struct incremental_record irec;

    progname = basename(argv[0]);

    /* keep this in alphabetical order */
    static const char short_options[] = "C:v";

    static const struct option long_options[] = {
        /* n.b. no long option for -C */
        { "verbose", no_argument, NULL, 'v' },
        { 0, 0, 0, 0 },
    };

    while (-1 != (opt = getopt_long(argc, argv,
                                    short_options, long_options, NULL)))
    {
        switch (opt) {
        case 'v':
            verbose++;
            break;

        case 'C': /* alt config file */
            alt_config = optarg;
            break;

        default:
            usage();
            break;
        }
    }

    if (optind == argc) {
        usage();
    }

    cyrus_init(alt_config, "dump", 0, CONFIG_NEED_PARTITION_DATA);

    search_attr_init();

    irec.incruid = 0;
    strarray_t *array = strarray_new();
    for (i = optind; i < argc; i++) {
        strarray_append(array, argv[i]);
    }
    if (array->count)
        mboxlist_findallmulti(NULL, array, 1, 0, 0, dump_me, &irec);

    strarray_free(array);

    cyrus_done();

    return 0;
}

static int usage(void)
{
    fprintf(stderr, "Usage: %s [OPTIONS] {mailboxes}\n", progname);
    fprintf(stderr, "Dumps out a basic copy of mailbox data to stdout.\n");
    fprintf(stderr, "\n");

    fprintf(stderr, "-C <config-file>         use <config-file> instead of config from imapd.conf\n");
    fprintf(stderr, "-v                       enable verbose output\n");

    fprintf(stderr, "\n");

    exit(EX_USAGE);
}

static void generate_boundary(char *boundary, size_t size)
{
    assert(size >= 100);

    snprintf(boundary, size, "dump-%ld-%ld-%ld",
             (long) getpid(), (long) time(NULL), (long) rand());
}

static search_expr_t *systemflag_match(int flag)
{
    search_expr_t *e = search_expr_new(NULL, SEOP_MATCH);
    e->attr = search_attr_find("systemflags");
    e->value.u = flag;
    return e;
}

static int dump_me(struct findall_data *data, void *rock)
{
    int r;
    char boundary[128];
    struct imapurl url;
    struct incremental_record *irec = (struct incremental_record *) rock;
    struct searchargs searchargs;
    struct index_state *state;
    unsigned *uids = NULL;
    unsigned *uidseq = NULL;
    int i, n, numuids;
    unsigned msgno;

    /* don't want partial matches */
    if (!data) return 0;
    if (!data->is_exactmatch) return 0;

    const char *name = mbname_intname(data->mbname);

    r = index_open(name, NULL, &state);
    if (r) {
        if (verbose) {
            printf("error opening %s: %s\n", name, error_message(r));
        }
        return 0;
    }

    generate_boundary(boundary, sizeof(boundary));

    printf("Content-Type: multipart/related; boundary=\"%s\"\n\n", boundary);

    printf("--%s\n", boundary);
    printf("Content-Type: text/xml\n");
    printf("IMAP-Dump-Version: 0\n");
    printf("\n");

    printf("<imapdump uniqueid=\"%s\">\n", mailbox_uniqueid(state->mailbox));
    memset(&url, 0, sizeof(struct imapurl));
    url.server = config_servername;
    url.mailbox = name;

    struct buf urlbuf = BUF_INITIALIZER;
    imapurl_toURL(&urlbuf, &url);
    printf("  <mailbox-url>%s</mailbox-url>\n", buf_cstring(&urlbuf));
    printf("  <incremental-uid>%d</incremental-uid>\n", irec->incruid);
    printf("  <nextuid>%u</nextuid>\n", state->mailbox->i.last_uid + 1);
    printf("\n");
    buf_free(&urlbuf);

    memset(&searchargs, 0, sizeof(struct searchargs));
    searchargs.root = search_expr_new(NULL, SEOP_TRUE);
    numuids = index_getuidsequence(state, &searchargs, &uids);
    search_expr_free(searchargs.root);
    print_seq("uidlist", NULL, uids, numuids);
    printf("\n");

    printf("  <flags>\n");

    searchargs.root = systemflag_match(FLAG_ANSWERED);
    uidseq = NULL;
    n = index_getuidsequence(state, &searchargs, &uidseq);
    search_expr_free(searchargs.root);
    print_seq("flag", "name=\"\\Answered\" user=\"*\"", uidseq, n);
    if (uidseq) free(uidseq);

    searchargs.root = systemflag_match(FLAG_DELETED);
    uidseq = NULL;
    n = index_getuidsequence(state, &searchargs, &uidseq);
    search_expr_free(searchargs.root);
    print_seq("flag", "name=\"\\Deleted\" user=\"*\"", uidseq, n);
    if (uidseq) free(uidseq);

    searchargs.root = systemflag_match(FLAG_DRAFT);
    uidseq = NULL;
    n = index_getuidsequence(state, &searchargs, &uidseq);
    search_expr_free(searchargs.root);
    print_seq("flag", "name=\"\\Draft\" user=\"*\"", uidseq, n);
    if (uidseq) free(uidseq);

    searchargs.root = systemflag_match(FLAG_FLAGGED);
    uidseq = NULL;
    n = index_getuidsequence(state, &searchargs, &uidseq);
    search_expr_free(searchargs.root);
    print_seq("flag", "name=\"\\Flagged\" user=\"*\"", uidseq, n);
    if (uidseq) free(uidseq);

    printf("  </flags>\n");

    printf("</imapdump>\n");

    i = 0;
    while (i < numuids && uids[i] < irec->incruid) {
        /* already dumped this message */
        /* xxx could do binary search to get to the first
           undumped uid */
        i++;
    }

    for (msgno = 1; msgno <= state->exists; msgno++) {
        struct buf buf = BUF_INITIALIZER;
        struct index_map *im = &state->map[msgno-1];
        struct index_record record;

        while (im->uid > uids[i] && i < numuids)
            i++;
        if (i >= numuids)
            break;

        if (im->uid < uids[i])
            continue;

        /* got a match */
        i++;
        memset(&record, 0, sizeof(struct index_record));
        record.recno = im->recno;
        record.uid = im->uid;
        if (mailbox_reload_index_record(state->mailbox, &record))
            continue;

        printf("\n--%s\n", boundary);
        printf("Content-Type: message/rfc822\n");
        printf("Content-ID: %d\n", uids[i]);
        printf("\n");
        r = mailbox_map_record(state->mailbox, &record, &buf);
        if (r) {
            if (verbose) {
                printf("error mapping message %u: %s\n", record.uid,
                       error_message(r));
            }
            break;
        }
        fwrite(buf.s, 1, buf.len, stdout);
        buf_free(&buf);
    }

    printf("\n--%s--\n", boundary);

    free(uids);
    index_close(&state);

    return 0;
}

static void print_seq(const char *tag, const char *attrib,
                      unsigned *seq, int n)
{
    int i;

    printf("  <%s%s%s>", tag, attrib ? " " : "", attrib ? attrib : "");
    for (i = 0; i < n; i++) {
        printf("%u ", seq[i]);
    }
    printf("</%s>\n", tag);
}
