/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include <pjlib-util/scanner.h>
#include <pj/ctype.h>
#include <pj/string.h>
#include <pj/except.h>
#include <pj/os.h>
#include <pj/log.h>
#include <pj/errno.h>
#include <pj/assert.h>

#define THIS_FILE   "scanner.c"

#define PJ_SCAN_IS_SPACE(c)             ((c)==' ' || (c)=='\t')
#define PJ_SCAN_IS_NEWLINE(c)           ((c)=='\r' || (c)=='\n')
#define PJ_SCAN_IS_PROBABLY_SPACE(c)    ((c) <= 32)
#define PJ_SCAN_CHECK_EOF(s)            (s != scanner->end)


#if defined(PJ_SCANNER_USE_BITWISE) && PJ_SCANNER_USE_BITWISE != 0
#  include "scanner_cis_bitwise.c"
#else
#  include "scanner_cis_uint.c"
#endif


static void pj_scan_syntax_err(pj_scanner *scanner)
{
    (*scanner->callback)(scanner);
}


PJ_DEF(void) pj_cis_add_range(pj_cis_t *cis, int cstart, int cend)
{
    /* Can not set zero. This is the requirement of the parser. */
    pj_assert(cstart > 0);

    while (cstart != cend) {
        PJ_CIS_SET(cis, cstart);
        ++cstart;
    }
}

PJ_DEF(void) pj_cis_add_alpha(pj_cis_t *cis)
{
    pj_cis_add_range( cis, 'a', 'z'+1);
    pj_cis_add_range( cis, 'A', 'Z'+1);
}

PJ_DEF(void) pj_cis_add_num(pj_cis_t *cis)
{
    pj_cis_add_range( cis, '0', '9'+1);
}

PJ_DEF(void) pj_cis_add_str( pj_cis_t *cis, const char *str)
{
    while (*str) {
        PJ_CIS_SET(cis, *str);
        ++str;
    }
}

PJ_DEF(void) pj_cis_add_cis( pj_cis_t *cis, const pj_cis_t *rhs)
{
    int i;
    for (i=0; i<256; ++i) {
        if (PJ_CIS_ISSET(rhs, i))
            PJ_CIS_SET(cis, i);
    }
}

PJ_DEF(void) pj_cis_del_range( pj_cis_t *cis, int cstart, int cend)
{
    while (cstart != cend) {
        PJ_CIS_CLR(cis, cstart);
        cstart++;
    }
}

PJ_DEF(void) pj_cis_del_str( pj_cis_t *cis, const char *str)
{
    while (*str) {
        PJ_CIS_CLR(cis, *str);
        ++str;
    }
}

PJ_DEF(void) pj_cis_invert( pj_cis_t *cis )
{
    unsigned i;
    /* Can not set zero. This is the requirement of the parser. */
    for (i=1; i<256; ++i) {
        if (PJ_CIS_ISSET(cis,i))
            PJ_CIS_CLR(cis,i);
        else
            PJ_CIS_SET(cis,i);
    }
}

PJ_DEF(void) pj_scan_init( pj_scanner *scanner, char *bufstart, 
                           pj_size_t buflen, unsigned options, 
                           pj_syn_err_func_ptr callback )
{
    PJ_CHECK_STACK();

    scanner->begin = scanner->curptr = bufstart;
    scanner->end = bufstart + buflen;
    scanner->line = 1;
    scanner->start_line = scanner->begin;
    scanner->callback = callback;
    scanner->skip_ws = options;

    if (scanner->skip_ws) 
        pj_scan_skip_whitespace(scanner);
}


PJ_DEF(void) pj_scan_fini( pj_scanner *scanner )
{
    PJ_CHECK_STACK();
    PJ_UNUSED_ARG(scanner);
}

