/* $Cambridge: hermes/src/prayer/session/speller.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 which provides support routines to the spelling checker */

/* speller_create() *****************************************************
 *
 * Create a speller structure.
 ***********************************************************************/

struct speller *speller_create(struct pool *p)
{
    struct speller *speller = pool_alloc(p, sizeof(struct speller));

    speller->pool = p;
    speller->stream = NIL;
    speller->pid = 0;
    speller->line = NIL;
    speller->offset = 0;
    speller->ignore = NIL;

    return (speller);
}

/* speller_free() *******************************************************
 *
 * Free speller structure
 ***********************************************************************/

void speller_free(struct speller *speller)
{
    struct speller_ignore_list *current, *next;

    if (speller->pool)
        return;

    for (current = speller->ignore; current; current = next) {
        next = current->next;
        free(current->word);
        free(current);
    }
    speller->ignore = NIL;

    free(speller);
}

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

/* speller_start() ******************************************************
 *
 * Start up engine for spell check run.
 *  speller:
 *  session: 
 *     text: Input text to be spell checked
 * language: Ispell language e.g: "british", "american"
 * spell_skip_quoted: Use --mode=email in ispell.
 *
 * Returns: T if spell check engine started okay. NIL on error.
 ***********************************************************************/

BOOL
speller_start(struct speller *speller, struct session *session,
              char *text, char *language, BOOL spell_skip_quoted)
{
    pid_t childpid;
    int sockfd[2];
    int c;
    struct config *config = session->config;

    speller->language = pool_strdup(speller->pool, language);

    if (!((config->aspell_path && config->aspell_path[0]) ||
          (config->ispell_path && config->ispell_path[0]))) {
        session_paniclog(session, "[speller_start()] No speller defined");
        return (NIL);
    }

    if (!os_socketpair(sockfd)) {
        session_paniclog(session, "[speller_start()] socketpair()");
        return (NIL);
    }

    if ((childpid = fork()) < 0) {
        session_paniclog(session, "[speller_start()] fork() failed");
        return (NIL);
    }

    if (childpid == 0) {
        close(sockfd[0]);
        dup2(sockfd[1], 0);
        dup2(sockfd[1], 1);
        dup2(sockfd[1], 2);
        close(sockfd[1]);

        if (config->aspell_path) {
            if (spell_skip_quoted) {
                execl(config->aspell_path, "aspell", "-a", "--mode=email",
                      "--encoding=utf-8", "-d", speller->language, NULL);
            } else {
                execl(config->aspell_path, "aspell", "-a",
                      "-d", speller->language, NULL);
            }
        } else {
            execl(config->ispell_path, "ispell", "-a", "-d",
                  speller->language, NULL);
        }
                  
        session_fatal(session,
                      "[speller_start()] Failed to execl() ispell");
    }

    /* Parent */
    close(sockfd[1]);

    /* Create iostream with small block size and timeout */
    speller->stream = iostream_create(speller->pool, sockfd[0], 256);
    iostream_set_timeout(speller->stream, 30);

    /* Discard banner line */
    while (((c = iogetc(speller->stream)) != EOF) && (c != '\n'));

    if (c == EOF) {
        session_paniclog(session,
                         "[speller_start()] Failed to read banner line");
        return (NIL);
    }

    speller->ignore = NIL;
    speller->pid = childpid;
    speller->line = text;
    speller->offset = 0;
    speller->cursize = 0;
    speller->output = buffer_create(NIL, 4096); /* Small output buffer */
    speller->from_speller = buffer_create(NIL, 4096);   /* Small input  buffer */
    speller->no_changes = 0;
    speller->last_line = 0;     /* Offsets for last line of output */
    speller->last_line1 = 0;

    return (T);
}

/* speller_stop() *******************************************************
 *
 * Shut down current spell check session.
 ***********************************************************************/

void speller_stop(struct speller *speller)
{
    struct speller_ignore_list *current, *next;
    int status = 0;
    int rc = 0;

    if (speller->pid) {
        if (speller->output)
            buffer_free(speller->output);

        if (speller->from_speller)
            buffer_free(speller->from_speller);

        if (speller->stream)
            iostream_close(speller->stream);

        do {
            rc = waitpid(speller->pid, &status, 0);
        } while ((rc < 0) && (errno == EINTR));

        if ((rc >= 0) && WIFEXITED(status) && (WEXITSTATUS(status) != 0))
            log_misc("Non-zero return code from spell checker");

        if (!speller->pool) {
            for (current = speller->ignore; current; current = next) {
                next = current->next;
                free(current->word);
                free(current);
            }
        }
        speller->ignore = NIL;
    }

    speller->output = NIL;
    speller->from_speller = NIL;
    speller->pid = 0;
    speller->stream = NIL;
}

/* speller_active() ******************************************************
 *
 * Check whether spell check running running.
 ************************************************************************/

BOOL speller_active(struct speller *speller)
{
    if (speller->output && speller->from_speller && speller->stream)
        return (T);

    return (NIL);
}

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

/* speller_feedline() ***************************************************
 *
 * Feed next line of input into the speller checker. Munges first character
 * to avoid confusing ispell.
 ***********************************************************************/

