/*
 * 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 linux
#define max(a, b)               ((a) < (b) ? (b) : (a))
#define min(a, b)               ((a) > (b) ? (b) : (a))
#endif
#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
}