PJ_DEF(void) pj_scan_skip_whitespace( pj_scanner *scanner )
{
    register char *s = scanner->curptr;

    while (PJ_SCAN_IS_SPACE(*s)) {
        ++s;
    }

    if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_NEWLINE)) {
        for (;;) {
            if (*s == '\r') {
                ++s;
                if (*s == '\n') ++s;
                ++scanner->line;
                scanner->curptr = scanner->start_line = s;
            } else if (*s == '\n') {
                ++s;
                ++scanner->line;
                scanner->curptr = scanner->start_line = s;
            } else if (PJ_SCAN_IS_SPACE(*s)) {
                do {
                    ++s;
                } while (PJ_SCAN_IS_SPACE(*s));
            } else {
                break;
            }
        }
    }

    if (PJ_SCAN_IS_NEWLINE(*s) && (scanner->skip_ws & PJ_SCAN_AUTOSKIP_WS_HEADER)==PJ_SCAN_AUTOSKIP_WS_HEADER) {
        /* Check for header continuation. */
        scanner->curptr = s;

        if (*s == '\r') {
            ++s;
        }
        if (*s == '\n') {
            ++s;
        }
        scanner->start_line = s;

        if (PJ_SCAN_IS_SPACE(*s)) {
            register char *t = s;
            do {
                ++t;
            } while (PJ_SCAN_IS_SPACE(*t));

            ++scanner->line;
            scanner->curptr = t;
        }
    } else {
        scanner->curptr = s;
    }
}

PJ_DEF(void) pj_scan_skip_line( pj_scanner *scanner )
{
    char *s;

    if (pj_scan_is_eof(scanner)) {
        return;
    }

    s = pj_memchr(scanner->curptr, '\n', scanner->end - scanner->curptr);
    if (!s) {
        scanner->curptr = scanner->end;
    } else {
        scanner->curptr = scanner->start_line = s+1;
        scanner->line++;
   }
}

PJ_DEF(int) pj_scan_peek( pj_scanner *scanner,
                          const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
        pj_scan_syntax_err(scanner);
        return -1;
    }

    /* Don't need to check EOF with PJ_SCAN_CHECK_EOF(s) */
    while (pj_cis_match(spec, *s))
        ++s;

    pj_strset3(out, scanner->curptr, s);
    return *s;
}


PJ_DEF(int) pj_scan_peek_n( pj_scanner *scanner,
                             pj_size_t len, pj_str_t *out)
{
    char *endpos = scanner->curptr + len;

    if (endpos > scanner->end) {
        pj_scan_syntax_err(scanner);
        return -1;
    }

    pj_strset(out, scanner->curptr, len);
    return *endpos;
}


PJ_DEF(int) pj_scan_peek_until( pj_scanner *scanner,
                                const pj_cis_t *spec, 
                                pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
        pj_scan_syntax_err(scanner);
        return -1;
    }

    while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match( spec, *s))
        ++s;

    pj_strset3(out, scanner->curptr, s);
    return *s;
}


PJ_DEF(void) pj_scan_get( pj_scanner *scanner,
                          const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;

    pj_assert(pj_cis_match(spec,0)==0);

    if (pj_scan_is_eof(scanner) || !pj_cis_match(spec, *s)) {
        pj_scan_syntax_err(scanner);
        return;
    }

    do {
        ++s;
    } while (pj_cis_match(spec, *s));
    /* No need to check EOF here (PJ_SCAN_CHECK_EOF(s)) because
     * buffer is NULL terminated and pj_cis_match(spec,0) should be
     * false.
     */

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
        pj_scan_skip_whitespace(scanner);    
    }
}