void speller_feedline(struct speller *speller)
{
    char *s = speller->line;
    char c;

    /* Stop ispell from trying to interpret first character of output */
    ioputc('^', speller->stream);

    while ((c = *s++) && (c != '\015') && (c != '\012'))
        ioputc(c, speller->stream);

    ioputc('\n', speller->stream);      /* Flush line to ispell */
    ioflush(speller->stream);
}

/* speller_getline() *****************************************************
 *
 * Get line of output from the spell check engine.
 ************************************************************************/

char *speller_getline(struct speller *speller, struct pool *pool)
{
    struct buffer *b = speller->from_speller;
    unsigned long start = buffer_size(b);
    int c;

    while ((c = iogetc(speller->stream)) != EOF) {
        if (c == '\n')
            break;
        bputc(b, c);
    }

    return ((char *) buffer_fetch(b, start, buffer_size(b) - start, NIL));
}

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

/* speller_copy_line_from_offset() *************************************
 *
 * Copy remainder of current input line from draft into speller checker. 
 ***********************************************************************/

BOOL speller_copy_from_offset(struct speller *speller)
{
    char *s = speller->line;
    int c;

    utf8_find_offset(&s, speller->offset);

    /* Copy remainder of line to output buffer */
    while ((c = *s) && (c != '\015') && (c != '\012')) {
        bputc(speller->output, c);
        s++;
    }

    if (*s) {
        if ((s[0] == '\015') && (s[1] == '\012'))
            s += 2;
        else
            s += 1;
        bputs(speller->output, "" CRLF);
    }

    if (speller->last_line > 0) {
        speller->last_line1 = speller->last_line;
        speller->last_line = buffer_size(speller->output);
    } else
        speller->last_line = buffer_size(speller->output);

    speller->line = s;
    speller->offset = 0;

    return ((s[0]) ? T : NIL);
}

/* speller_copy_to_offset() *********************************************
 *
 * Copy input line from draft into output buffer up to given offset
 *  speller: 
 *   offset: Copy up to here
 ***********************************************************************/

void speller_copy_to_offset(struct speller *speller, unsigned long offset)
{
    char *s = speller->line;
    unsigned long i;

    utf8_find_offset(&s, speller->offset);

    for (i = speller->offset; i < offset; i++)
        utf8_print_char(speller->output, &s);

    speller->offset = offset;
}

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

/* speller_copy_remainder() *********************************************
 *
 * Copy remainder of current input line to output buffer
 ***********************************************************************/

void speller_copy_remainder(struct speller *speller)
{
    char *s = speller->line;
    char c;

    utf8_find_offset(&s, speller->offset);

    while ((c = *s++))
        bputc(speller->output, c);
}

/* speller_copy_string() ************************************************
 *
 * Copy given string into output buffer
 ***********************************************************************/

void speller_copy_string(struct speller *speller, char *s)
{
    char c;

    while ((c = *s++))
        bputc(speller->output, c);
}

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

/* speller_copy_output() ************************************************
 *
 * Copy given string into output buffer
 ***********************************************************************/

char *speller_output(struct speller *speller)
{
    return (buffer_fetch(speller->output, 0,
                         buffer_size(speller->output), NIL));
}

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

/* speller_print_lastline() *********************************************
 *
 * Print last last from output buffer
 *  speller:
 *        b: Target buffer
 ***********************************************************************/

void speller_print_lastline(struct speller *speller, struct buffer *b)
{
    int c;

    buffer_seek_offset(speller->output, speller->last_line1);

    while ((c = bgetc(speller->output)) != EOF)
        html_quote_char(b, c);
}

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

/* speller_record_cursize() *********************************************
 *
 * Record current size
 ***********************************************************************/

void speller_record_cursize(struct speller *speller, unsigned long size)
{
    speller->cursize = size;
}

/* speller_fetch_cursize() **********************************************
 *
 * Fetch current size
 ***********************************************************************/

unsigned long speller_fetch_cursize(struct speller *speller)
{
    return (speller->cursize);
}

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

/* speller_skip_input() *************************************************
 *
 * Skip input
 ***********************************************************************/

void speller_skip_input(struct speller *speller, unsigned long size)
{
    speller->offset += size;
}

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

/* speller_add_ignore_list() ********************************************
 *
 * Add word to (temporary) ignore list
 ***********************************************************************/

void speller_add_ignore_list(struct speller *speller, char *word)
{
    struct speller_ignore_list *new
        = pool_alloc(speller->pool, sizeof(struct speller_ignore_list));

    /* Add word to front of list */
    new->word = pool_strdup(speller->pool, word);
    new->next = speller->ignore;
    speller->ignore = new;
}

/* speller_check_ignore_list() ******************************************
 *
 * Check word against current temporary ignore list.
 ***********************************************************************/

BOOL speller_check_ignore_list(struct speller *speller, char *word)
{
    struct speller_ignore_list *current = speller->ignore;

    while (current) {
        if (!strcasecmp(current->word, word))
            return (T);
        current = current->next;
    }
    return (NIL);
}
