/* $Cambridge: hermes/src/prayer/session/options.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"

/* Class to manage user changable options:
 *   preferences, addressbook, dictionary, roles
 */

/* options_create() *****************************************************
 *
 * Create an fresh options structure, including own pool and subsiduary
 * prefs, abook, dictionary and roles structure
 ***********************************************************************/

struct options *options_create(struct config *config, struct pool *pool)
{
    struct options *o = pool_alloc(pool, sizeof(struct options));

    o->abook = abook_create();
    o->prefs = prefs_create(config);
    o->prefs_save = prefs_copy(o->prefs);
    o->prefs_work = NIL;
    o->role_list = list_create(NIL, T);
    o->dictionary = dictionary_create();
    o->favourite_list = favourite_list_create();
    o->save = NIL;

    return (o);
}

/* options_free() ********************************************************
 *
 * Free options including subsiduary structures
 ************************************************************************/

void options_free(struct options *options)
{
    abook_free(options->abook);
    prefs_free(options->prefs);
    list_free(options->role_list);
    dictionary_free(options->dictionary);
    favourite_list_free(options->favourite_list);
}

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

/* options_decode() ******************************************************
 *
 * Decode options line: replace %xy URL encoding in place
 *    string: Source string
 *
 * Returns: Decoded string.
 ************************************************************************/

static BOOL options_upgrade_charset = NIL;
static struct pool *options_tpool   = NIL;

char *options_decode(char *string)
{
    unsigned char *s, *t;

    if (!(string && string[0]))
        return ("");

    /* Set all pointers to start of string */
    s = t = (unsigned char *) string;

    while (*t) {
        if (*t == ' ') {
            t++;
            continue;
        }

        if (*t == '%') {
            if (ishex(t[1]) && ishex(t[2])) {   /* Better way to do this? */
                *s++ = (16 * hex(t[1])) + hex(t[2]);
                t += 3;
                continue;
            }
            /* Otherwise fall through to default behaviour */
        }

        if (s < t) {
            *s++ = *t++;        /* Copy string to new location */
        } else {
            s++;
            t++;                /* Just keep going */
        }
    }
    *s = '\0';                  /* Tie off string */

    if (options_upgrade_charset && options_tpool) {
        string = utf8_from_string(options_tpool, "ISO-8859-1",
                                  string, strlen(string));
    }

    return (string);
}

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

/* Static support routine */

static BOOL options_special_char(unsigned char c)
{
    switch (c) {
    case '%':
    case '-':
    case ' ':
    case '\t':
    case '\015':
    case '\012':
        return (T);
    }
    return (NIL);
}

/* options_print_token() *************************************************
 *
 * Convert single token into printable format suitable for user. Splits
 * lines into LWS format when token would make (existing) line too long.
 * preferences file.
 *        b: Target buffer
 *       s0: Source string
 *  offsetp: Running count of current column offset.
 *   
 ************************************************************************/

void
options_print_token(struct buffer *b, char *s0, unsigned long *offsetp)
{
    unsigned char *s = (unsigned char *) s0;
    static char hex[] = "0123456789abcdef";
    unsigned char c;
    unsigned long offset = *offsetp;

    /* NB: Splits long lines between token. Not longer splits line in middle
     * of token: wasn't being reassembled properly... */

    if (offset > 0) {
        if (offset >= 76) {
            bputs(b, CRLF " ");
            offset = 1;
        } else {
            bputc(b, ' ');
            offset++;
        }
    }

    if (!(s && s[0])) {
        /* Encode empty string */
        bputs(b, "%00");
        offset += 3;
    } else
        while ((c = *s++)) {
            if (options_special_char(c)) {
                bputc(b, '%');
                bputc(b, hex[c / 16]);
                bputc(b, hex[c & 15]);
                offset += 3;
            } else {
                bputc(b, c);
                offset++;
            }
        }
    *offsetp = offset;
}

/* options_print() *******************************************************
 *
 * Print single (key, string) pair to target buffer
 ************************************************************************/

void options_print(struct buffer *b, char *key, char *s)
{
    unsigned long offset = 0;

    bputs(b, key);
    bputc(b, ':');
    bputc(b, ' ');
    offset = strlen(key) + 1;

    if (s && s[0])
        options_print_token(b, s, &offset);

    bputs(b, "" CRLF);
}

/* options_print_bool() **************************************************
 *
 * Print single (key, bool) pair to target buffer
 ************************************************************************/

void options_print_bool(struct buffer *b, char *key, BOOL value)
{
    if (value)
        bprintf(b, "%s: true" CRLF, key);
    else
        bprintf(b, "%s: false" CRLF, key);
}

/* options_print_number() ************************************************
 *
 * Print single (key, number) pair to target buffer
 ************************************************************************/

void options_print_number(struct buffer *b, char *key, unsigned long value)
{
    bprintf(b, "%s: %lu" CRLF, key, value);
}

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