PJ_DEF(void) pj_scan_get_unescape( pj_scanner *scanner,
                                   const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;
    char *dst = s;

    pj_assert(pj_cis_match(spec,0)==0);

    /* Must not match character '%' */
    pj_assert(pj_cis_match(spec,'%')==0);

    if (pj_scan_is_eof(scanner) || (!pj_cis_match(spec, *s) && *s != '%')) {
        pj_scan_syntax_err(scanner);
        return;
    }

    out->ptr = s;
    do {
        if (*s == '%') {
            if (s+3 <= scanner->end && pj_isxdigit(*(s+1)) && 
                pj_isxdigit(*(s+2))) 
            {
                *dst = (pj_uint8_t) ((pj_hex_digit_to_val(*(s+1)) << 4) +
                                      pj_hex_digit_to_val(*(s+2)));
                ++dst;
                s += 3;
            } else {
                *dst++ = *s++;
                *dst++ = *s++;
                break;
            }
        }
        
        if (pj_cis_match(spec, *s)) {
            char *start = s;
            do {
                ++s;
            } while (pj_cis_match(spec, *s));

            if (dst != start) pj_memmove(dst, start, s-start);
            dst += (s-start);
        } 
        
    } while (*s == '%');

    scanner->curptr = s;
    out->slen = (dst - out->ptr);

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
        pj_scan_skip_whitespace(scanner);    
    }
}


PJ_DEF(void) pj_scan_get_quote( pj_scanner *scanner,
                                int begin_quote, int end_quote, 
                                pj_str_t *out)
{
    char beg = (char)begin_quote;
    char end = (char)end_quote;
    pj_scan_get_quotes(scanner, &beg, &end, 1, out);
}

