/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/

 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.

 * The Original Code is Rhino code, released
 * May 6, 1999.

 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (the "GPL"), in which case the
 * provisions of the GPL are applicable instead of those above.
 * If you wish to allow use of your version of this file only
 * under the terms of the GPL and not to allow others to use your
 * version of this file under the NPL, indicate your decision by
 * deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL.  If you do not delete
 * the provisions above, a recipient may use your version of this
 * file under either the NPL or the GPL.
 */
/* Header file */
#include <stdlib.h>
#include <stdio.h>
#ifdef VX
#include <string.h>
#endif
#ifdef SUNOS5
#include <macros.h>
#endif
#if defined(USE_UTF8) || defined(USE_SJIS)
#include "js-stringruntime.h"
#endif
#include "js-context.h"
#include "js-linebuffer.h"

#if !defined(max)
#define max(a, b)       ((a) < (b) ? (b) : (a))
#endif
#if !defined(min)
#define min(a, b)       ((a) > (b) ? (b) : (a))
#endif

/* Procedures */
int
 JSLineBuffer::getOrigin()
{
    int origin;

    origin = offset;
    origin += lastPos;
    if (stringStart >= 0) {
        origin = min(origin, stringStart);
    }
    return min(origin, prevStart);
}

jsBool_t JSLineBuffer::readIntoBuffer(jsChar_t * buffer, jsChar_t * oldbuffer, int origin)
{
    memcpy(buffer, oldbuffer + origin, (end - origin));

    offset -= origin;
    if (stringStart >= 0) {
        stringStart -= origin;
    }
    prevStart -= origin;
    end -= origin;

    int n = in->read(buffer, end, bufferLength - end);
    if (n < 0) {
        end = offset;
        hitEOF = TRUE;
        return FALSE;
    }
    end += n;
    return TRUE;
}

jsBool_t JSLineBuffer::extend(JSContext * cx)
{
    if (eof())
        return FALSE;

    int origin = getOrigin();

    jsChar_t *oldbuffer = buffer;

    bufferLength *= 2;
    buffer = (jsChar_t *) JS_NewZoneObj(cx, sizeof(*buffer) * bufferLength);

    jsBool_t result = readIntoBuffer(buffer, oldbuffer, origin);

    JS_DeleteZoneObj(cx, oldbuffer);

    return result;
}

int JSLineBuffer::read(JSContext * cx)  // returns sjis code now
{
    int lastPos;
#if defined(USE_UTF8) || defined(USE_SJIS)
    jsBool_t reported = FALSE;
#endif

    lastPos = 0;
    for (;;) {
        if (end == offset && !fill(cx)) {
            this->lastPos = 0;
            return -1;
        }

        if (buffer[offset] == '\r' || buffer[offset] == '\n') {
            if (buffer[offset] == '\r') {
                // if the next character is a newline, skip past it.
                if ((offset + 1) < end) {
                    if (buffer[offset + 1] == '\n') {
                        lastPos -= 1;
                        offset++;
                    }
                }
                else {
                    // fallback
                    fill(cx);
                    continue;
                }
            }
            lastPos -= 1;
            offset++;
            prevStart = lineStart;
            lineStart = offset;
            lineno++;

            this->lastPos = lastPos;
            return '\n';
        }

#ifdef USE_UTF8
        char *ptr;
        int c;

        ptr = buffer + offset;
        c = *ptr & 0xff;
        if (c >= 0x80) {
            wchar_t wc;
            int n;

            n = JSStringRuntime::isUTF8String(ptr, end - offset);
            if (n > 0) {
                offset += n;
                lastPos -= n;
                this->lastPos = lastPos;
                (void)JSStringRuntime::mb_lentowc(&wc, ptr, n);
                return wc;
            }
            else if (n == 0) {
                // fallback
                fill(cx);
                continue;
            }
            n = JSStringRuntime::isSJString(ptr, end - offset);
            if (n > 0) {
                if (!reported) {
                    JSContext::reportWarning(cx, JSContext::getMessage(cx, "msg.illegal.character Shift-JIS"), NULL, getLineno(), getLine(cx), getOffset());
                    reported = TRUE;
                }
                offset += n;
                lastPos -= n;
                this->lastPos = lastPos;
                lastPos = 0;
                continue;
            }
            else if (n == 0) {
                // fallback
                fill(cx);
                continue;
            }
        }
#elif defined(USE_SJIS)
        ->on Shift - JIS environment < -
#endif
                break;
    }
    lastPos -= 1;

    this->lastPos = lastPos;
    return (int)(buffer[offset++] & 0xff);
}

