/* $Cambridge: hermes/src/prayer/session/wrap.c,v 1.8 2009/04/07 11:14:59 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"

/* Various auxillary functions */

/* wrap_isquote() ********************************************************
 *
 * Check whether character is potential quotation.
 ************************************************************************/

static BOOL wrap_isquote(char c)
{
    return ((c == '>') || (c == ':') || (c == '|'));
}

/* wrap_isspace() ********************************************************
 *
 * Check whether character is TAB or space
 ************************************************************************/

static BOOL wrap_isspace(char c)
{
    return ((c == ' ') || (c == '\t'));
}

/* wrap_word_bytes() *****************************************************
 *
 * Calculate length of next word in bytes
 ************************************************************************/

static unsigned long wrap_word_bytes(char *s)
{
    unsigned long len = 0;
    char c;

    while ((c = s[len]) &&
           (c != ' ') && (c != '\t') && (c != '\015') && (c != '\012'))
        len++;

    return (len);
}

/* wrap_calc_quote_length() **********************************************
 *
 * Calculate length of quotation for this block (doesn't include trailing
 * spaces).
 ************************************************************************/

static unsigned long wrap_calc_quote_length(char *s)
{
    char c;
    unsigned long len;

    if (!wrap_isquote(*s))
        return (0);

    len = 1;

    /* Work out quote count for this block */
    while (((c = s[len]) == ' ') || (c == '\t') || (wrap_isquote(c)))
        len++;

    /* Strip trailing whitespace from quote */
    while ((len > 1) && (((c = s[len - 1]) == ' ') || (c == '\t')))
        len--;

    return (len);
}

/* wrap_print_quote() ****************************************************
 *
 * Print section quote, quoting HTML specials if required
 ************************************************************************/

static unsigned long
wrap_print_quote(struct buffer *b,
                 char *quote, unsigned long quote_len, BOOL quote_html)
{
    unsigned long i;

    if (quote_len == 0)
        return (0);

    for (i = 0; i < quote_len; i++) {
        if (quote_html)
            html_quote_char(b, quote[i]);
        else
            bputc(b, quote[i]);
    }
    bputc(b, ' ');
    return (quote_len + 1);
}

/* wrap_allow_break() ****************************************************
 *
 * Check whether line break allowed for given (column, wrap_len) tuple.
 ************************************************************************/

static BOOL
wrap_allow_break(unsigned long quote_length, unsigned long column)
{
    if (quote_length > 0)
        return ((column > (quote_length + 1)) ? T : NIL);
    else
        return ((column > 0) ? T : NIL);
}

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

/* wrap_getblock_len() ***************************************************
 *
 * Work out length of next block.
 *      text: String to wrap
 * paragraph: Isolate paragraph blocks.
 ************************************************************************/

/* Short line => end of paragraph still relevant?  Look for long line for
 * start of paragraph to wrap might be better approach...
 */


static unsigned long wrap_getblock_len(char *text, BOOL paragraph)
{
    char *s = text, *t, *line;
    char c;
    unsigned long quote_length, i;
    char *quote;

    if (s == NIL)
        return (NIL);

    quote = s;
    quote_length = wrap_calc_quote_length(s);
    s += quote_length;

    while (wrap_isspace(*s))
        s++;

    if (paragraph) {
        /* Check for (Quoted) blank line: empty block */
        if ((*s == '\015') || (*s == '\012')) {
            s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;        /* CRLF */
            return (s - text);
        }
    }

    /* Isolate multiple line block */
    while (1) {
        line = s;

        /* Look for end of this line */
        while ((c = *s) && (c != '\015') && (c != '\012'))
            s++;

        /* End of block if we ran out of text */
        if (c == '\0')
            break;

        s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;    /* CRLF */

        /* Current line unquoted, Following line quoted => end of block */
        if ((quote_length == 0) && wrap_isquote(*s))
            break;

        /* Check if following line has the same quote prefix */
        for (i = 0; i < quote_length; i++)
            if (s[i] != quote[i])
                return (s - text);      /* Prefix changed => end of block */

        /* Scan following line for interesting patterns */
        t = s + quote_length;

        /* Allow single space if quoted */
        if ((quote_length > 0) && wrap_isspace(*t))
            t++;

        /* Indented => end of block */
        if (paragraph && wrap_isspace(*t))
            break;

        if ((((c = *t) == '\015') || (c == '\012')))
            break;              /* Blank Line => end of  block */

        if ((t[0] == '-') && (t[1] == '-') && (t[2] == ' ') &&
            ((t[3] == '\015') || (t[3] == '\012')))
            break;              /* Sigdash -> new block */

        if (wrap_isquote(c))
            break;              /* Line has extra quoting => end of block */

        /* Quote level is the same */
        s = t;
    }

    return (s - text);
}

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