PJ_DEF(void) pj_scan_get_quotes(pj_scanner *scanner,
                                const char *begin_quote, const char *end_quote,
                                int qsize, pj_str_t *out)
{
    register char *s = scanner->curptr;
    int qpair = -1;
    int i;

    pj_assert(qsize > 0);

    /* Check and eat the begin_quote. */
    for (i = 0; i < qsize; ++i) {
        if (*s == begin_quote[i]) {
            qpair = i;
            break;
        }
    }
    if (qpair == -1) {
        pj_scan_syntax_err(scanner);
        return;
    }
    ++s;

    /* Loop until end_quote is found. 
     */
    do {
        /* loop until end_quote is found. */
        while (PJ_SCAN_CHECK_EOF(s) && *s != '\n' && *s != end_quote[qpair]) {
            ++s;
        }

        /* check that no backslash character precedes the end_quote. */
        if (*s == end_quote[qpair]) {
            if (*(s-1) == '\\') {
                char *q = s-2;
                char *r = s-2;

                while (r != scanner->begin && *r == '\\') {
                    --r;
                }
                /* break from main loop if we have odd number of backslashes */
                if (((unsigned)(q-r) & 0x01) == 1) {
                    break;
                }
                ++s;
            } else {
                /* end_quote is not preceeded by backslash. break now. */
                break;
            }
        } else {
            /* loop ended by non-end_quote character. break now. */
            break;
        }
    } while (1);

    /* Check and eat the end quote. */
    if (*s != end_quote[qpair]) {
        pj_scan_syntax_err(scanner);
        return;
    }
    ++s;

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
        pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(void) pj_scan_get_n( pj_scanner *scanner,
                            unsigned N, pj_str_t *out)
{
    if (scanner->curptr + N > scanner->end) {
        pj_scan_syntax_err(scanner);
        return;
    }

    pj_strset(out, scanner->curptr, N);
    
    scanner->curptr += N;

    if (!pj_scan_is_eof(scanner) &&
        PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws)
    {
        pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(int) pj_scan_get_char( pj_scanner *scanner )
{
    register char *s = scanner->curptr;
    int chr;

    if (s >= scanner->end || !*s) {
        pj_scan_syntax_err(scanner);
        return 0;
    }

    chr = *s;

    ++s;
    scanner->curptr = s;
    if (PJ_SCAN_CHECK_EOF(s) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
        scanner->skip_ws)
    {
        pj_scan_skip_whitespace(scanner);
    }
    return chr;
}


PJ_DEF(void) pj_scan_get_newline( pj_scanner *scanner )
{
    if (pj_scan_is_eof(scanner) || !PJ_SCAN_IS_NEWLINE(*scanner->curptr)) {
        pj_scan_syntax_err(scanner);
        return;
    }

    /* We have checked scanner->curptr validity above */
    if (*scanner->curptr == '\r') {
        ++scanner->curptr;
    }
    if (!pj_scan_is_eof(scanner) && *scanner->curptr == '\n') {
        ++scanner->curptr;
    }

    ++scanner->line;
    scanner->start_line = scanner->curptr;

    /**
     * This probably is a bug, see PROTOS test #2480.
     * This would cause scanner to incorrectly eat two new lines, e.g.
     * when parsing:
     *   
     *  Content-Length: 120\r\n
     *  \r\n
     *  <space><space><space>...
     *
     * When pj_scan_get_newline() is called to parse the first newline
     * in the Content-Length header, it will eat the second newline
     * too because it thinks that it's a header continuation.
     *
     * if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
     *    pj_scan_skip_whitespace(scanner);
     * }
     */
}


PJ_DEF(void) pj_scan_get_until( pj_scanner *scanner,
                                const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
        pj_scan_syntax_err(scanner);
        return;
    }

    while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match(spec, *s)) {
        ++s;
    }

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
        scanner->skip_ws)
    {
        pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(void) pj_scan_get_until_ch( pj_scanner *scanner, 
                                   int until_char, pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
        pj_scan_syntax_err(scanner);
        return;
    }

    while (PJ_SCAN_CHECK_EOF(s) && *s != until_char) {
        ++s;
    }

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
        scanner->skip_ws)
    {
        pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(void) pj_scan_get_until_chr( pj_scanner *scanner,
                                     const char *until_spec, pj_str_t *out)
{
    register char *s = scanner->curptr;
    pj_size_t speclen;

    if (s >= scanner->end) {
        pj_scan_syntax_err(scanner);
        return;
    }

    speclen = strlen(until_spec);
    while (PJ_SCAN_CHECK_EOF(s) && !memchr(until_spec, *s, speclen)) {
        ++s;
    }

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (!pj_scan_is_eof(scanner) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
        scanner->skip_ws)
    {
        pj_scan_skip_whitespace(scanner);
    }
}

PJ_DEF(void) pj_scan_advance_n( pj_scanner *scanner,
                                 unsigned N, pj_bool_t skip_ws)
{
    if (scanner->curptr + N > scanner->end) {
        pj_scan_syntax_err(scanner);
        return;
    }

    scanner->curptr += N;

    if (!pj_scan_is_eof(scanner) && 
        PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && skip_ws)
    {
        pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(int) pj_scan_strcmp( pj_scanner *scanner, const char *s, int len)
{
    if (scanner->curptr + len > scanner->end) {
        pj_scan_syntax_err(scanner);
        return -1;
    }
    return strncmp(scanner->curptr, s, len);
}


PJ_DEF(int) pj_scan_stricmp( pj_scanner *scanner, const char *s, int len)
{
    if (scanner->curptr + len > scanner->end) {
        pj_scan_syntax_err(scanner);
        return -1;
    }
    return pj_ansi_strnicmp(scanner->curptr, s, len);
}

PJ_DEF(int) pj_scan_stricmp_alnum( pj_scanner *scanner, const char *s, 
                                   int len)
{
    if (scanner->curptr + len > scanner->end) {
        pj_scan_syntax_err(scanner);
        return -1;
    }
    return strnicmp_alnum(scanner->curptr, s, len);
}

PJ_DEF(void) pj_scan_save_state( const pj_scanner *scanner, 
                                 pj_scan_state *state)
{
    state->curptr = scanner->curptr;
    state->line = scanner->line;
    state->start_line = scanner->start_line;
}


PJ_DEF(void) pj_scan_restore_state( pj_scanner *scanner, 
                                    pj_scan_state *state)
{
    scanner->curptr = state->curptr;
    scanner->line = state->line;
    scanner->start_line = state->start_line;
}