void JSLineBuffer::unread()
{
    if (lastPos == 0)
        return;

    offset += lastPos;
    lastPos = 0;
    if (buffer[offset] == '\r' || buffer[offset] == '\n') {
        lineStart = prevStart;
        lineno--;
    }
}
int JSLineBuffer::peek(JSContext * cx)
{
    int c, lastPos;

    lastPos = this->lastPos;
    c = read(cx);
    if (c < 0)
        return c;

    unread();
    this->lastPos += lastPos;

    return c;
}

jsBool_t JSLineBuffer::match(JSContext * cx, jsChar_t c)
{
    int mc;

    lastPos = this->lastPos;
    mc = read(cx);
    if (mc < 0)
        return FALSE;

    if (mc == c) {
        return TRUE;
    }
    unread();
    this->lastPos += lastPos;

    return FALSE;
}

jsString_t JSLineBuffer::getLine(JSContext * cx)
{
    int length = 0;

    while (TRUE) {
        int i;

        for (i = lineStart; i < end; ++i) {
            int c = buffer[i];

            if (c == '\r' || c == '\n')
                break;
        }
        if (i < end || !extend(cx)) {
            length = i - lineStart;
            break;
        }
    }
    jsString_t result = (jsString_t) JS_NewZoneObj(cx,
            sizeof(*result) * (length + 1));

    memcpy(result, buffer + lineStart, sizeof(*result) * length);
    result[length] = '\0';

    return result;
}

int JSLineBuffer::getOffset()
{
    return offset - lineStart;
}

void JSLineBuffer::startString()
{
    stringStart = offset + lastPos;
}

jsString_t JSLineBuffer::getString(JSContext * cx)
{
#ifdef FASTER
    if (stringStart < 0)
        return NULL;

    int length = offset - stringStart;

    if (length + 1 > stringLength) {
        if (string != NULL) {
            JS_DeleteZoneObj(cx, string);
        }
        string = (jsString_t) JS_NewZoneObj(cx, sizeof(*string) * (length + 1));
        stringLength = length + 1;
    }
    memcpy(string, buffer + stringStart, sizeof(*string) * length);
    string[length] = '\0';

    return string;
#else /* FASTER */
    if (stringStart < 0)
        return NULL;

    int length = offset - stringStart;
    jsString_t result = (jsString_t) JS_NewZoneObj(cx,
            sizeof(*result) * (length + 1));

    memcpy(result, buffer + stringStart, sizeof(*result) * length);
    result[length] = '\0';

    return result;
#endif /* FASTER */
}

jsBool_t JSLineBuffer::fill(JSContext * cx)
{
    const int maxCharLength = 6;

    if (eof())
        return FALSE;

    int origin = getOrigin();

    if (origin < maxCharLength && (bufferLength - end) < maxCharLength) {
        return extend(cx);
    }
    return readIntoBuffer(buffer, buffer, origin);
}

void JSLineBuffer::destroy(JSContext * cx)
{
    JS_DeleteZoneObj(cx, buffer);
#ifdef FASTER
    if (string != NULL)
        JS_DeleteZoneObj(cx, string);
#endif
}

JSLineBuffer::JSLineBuffer(JSContext * cx, JSReader * in, int lineno)
{
    this->in = in;
    this->lineno = lineno;

    offset = 0;
    lastPos = 0;
    prevStart = lineStart = 0;
    stringStart = -1;
    end = 0;
    hitEOF = FALSE;
    bufferLength = 512;
    buffer = (jsChar_t *) JS_NewZoneObj(cx, sizeof(*buffer) * bufferLength);

#ifdef FASTER
    stringLength = 0;
    string = NULL;
#endif
}