/* wrap_need_paragraph() *************************************************
 *
 * Check whether paragraph wrap required (TRUE if any line> wrap_length)
 *    s: Text to check
 *  len: Line length to wrap at.
 ************************************************************************/

static BOOL wrap_need_paragraph(char *s, unsigned long wraplen)
{
    char c;
    char *t = s;
    unsigned long len = 0;

    if (wraplen < 1) return(T);

    while (1) {
        c = *s++;
        if ((c != '\0') && (c != '\015') && (c != '\012')) {
            len++;
            continue;
        }

        /* Gathered an entire line: calculate width of UTF-8 string */
        if ((len > 0) && (utf8_calc_width(t, len) > wraplen))
            return(T);

        if (c == '\0') break;

        t   = s;
        len = 0;
    }
    return (NIL);
}

/* wrap_block_paragraph() ************************************************
 *
 * Wrap a single block of text
 *                   b: Output buffer
 *                   s: String to wrap
 *         wrap_length: Length to wrap to
 ************************************************************************/

static void
wrap_block_paragraph(struct buffer *b, char *s, unsigned long wrap_length)
{
    char c;
    unsigned long bytes, width, i;
    unsigned long column, new_column, new_stop;
    char *quote;
    unsigned long quote_length;

    quote = s;
    quote_length = wrap_calc_quote_length(s);
    s += quote_length;

    /* Print quote */
    column = wrap_print_quote(b, quote, quote_length, NIL);

    /* Quote will includes a single space */
    if ((quote_length > 0) && (*s == ' ')) {
        s++;
        column++;
    }

    if (wrap_isspace(*s)) {
        /* Print leading space on first line of paragraph verbatim */

        new_column = column;
        while (wrap_isspace(*s)) {
            if (*s == '\t') {
                new_stop = (new_column / 8) + 1;        /* Next tab stop starts here */
                new_column = (new_stop * 8) + 1;
                s++;
            } else {
                new_column++;
                s++;
            }
        }

        while (column < new_column) {
            bputc(b, ' ');
            column++;
        }
    }

    while (*s) {
        new_column = column;

        /* Amalgamate LWS to single potential space. */
        while (((c = *s) == ' ') || (c == '\t') || (c == '\015')
               || (c == '\012')) {
            if ((c == '\015') || (c == '\012')) {       /* CR or LF */
                s += ((c == '\015') && (s[1] == '\012')) ? 2 : 1;       /* CRLF */
                if (*s) {
                    for (i = 0; i < quote_length; i++) {
                        if (s[i] != quote[i])
                            fatal
                                ("[wrap_block]: Inconsistent quote prefix");
                    }
                    s += quote_length;
                    while (wrap_isspace(*s))
                        s++;
                }
            } else
                s++;
            new_column = column + 1;
        }
        if ((bytes = wrap_word_bytes(s)) == 0)      /* Reached end of block */
            break;

        width = utf8_calc_width(s, bytes);

        if (wrap_allow_break(quote_length, column) &&
            ((new_column + width) >= wrap_length)) {
            bputs(b, " " CRLF); /* Soft break */
            column = wrap_print_quote(b, quote, quote_length, NIL);
        } else {
            while (column < new_column) {       /* Tab to correct column */
                bputc(b, ' ');
                column++;
            }
        }

        column += width;
        while (bytes > 0) {
            bputc(b, *s++);
            bytes--;
        }
    }
    bputs(b, CRLF);
}

/* wrap_paragraph() *******************************************************
 *
 * Wrap string in paragraph blocks.
 *     b: Output buffer
 *     s: Input string
 *   len: Length to wrap to.
 ************************************************************************/