/* options_create_message() **********************************************
 *
 * Convert options into RFC822 message suitable for upload to user.
 * Most of the real work is done by the subsiduary classes.
 * preferences file on IMAP server
 *    options: Options to write
 *     config: Prayer configuration
 *       pool: Scratch pool
 *
 * Returns: NULL terminated string
 ************************************************************************/

char *options_create_message(struct options *options,
                             struct config *config, struct pool *pool)
{
    struct prefs *prefs = options->prefs;
    struct buffer *b = buffer_create(pool, 2048);       /* Temporary */
    time_t now = time(0);

    /* Create new status message */

    bprintf(b, "From: %s <%s@%s>" CRLF,
            "Mail System Internal Data",
            "MAILER-DAEMON", prefs->default_domain);
    bprintf(b, "Subject: Prayer preferences" CRLF);
    bputs(b, "Date: ");
    {
        char date[64];

        rfc822_fixed_date(date);        /* Generates fixed length output */
        bputs(b, date);
    }
    bputs(b, "" CRLF);

    bprintf(b, "Message-ID: %lu@%s" CRLF, (unsigned long) now,
            prefs->default_domain);
    bprintf(b, "" CRLF);

    bprintf(b, "--VERSION-- 3.0" CRLF);
    bprintf(b, "--PREFS--" CRLF);
    prefs_print_options(options->prefs_save, config, b);

    bprintf(b, "--ADDRESSBOOK--" CRLF);
    abook_print_options(options->abook, b);

    bprintf(b, "--DICTIONARY--" CRLF);
    dictionary_print_options(options->dictionary, b);

    bprintf(b, "--FAVOURITES--" CRLF);
    favourite_print_options(options->favourite_list, b);

    bprintf(b, "--ROLES--" CRLF);
    role_print_options(options->role_list, b);
    bputs(b, "" CRLF);

    return (buffer_fetch(b, 0, buffer_size(b), NIL));
}

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

/* Routine for parsing options message */

/* options_parse_bool() **************************************************
 *
 * Parse a boolean value from preferences file.
 ************************************************************************/

BOOL options_parse_bool(char *value)
{
    if (!strcasecmp(value, "true"))
        return (T);

    if (!strcmp(value, "1"))
        return (T);

    return (NIL);
}

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

/* options_parse_string() ************************************************
 *
 * Parse options message fetched from IMAP server. Most of the real
 * work is done by foobar_parse methods in the subsiduary classes.
 *
 * options: Options structure to be updated
 * string:  Options to be parsed (series of lines)
 * session: For logging purposes only
 ************************************************************************/

BOOL
options_parse(struct options * options, char *string,
              struct session * session)
{
    char *line;
    enum { PREFS, ABOOK, DICTIONARY, FAVOURITES, ROLES } state = PREFS;

    /* First thing in file must be version string */
    while ((line = string_get_lws_line(&string, T))) {
        char *key, *value;

        line = string_trim_whitespace(line);
        if (line[0] == '\0')
            continue;

        if (!(key = string_get_token(&line)))
            return (NIL);

        if (!(value = string_get_token(&line)))
            return (NIL);

        if (strcmp(key, "--VERSION--") != 0)
            return (NIL);

        if (!strcmp(value, "2.0")) {
            options_upgrade_charset = T;
            options_tpool = pool_create(64);
        } else if (strcmp(value, "3.0") != 0)
            return (NIL);

        break;
    }

    while ((line = string_get_lws_line(&string, T))) {
        line = string_trim_whitespace(line);
        if (line[0] == '\0')
            continue;

        if ((line[0] == '-') && (line[1] == '-')) {     /* Section change */
            if (!strcmp(line, "--PREFS--")) {
                state = PREFS;
                continue;
            }
            if (!strcmp(line, "--ADDRESSBOOK--")) {
                state = ABOOK;
                continue;
            }
            if (!strcmp(line, "--DICTIONARY--")) {
                state = DICTIONARY;
                continue;
            }
            if (!strcmp(line, "--FAVOURITES--")) {
                state = FAVOURITES;
                continue;
            }
            if (!strcmp(line, "--ROLES--")) {
                state = ROLES;
                continue;
            }
        }

        switch (state) {
        case PREFS:
            prefs_parse_option(options->prefs, line, session);
            break;
        case ABOOK:
            abook_parse_line(options->abook, line, session);
            break;
        case DICTIONARY:
            dictionary_parse_line(options->dictionary, line, session);
            break;
        case FAVOURITES:
            favourite_parse_line(options->favourite_list, line, session);
            break;
        case ROLES:
            role_parse_line(options->role_list, line, session);
            break;
        }
    }

    prefs_parse_check(options->prefs);

    /* Make spare copy of live preferences */
    prefs_free(options->prefs_save);
    options->prefs_save = prefs_copy(options->prefs);

    if (options_tpool)
        pool_free(options_tpool);

    return (T);
}