void wrap_paragraph(struct buffer *b, char *s, unsigned long wrap_length)
{
    char *t;
    unsigned long len;
    char c;

    if (s == NIL)
        return;

    /* Look for forwarded message block: isolate everything up to that point */
    t = s;
    while (*t) {
        if ((*t == '\015') || (*t == '\012')) {
            t += ((t[0] == '\015') && (t[1] == '\012')) ? 2 : 1;
            if (!strncmp
                (t, "---------- Forwarded",
                 strlen("---------- Forwarded")))
                break;
        } else
            t++;
    }
    if (*t)
        *t = '\0';
    else
        t = NIL;

    while (*s) {
        len = wrap_getblock_len(s, T);
        c = s[len];
        s[len] = '\0';

        if (wrap_need_paragraph(s, wrap_length))
            wrap_block_paragraph(b, s, wrap_length);
        else
            bputs(b, s);

        s[len] = c;
        s += len;
    }

    /* Print Forwarded msg block verbatim */
    if (t) {
        *t = '-';
        while ((c = *t++))
            bputc(b, c);
    }
}


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

/* wrap_line_html_block() ************************************************
 *
 * Wrap a single block of text
 *                   b: Output buffer
 *                   s: String to wrap
 *         wrap_length: Length to wrap to
 ************************************************************************/

static void
wrap_line_html_block(struct session *session,
                     struct buffer *b, char *s, unsigned long wrap_length)
{
    char c;
    unsigned long bytes, width, i;
    unsigned long column, new_column, new_stop;
    char *quote, *t;
    unsigned long quote_length;

    quote = s;
    quote_length = wrap_calc_quote_length(s);
    s += quote_length;

    if ((quote_length > 0) && (*s == ' '))
        s++;                    /* quote includes single space */

    column = wrap_print_quote(b, quote, quote_length, T);

    while (*s) {
        new_column = column;

        while (((c = *s) == ' ') || (c == '\t') || (c == '\015')
               || (c == '\012')) {
            if ((c == '\015') || (c == '\012')) {       /* CR or LF */
                s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;    /* CRLF */
                if (*s) {
                    for (i = 0; i < quote_length; i++)
                        if (s[i] != quote[i])
                            fatal("[wrap_line_html_block]: "
                                  "Inconsistent quote prefix");
                    s += quote_length;
                    if ((quote_length > 0) && (*s == ' '))
                        s++;    /* quote includes single space */
                    /* Preserve CRLF */
                    bputs(b, CRLF);
                    column = wrap_print_quote(b, quote, quote_length, T);
                    new_column = column;
                }
            } else if (c == '\t') {
                new_stop = (new_column / 8) + 1;        /* Next tab stop starts here */
                new_column = (new_stop * 8);
                s++;
            } else {            /* Space */
                new_column++;
                s++;
            }
        }
        if ((bytes = wrap_word_bytes(s)) == 0)
            break;

        width = utf8_calc_width(s, bytes);

        if (wrap_allow_break(quote_length, column) &&
            ((new_column + width) >= wrap_length)) {
            bputs(b, CRLF);
            column = wrap_print_quote(b, quote, quote_length, T);
        } else {
            while (column < new_column) {       /* Tab to correct column */
                bputc(b, ' ');
                column++;
            }
        }
        column += width;

        if ((!strncasecmp(s, "<http://", strlen("<http://")) ||
             !strncasecmp(s, "<https://", strlen("<https://"))) &&
            (bytes > strlen("<http://")) && (s[bytes-1] == '>')) {
            /* Prune leading '<' and trailing '>': add them back by hand */
            s++;
            bytes -= 2;
            
            t = pool_alloc(b->pool, bytes + 1);
            memcpy(t, s, bytes);
            t[bytes] = '\0';

            bprintf(b, "<a target=\"_blank\" href=\"%s\">", t);

            html_quote_char(b, '<');
            for (i = 0; i < bytes; i++)
                html_quote_char(b, s[i]);
            html_quote_char(b, '>');
            bputs(b, "</a>");

            s += bytes + 1;
        } else if (!strncasecmp(s, "http://", strlen("http://")) ||
                   !strncasecmp(s, "https://", strlen("https://"))) {
            /* Quietly remove trailing '.' or ',' from URL */
            BOOL add_trailing = NIL;

            if ((s[bytes - 1] == '.') || (s[bytes - 1] == ',')) {
                bytes--;
                add_trailing = T;
            }
            
            t = pool_alloc(b->pool, bytes + 1);
            memcpy(t, s, bytes);
            t[bytes] = '\0';

            bprintf(b, "<a target=\"_blank\" href=\"%s\">", t);

            for (i = 0; i < bytes; i++)
                html_quote_char(b, s[i]);
            bputs(b, "</a>");
            s += bytes;
            if (add_trailing) {
                bputc(b, *s);
                s++;
            }
        } else {
            for (i = 0; i < bytes; i++)
                html_quote_char(b, s[i]);
            s += bytes;
        }
    }
    bputs(b, CRLF);
}

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

/* wrap_line_html() ******************************************************
 *
 * Wrap by line, quoting HTML characters and adding colours
 *     b: Output buffer
 *     s: Input string
 *   len: Length to wrap to.
 ************************************************************************/

void
wrap_line_html(struct session *session,
               struct buffer *b, char *s, unsigned long wrap_length)
{
    struct config_theme *theme = session->theme_main;
    unsigned long len, i, depth, depth_max, depth_offset;
    char c;
    char *color[5];

    if (s == NIL)
        return;

    color[0] = theme->fgcolor_quote1;
    color[1] = theme->fgcolor_quote2;
    color[2] = theme->fgcolor_quote3;
    color[3] = theme->fgcolor_quote4;
    color[4] = NIL;

    for (depth_max = 0; color[depth_max]; depth_max++);

    while (*s) {
        len = wrap_getblock_len(s, NIL);
        c = s[len];
        s[len] = '\0';

        for (i = 0, depth = 0; i < len; i++) {
            if (wrap_isquote(s[i]))
                depth++;
            else if (!wrap_isspace(s[i]))
                break;
        }

        depth_offset = (depth > depth_max) ? depth_max : depth;

        if (depth_offset > 0)
            bprintf(b, "<span class=\"t_quote%d\">", depth_offset);
        wrap_line_html_block(session, b, s, wrap_length);
        if (depth_offset > 0)
            bputs(b, "</span>");
        s[len] = c;
        s += len;
    }
}

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

/* wrap_text_html_quotes () **********************************************
 *
 * Print string replacing significant HTML characters with eqivalent
 * escape sequences and wraping long lines.
 * translation stuff.
 *           b: Buffer
 *           t: String to print
 * line_length: Line length to wrap at
 *      indent: Indent wrapped lines (used for wrapping address lists)
 ***********************************************************************/

void
wrap_text_html_quotes(struct buffer *b, char *t,
                      unsigned long line_length, BOOL indent)
{
    unsigned char *s = (unsigned char *) t;
    unsigned char c;
    unsigned long count;
    unsigned long offset = 0L;

    if (!s)
        bputs(b, "(nil)");
    else {
        while ((c = *s++)) {
            switch (c) {
            case '\015':
                if (*s == '\012')
                    s++;
            case '\012':
                /* Convert CR, LF and CRLF into CRLF */
                bputs(b, "" CRLF);
                offset = 0;
                break;
            case '\t':
                count = 8 - (offset % 8);
                if ((offset + count) > line_length) {
                    /* Fold long line */
                    if (indent) {
                        bputs(b, CRLF "    ");
                        offset = 4;
                    } else {
                        bputs(b, "" CRLF);
                        offset = 0;
                    }
                    while ((*s == ' ') || (*s == '\t'))
                        s++;
                } else {
                    offset += count;
                    while (count-- > 0)
                        bputc(b, ' ');
                }
                break;
            case ' ':
                /* Should include next word */
                if (offset >= line_length) {
                    /* Fold long line */
                    if (indent) {
                        bputs(b, CRLF "    ");
                        offset = 4;
                    } else {
                        bputs(b, "" CRLF);
                        offset = 0;
                    }
                    while ((*s == ' ') || (*s == '\t'))
                        s++;
                } else {
                    bputc(b, ' ');
                    offset++;
                }
                break;
            default:
                html_quote_char(b, c);
                offset++;
                break;
            }
        }
    }
}
