/*
 * 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 <stdio.h>
#include <ctype.h>
#include <float.h>
#include <math.h>
#ifdef VX
#include <string.h>
#include <private/mathP.h>
#endif
#include "js-utils.h"
#include "js-objectadapter.h"
#include "js-stringbuffer.h"
#include "js-scriptruntime.h"
#include "js-regexpproxy.h"
#include "js-evaluatorexception.h"
#include "js-exceptionhandler.h"
#include "js-functionobject.h"
#if defined(USE_UTF8) || defined(USE_SJIS)
# include "js-stringruntime.h"
#endif
#include "js-nativestring.h"

/* Internal data */
const jsString_t	JSNativeString::className = "String";
const jsString_t	JSNativeString::defaultValue = "";

/* Procedures */
void
JSNativeString::finishInit(JSContext *cx, JSScriptable *scope,
			JSFunctionObject *ctor, JSScriptable *proto)
{
    class StringAdapter: public JSObjectAdapter {
 	public:
#define	defineStringFunction(name, clazz)				\
	    static jsVal_t						\
		name(JSContext *cx, JSScriptable *thisObj, int numArgs,	\
			jsVal_t *args, JSFunction *funObj) {		\
                JSScriptable *scriptable				\
			= getScriptable(cx, thisObj, clazz::className,	\
					funObj);			\
									\
                clazz *_thisObj = (clazz *) scriptable;			\
									\
	        jsString_t value					\
			= JSScriptRuntime::toString(cx, args[0]);	\
	        jsVal_t result = _thisObj->jsFunction_##name(cx, value);\
									\
	        JS_DeleteZoneObj(cx, value);				\
	        return result;						\
            }

	    JS_OA_DefineGetter(length, JSNativeString)

	    JS_OA_DefineFunction(toString, JSNativeString)
	    JS_OA_DefineFunction(valueOf, JSNativeString)
	    JS_OA_DefineFunction(bold, JSNativeString)
	    JS_OA_DefineFunction(italics, JSNativeString)
	    JS_OA_DefineFunction(fixed, JSNativeString)
	    JS_OA_DefineFunction(strike, JSNativeString)
	    JS_OA_DefineFunction(small, JSNativeString)
	    JS_OA_DefineFunction(big, JSNativeString)
	    JS_OA_DefineFunction(blink, JSNativeString)
	    JS_OA_DefineFunction(sup, JSNativeString)
	    JS_OA_DefineFunction(sub, JSNativeString)
	    defineStringFunction(fontsize, JSNativeString)
	    defineStringFunction(fontcolor, JSNativeString)
	    defineStringFunction(link, JSNativeString)
	    defineStringFunction(anchor, JSNativeString)
#undef defineStringFunction
    } ;
    static jsFunction_t	functions[] = {
#define	Item(name, length)	{ #name,  jsFunction_##name, length }
#define	ItemAdapter(name, clazz, length)	\
				{ #name,  clazz::name, length }
	Item(charAt, -1),		Item(charCodeAt, -1),
	Item(indexOf, -1),		Item(lastIndexOf, -1),
	Item(split, -1),		Item(substring, -1),
	Item(toLowerCase, -1),		Item(toUpperCase, -1),
	Item(substr, -1),		Item(concat, -1),
	Item(slice, -1),		Item(equals, -1),
	Item(equalsIgnoreCase, -1),	Item(match, -1),
	Item(search, -1),		Item(replace, -1),
	ItemAdapter(toString, StringAdapter, 0),
	ItemAdapter(valueOf, StringAdapter, 0),
	ItemAdapter(bold, StringAdapter, 0),
	ItemAdapter(italics, StringAdapter, 0),
	ItemAdapter(fixed, StringAdapter, 0),
	ItemAdapter(strike, StringAdapter, 0),
	ItemAdapter(small, StringAdapter, 0),
	ItemAdapter(big, StringAdapter, 0),
	ItemAdapter(blink, StringAdapter, 0),
	ItemAdapter(sup, StringAdapter, 0),
	ItemAdapter(sub, StringAdapter, 0),
	ItemAdapter(fontsize, StringAdapter, 1),
	ItemAdapter(fontcolor, StringAdapter, 1),
	ItemAdapter(link, StringAdapter, 1),
	ItemAdapter(anchor, StringAdapter, 1),
#undef	ItemAdapter
#undef	Item
    } ;
    static jsProperty_t properties[] = {
#define ItemG(name, clazz)                                            \
        {                                                              \
            #name, JS_PA_ReadOnly | JS_PA_Permanent | JS_PA_DontEnum , \
            &clazz::name##_getter, NULL                          \
        }
        ItemG(length, StringAdapter)
#undef ItemG
    };
    static jsFunction_t	ctorFunctions[] = {
#define	Item(name, length)	{ #name,  jsStaticFunction_##name, length }
	Item(fromCharCode, -1),
#undef	Item
    } ;

    ((JSScriptableObject *) proto)->defineFunctionProperties(cx, className,
				TableSize(functions), functions,
				JS_PA_DontEnum);
    ((JSScriptableObject *) proto)->defineProperties(cx,
                                TableSize(properties), properties);
    ctor->defineFunctionProperties(cx, className, TableSize(ctorFunctions),
			ctorFunctions, JS_PA_DontEnum);

    static const struct {
        jsString_t      name;
        short           value;
    } specialLengths[] = {
        { "indexOf",	2 },	{ "lastIndexOf", 2 },
	{ "substring",	2 },	{ "toUpperCase", 0 },
	{ "toLowerCase", 0 },	{ "toString", 0 },
    } ;

    for (int i = 0; i < (int) TableSize(specialLengths); ++i) {
        jsVal_t value;

        (void) proto->get(cx, specialLengths[i].name, proto, &value);
        ((JSFunctionObject *) JS_VAL_TO_OBJECT(value))
                                        ->setLength(specialLengths[i].value);
    }
}
jsVal_t
JSNativeString::jsConstructor(JSContext *cx, int numArgs,
			jsVal_t *args, JSFunction *ctorObj, jsBool_t inNewExpr)
{
    jsVal_t result;

    jsString_t s = numArgs >= 1
			? JSScriptRuntime::toString(cx, args[0])
			: defaultValue;
    if (inNewExpr) {
        // new String(val) creates a new String object.
        result = JS_OBJECT_TO_VAL(new (cx) JSNativeString(cx, s));
	if (s != defaultValue) {
	    JS_DeleteZoneObj(cx, s);
	}
	return result;
    }
    // String(val) converts val to a string value.
    result = JS_STRING_TO_VAL(JS_NewGCString(cx, s));
    if (s != defaultValue) {
	JS_DeleteZoneObj(cx, s);
    }
    return result;
}
jsVal_t
JSNativeString::jsStaticFunction_fromCharCode(JSContext *cx,
				JSScriptable *thisObj, int numArgs,
				jsVal_t *args, JSFunction *funObj)
{
    if (numArgs < 1) {
        return JS_STRING_TO_VAL(JS_NewGCString(cx, ""));
    }
    JSStringBuffer s(cx);

    for (int i = 0; i < numArgs; i++) {
#if defined(USE_UTF8) || defined(USE_SJIS)
	uint16_t c = JSScriptRuntime::toUint16(cx, args[i]);

	if (c < 0x80) {
	    s.append((jsChar_t) c);
	} else {
	    jsChar_t mb[JSStringRuntime::_MB_CUR_MAX];
	    int n;

	    n = JSStringRuntime::wctomb(mb, c);
	    s.append(mb, 0, n);
	}
#else
	// at uncode version, cast is not needed
        s.append((jsChar_t) JSScriptRuntime::toUint16(cx, args[i]));
#endif
    }
    return JS_STRING_TO_VAL(JS_NewGCString(cx, s.toString()));
}
/**
 *
 * See ECMA 15.5.4.[4,5]
 */
jsVal_t
JSNativeString::jsFunction_charAt(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t *padArgs;

    if (numArgs < 1) {
        args = padArgs = JSScriptRuntime::padArguments(cx, numArgs, args, 1);
    } else {
	padArgs = NULL;
    }
 
    // this'll return 0 if undefined... seems
    // to be ECMA.
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    double pos = JSScriptRuntime::toInteger(cx, args[0]);
 
    if (padArgs != NULL) {
	JS_DeleteZoneObj(cx, padArgs);
    }
#if defined(USE_UTF8) || defined(USE_SJIS)
    int length = JSStringRuntime::mb_strlen(target);
#else
    int length = strlen(target);
#endif

    if (pos < 0 || pos >= length) {
	JS_DeleteZoneObj(cx, target);
        return JS_STRING_TO_VAL(JS_NewGCString(cx, ""));
    }
 
#if defined(USE_UTF8) || defined(USE_SJIS)
    jsString_t substring
		= JSStringRuntime::mb_substring(cx, target, (int) pos,
					(int) pos + 1);
#else
    jsString_t substring = JS_Substring(cx, target, (int) pos, (int) pos + 1);
#endif
    jsVal_t result = JS_STRING_TO_VAL(JS_NewGCString(cx, substring));

    JS_DeleteZoneObj(cx, substring);
    JS_DeleteZoneObj(cx, target);

    return result;
}
jsVal_t
JSNativeString::jsFunction_charCodeAt(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t *padArgs;

    if (numArgs < 1) {
        args = padArgs = JSScriptRuntime::padArguments(cx, numArgs, args, 1);
    } else {
	padArgs = NULL;
    }

    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    double pos = JSScriptRuntime::toInteger(cx, args[0]);

    if (padArgs != NULL) {
	JS_DeleteZoneObj(cx, padArgs);
    }
#if defined(USE_UTF8) || defined(USE_SJIS)
    int length = JSStringRuntime::mb_strlen(target);
#else
    int length = strlen(target);
#endif

    if (pos < 0 || pos >= length) {
	JS_DeleteZoneObj(cx, target);
        return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, JSScriptRuntime::NaN));
    }
#if defined(USE_UTF8) || defined(USE_SJIS)
    wchar_t wc;

    JSStringRuntime::mbtowc(&wc,
			target + JSStringRuntime::mb_postobpos(target,
							(int) pos),
			JSStringRuntime::_MB_CUR_MAX);
 
    jsVal_t result = JSScriptRuntime::toValue(cx, (double) wc);
#else
    jsVal_t result = JSScriptRuntime::toValue(cx, (double) target[(int) pos]);
#endif

    JS_DeleteZoneObj(cx, target);

    return result;
}
/**
  * See ECMA 15.5.4.6. 
  */ 
jsVal_t
JSNativeString::jsFunction_indexOf(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t *padArgs;

    if (numArgs < 2) {
        args = padArgs = JSScriptRuntime::padArguments(cx, numArgs, args, 2);
    } else {
	padArgs = NULL;
    }
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    jsString_t search = JSScriptRuntime::toString(cx, args[0]);
    double begin = JSScriptRuntime::toInteger(cx, args[1]);
 
    if (padArgs != NULL) {
	JS_DeleteZoneObj(cx, padArgs);
    }
#if defined(USE_UTF8) || defined(USE_SJIS)
    int length = JSStringRuntime::mb_strlen(target);
#else
    int length = strlen(target);
#endif

    if (begin > length) {
	JS_DeleteZoneObj(cx, search);
	JS_DeleteZoneObj(cx, target);
	return JS_INT_TO_VAL(-1);
    } else {
	jsVal_t result;

        if (begin < 0) {
            begin = 0;
	}
#if defined(USE_UTF8) || defined(USE_SJIS)
	jsString_t s
		= strstr(target + JSStringRuntime::mb_postobpos(target,
							(int) begin), search);
#else
	jsString_t s = strstr(target + (int) begin, search);
#endif

	if (s != NULL) {
#if defined(USE_UTF8) || defined(USE_SJIS)
	    result = JS_INT_TO_VAL(
				JSStringRuntime::mb_bpostopos(target,
							s - target));
#else
	    result = JS_INT_TO_VAL(s - target);
#endif
	} else {
	    result = JS_INT_TO_VAL(-1);
	}
	JS_DeleteZoneObj(cx, search);
	JS_DeleteZoneObj(cx, target);
	return result;
    }
}
/**
 * See ECMA 15.5.4.7
 */
jsVal_t
JSNativeString::jsFunction_lastIndexOf(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t *padArgs;

    if (numArgs < 2) {
        args = padArgs = JSScriptRuntime::padArguments(cx, numArgs, args, 2);
    } else {
	padArgs = NULL;
    }
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    jsString_t search = JSScriptRuntime::toString(cx, args[0]);
    double end = JSScriptRuntime::toNumber(cx, args[1]);

    if (padArgs != NULL) {
	JS_DeleteZoneObj(cx, padArgs);
    }
#if defined(USE_UTF8) || defined(USE_SJIS)
    int length = JSStringRuntime::mb_strlen(target);
#else
    int length = strlen(target);
#endif

#ifdef VX
    if (isNan(end) || (int) end > length) {
#else
    if (isnan(end) || (int) end > length) {
#endif
        end = (double) length;
    } else if (end < 0.0) {
        end = 0.0;
    }

#if defined(USE_UTF8) || defined(USE_SJIS)
    int searchLength = JSStringRuntime::mb_strlen(search);
#else
    int searchLength = strlen(search);
#endif
    int pos = (int) end;

    if (pos > length - searchLength) {
	pos = length - searchLength;
    }
#if defined(USE_UTF8) || defined(USE_SJIS)
    int bpos = JSStringRuntime::mb_postobpos(target, pos);
    jsString_t s = target;
    jsString_t last = NULL;
    while ((s = strstr(s, search))) {
	if (s > target + bpos) break;
	last = s;
	s += JSStringRuntime::mblen(s, JSStringRuntime::_MB_CUR_MAX);
    }
#else
    jsString_t s = target;
    jsString_t last = NULL;
    while ((s = strstr(s, search))) {
	if (s > target + pos) break;
	last = s;
	s += 1;
    }
#endif

#if defined(USE_UTF8) || defined(USE_SJIS)
    jsVal_t result
		= JS_INT_TO_VAL((last != NULL) ?
			JSStringRuntime::mb_bpostopos(target, last - target)
			: -1);
    JS_DeleteZoneObj(cx, search);
    JS_DeleteZoneObj(cx, target);
    return result;
#else
    JS_DeleteZoneObj(cx, search);
    JS_DeleteZoneObj(cx, target);
    return JS_INT_TO_VAL((last != NULL) ? (last - target) : -1);
#endif
}
/**
 * See ECMA 15.5.4.8.  Modified to match JS 1.2 - optionally takes
 * a limit argument and accepts a regular expression as the split
 * argument.
 */
jsVal_t
JSNativeString::jsFunction_split(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));

    // create an empty Array to return;
    JSScriptable *scope = getTopLevelScope(cx, (JSNativeFunction *) funObj);
    JSScriptable *result
		= JSScriptRuntime::newObject(cx, scope, "Array", 0, NULL);

    cx->pushLocalVal(JS_OBJECT_TO_VAL(result));

    // return an array consisting of the target if no separator given
    // don't check against undefined, because we want
    // 'fooundefinedbar'.split(void 0) to split to ['foo', 'bar']
    if (numArgs < 1) {
        result->put(cx, 0, result,
		JS_STRING_TO_VAL(JS_NewGCString(cx, target)));
	JS_DeleteZoneObj(cx, target);

	cx->popLocalVals(1);
        return JS_OBJECT_TO_VAL(result);
    }

    // Use the second argument as the split limit, if given.
    jsBool_t limited = (numArgs > 1) && (args[1] != JS_VAL_VOID);
    uint32_t limit = 0;  // Initialize to avoid warning.
#if defined(USE_UTF8) || defined(USE_SJIS)
    int length = JSStringRuntime::mb_strlen(target);
    int bytes = strlen(target);
#else
    int length = strlen(target);
#endif

    if (limited) {
        /* Clamp limit between 0 and 1 + string length. */
        limit = JSScriptRuntime::toUint32(cx, args[1]);
        if (limit > (uint32_t) length) {
            limit = 1 + length;
	}
    }

    jsString_t separator = NULL;
    int matchlen = 0;
    jsVal_t re = JS_VAL_NULL;
    JSRegExpProxy *reProxy = cx->getRegExpProxy();

    if (reProxy != NULL && reProxy->isRegExp(args[0])) {
        re = args[0];
    } else {
        separator = JSScriptRuntime::toString(cx, args[0]);
        matchlen = strlen(separator);
    }

    // split target with separator or re
    int ip = 0;
    int match;
    int len = 0;
    jsBool_t matched = FALSE;
    int	parensLength = 0;
    jsString_t *parens;

    while ((match = find_split(cx, funObj, target, separator, re, &ip, 
			&matchlen, &matched, &parens, &parensLength)) >= 0) {
#if defined(USE_UTF8) || defined(USE_SJIS)
        if ((limited && ((uint32_t) len >= limit)) || (match > bytes)) {
#else
        if ((limited && ((uint32_t) len >= limit)) || (match > length)) {
#endif
            break;
	}

        jsString_t substr;

        if (length == 0) {
            substr = target;
        } else {
            substr = JS_Substring(cx, target, ip, match);
	}

        result->put(cx, len, result,
		JS_STRING_TO_VAL(JS_NewGCString(cx, substr)));
        len++;
	if (substr != target) {
	   JS_DeleteZoneObj(cx, substr);
	}
	/*
	 * Imitate perl's feature of including parenthesized substrings
	 * that matched part of the delimiter in the new array, after the
	 * split substring that was delimited.
	 */
        if (re != JS_VAL_NULL && matched == TRUE) {
	    int size = parensLength;
            for (int num = 0; num < size; num++) {
                if (limited && ((uint32_t) len >= limit)) {
                    break;
		}

                result->put(cx, len, result,
			JS_STRING_TO_VAL(JS_NewGCString(cx, parens[num])));
		JS_DeleteZoneObj(cx, parens[num]);
                len++;
            }
	    if (parens != NULL) {
		JS_DeleteZoneObj(cx, parens);
	    }
            matched = FALSE;
        }            
        ip = match + matchlen;

        if (cx->getLanguageVersion() < JSContext::VERSION_1_3
            && cx->getLanguageVersion() != JSContext::VERSION_DEFAULT) {
	    /*
	     * Deviate from ECMA to imitate Perl, which omits a final
	     * split unless a limit argument is given and big enough.
	     */
            if (!limited && ip == length) {
                break;
	    }
        }
    }
    if (separator != NULL) JS_DeleteZoneObj(cx, separator);
    JS_DeleteZoneObj(cx, target);

    cx->popLocalVals(1);
    return JS_OBJECT_TO_VAL(result);
}
/**
 *
 * See ECMA 15.5.4.[9,10]
 */
jsVal_t
JSNativeString::jsFunction_substring(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsVal_t *padArgs;

    if (numArgs < 1) {
        args = padArgs = JSScriptRuntime::padArguments(cx, numArgs, args, 1);
	numArgs = 1;
    } else {
	padArgs = NULL;
    }

    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
#if defined(USE_UTF8) || defined(USE_SJIS)
    int length = JSStringRuntime::mb_strlen(target);
#else
    int length = strlen(target);
#endif
    double start = JSScriptRuntime::toInteger(cx, args[0]);
    double end;

    if (start < 0) {
	start = 0;
    } else if (start > length) {
	start = length;
    }

    if (numArgs == 1) {
	end = length;
    } else {
        end = JSScriptRuntime::toInteger(cx, args[1]);
	if (end < 0) {
	    end = 0;
	} else if (end > length) {
	    end = length;
	}

        // swap if end < start
        if (end < start) {
            if (cx->getLanguageVersion() != JSContext::VERSION_1_2) {
                double temp = start;
                start = end;
                end = temp;
            } else {
                // Emulate old JDK1.0 java.lang.String.substring()
                end = start;
            }
        }
    }
#if defined(USE_UTF8) || defined(USE_SJIS)
    jsString_t substring
			= JSStringRuntime::mb_substring(cx, target,
						(int) start, (int) end);
#else
    jsString_t substring = JS_Substring(cx, target, (int) start, (int) end);
#endif
    jsVal_t result JS_STRING_TO_VAL(JS_NewGCString(cx, substring));

    JS_DeleteZoneObj(cx, substring);
    JS_DeleteZoneObj(cx, target);

    return result;
}
/**
 *
 * See ECMA 15.5.4.[11,12]
 */
jsVal_t
JSNativeString::jsFunction_toLowerCase(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    int length = strlen(target);
    
#if defined(USE_UTF8) || defined(USE_SJIS)
    for (int i = 0; i < length; ) {
	wchar_t wc;
	int n = JSStringRuntime::mbtowc(&wc, target + i,
					JSStringRuntime::_MB_CUR_MAX);

	if (wc < 0x80) {
	    target[i] = tolower(target[i]);
	}
	i += n;
    }
#else
    for (int i = 0; i < length; ++i) {
	target[i] = tolower(target[i]);
    }
#endif
    jsVal_t result = JS_STRING_TO_VAL(JS_NewGCString(cx, target));

    JS_DeleteZoneObj(cx, target);

    return result;
}
jsVal_t
JSNativeString::jsFunction_toUpperCase(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    int length = strlen(target);
    
#if defined(USE_UTF8) || defined(USE_SJIS)
    for (int i = 0; i < length; ) {
	wchar_t wc;
	int n = JSStringRuntime::mbtowc(&wc, target + i,
					JSStringRuntime::_MB_CUR_MAX);

	if (wc < 0x80) {
	    target[i] = toupper(target[i]);
	}
	i += n;
    }
#else
    for (int i = 0; i < length; ++i) {
	target[i] = toupper(target[i]);
    }
#endif
    jsVal_t result = JS_STRING_TO_VAL(JS_NewGCString(cx, target));

    JS_DeleteZoneObj(cx, target);

    return result;
}

/**
 * Non-ECMA methods.
 */
jsVal_t
JSNativeString::jsFunction_substr(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));

    if (numArgs < 1) {
        jsVal_t result = JS_STRING_TO_VAL(JS_NewGCString(cx, target));

	JS_DeleteZoneObj(cx, target);

	return result;
    }

    double begin = JSScriptRuntime::toInteger(cx, args[0]);
    double end;
#if defined(USE_UTF8) || defined(USE_SJIS)
    int length = JSStringRuntime::mb_strlen(target);
#else
    int length = strlen(target);
#endif

    if (begin < 0) {
        begin += length;
        if (begin < 0) {
            begin = 0;
	}
    } else if (begin > length) {
        begin = length;
    }

    if (numArgs == 1) {
        end = length;
    } else {
        end = JSScriptRuntime::toInteger(cx, args[1]);
        if (end < 0) {
            end = 0;
	}
        end += begin;
        if (end > length) {
            end = length;
	}
    }

#if defined(USE_UTF8) || defined(USE_SJIS)
    jsString_t substring
		= JSStringRuntime::mb_substring(cx, target, (int) begin,
					(int) end);
#else
    jsString_t substring = JS_Substring(cx, target, (int) begin, (int) end);
#endif
    jsVal_t result = JS_STRING_TO_VAL(JS_NewGCString(cx, substring));

    JS_DeleteZoneObj(cx, substring);
    JS_DeleteZoneObj(cx, target);

    return result;
}

/**
 * Python-esque sequence operations.
 */
jsVal_t
JSNativeString::jsFunction_concat(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    JSStringBuffer result(cx);
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));

    result.append(target);
    JS_DeleteZoneObj(cx, target);

    for (int i = 0; i < numArgs; i++) {
	jsString_t string = JSScriptRuntime::toString(cx, args[i]);

        result.append(string);
	JS_DeleteZoneObj(cx, string);
    }
    jsVal_t rval = JS_STRING_TO_VAL(JS_NewGCString(cx, result.toString()));

    return rval;
}

jsVal_t
JSNativeString::jsFunction_slice(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    jsVal_t result;

    if (numArgs != 0) {
        double begin = JSScriptRuntime::toInteger(cx, args[0]);
        double end;
#if defined(USE_UTF8) || defined(USE_SJIS)
	int length = JSStringRuntime::mb_strlen(target);
#else
	int length = strlen(target);
#endif

        if (begin < 0) {
            begin += length;
            if (begin < 0) {
                begin = 0;
	    }
        } else if (begin > length) {
            begin = length;
        }

        if (numArgs == 1) {
            end = length;
        } else {
            end = JSScriptRuntime::toInteger(cx, args[1]);
            if (end < 0) {
                end += length;
                if (end < 0) {
                    end = 0;
		}
            } else if (end > length) {
                end = length;
            }
            if (end < begin) {
                end = begin;
	    }
        }
#if defined(USE_UTF8) || defined(USE_SJIS)
	jsString_t substring
			= JSStringRuntime::mb_substring(cx, target, (int) begin,
							(int) end);
#else
	jsString_t substring
			= JS_Substring(cx, target, (int) begin, (int) end);
#endif

	result = JS_STRING_TO_VAL(JS_NewGCString(cx, substring));

	JS_DeleteZoneObj(cx, substring);
	JS_DeleteZoneObj(cx, target);
	return result;
    }
    result = JS_STRING_TO_VAL(JS_NewGCString(cx, target));
    JS_DeleteZoneObj(cx, target);

    return result;
}
jsVal_t
JSNativeString::jsFunction_equals(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    jsString_t strOther = JSScriptRuntime::toString(cx, args[0]);

    jsVal_t result = JS_BOOLEAN_TO_VAL(strcmp(target, strOther) == 0);

    JS_DeleteZoneObj(cx, strOther);
    JS_DeleteZoneObj(cx, target);
    return result;
}

jsVal_t
JSNativeString::jsFunction_equalsIgnoreCase(JSContext *cx,
				JSScriptable *thisObj, int numArgs,
				jsVal_t *args, JSFunction *funObj)
{
    jsString_t target
		= JSScriptRuntime::toString(cx, JS_OBJECT_TO_VAL(thisObj));
    jsString_t strOther = JSScriptRuntime::toString(cx, args[0]);
    jsVal_t result = JS_BOOLEAN_TO_VAL(strcasecmp(target, strOther) == 0);

    JS_DeleteZoneObj(cx, strOther);
    JS_DeleteZoneObj(cx, target);
    return result;
}
jsVal_t
JSNativeString::jsFunction_match(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    return checkReProxy(cx)->match(cx, thisObj, numArgs, args, funObj);
}

jsVal_t
JSNativeString::jsFunction_search(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    return checkReProxy(cx)->search(cx, thisObj, numArgs, args, funObj);
}

jsVal_t
JSNativeString::jsFunction_replace(JSContext *cx, JSScriptable *thisObj,
				int numArgs, jsVal_t *args, JSFunction *funObj)
{
    return checkReProxy(cx)->replace(cx, thisObj, numArgs, args, funObj);
}
/*
 * Used by js_split to find the next split point in target,
 * starting at offset ip and looking either for the given
 * separator substring, or for the next re match.  ip and
 * matchlen must be reference variables (assumed to be arrays of
 * length 1) so they can be updated in the leading whitespace or
 * re case.
 *
 * Return -1 on end of string, >= 0 for a valid index of the next
 * separator occurrence if found, or the string length if no
 * separator is found.
 */
int
JSNativeString::find_split(JSContext *cx, JSFunction *funObj,
			jsString_t target, jsString_t separator,
			jsVal_t re, int *ip, int *matchlen,
			jsBool_t *matched, jsString_t **parensp,
			int *parensLength)
{
    int i = *ip;
    int length = strlen(target);
    int separatorLength = separator != NULL ? strlen(separator) : 0;
    int version = cx->getLanguageVersion();

    /*
     * Perl4 special case for str.split(' '), only if the user has selected
     * JavaScript1.2 explicitly.  Split on whitespace, and skip leading w/s.
     * Strange but true, apparently modeled after awk.
     */
    if (version == JSContext::VERSION_1_2 && re == JS_VAL_NULL
	&& separatorLength == 1 && separator[0] == ' ') {
        /* Skip leading whitespace if at front of str. */
        if (i == 0) {
            while (i < length && isspace(target[i])) {
                i++;
	    }
            *ip = i;
        }

        /* Don't delimit whitespace at end of string. */
        if (i == length) {
            return -1;
	}

        /* Skip over the non-whitespace chars. */
        while (i < length && !isspace(target[i])) {
            i++;
	}

        /* Now skip the next run of whitespace. */
        int j = i;
        while (j < length && isspace(target[j])) {
            j++;
	}

        /* Update matchlen to count delimiter chars. */
        *matchlen = j - i;
        return i;
    }

    /*
     * Stop if past end of string.  If at end of string, we will
     * return target length, so that
     *
     *  "ab,".split(',') => new Array("ab", "")
     *
     * and the resulting array converts back to the string "ab,"
     * for symmetry.  NB: This differs from perl, which drops the
     * trailing empty substring if the LIMIT argument is omitted.
     */
    if (i > length) {
        return -1;
    }

    /*
     * Match a regular expression against the separator at or
     * above index i.  Return -1 at end of string instead of
     * trying for a match, so we don't get stuck in a loop.
     */
    if (re != JS_VAL_NULL) {
        return cx->getRegExpProxy()->find_split(cx, funObj, target, 
					separator, re, ip, matchlen, matched,
					parensp, parensLength);
    }

    /*
     * Deviate from ECMA by never splitting an empty string by any separator
     * string into a non-empty array (an array of length 1 that contains the
     * empty string).
     */
    if (version != JSContext::VERSION_DEFAULT
	&& version < JSContext::VERSION_1_3 && length == 0) {
        return -1;
    }

    /*
     * Special case: if sep is the empty string, split str into
     * one character substrings.  Let our caller worry about
     * whether to split once at end of string into an empty
     * substring.
     *
     * For 1.2 compatibility, at the end of the string, we return the length as
     * the result, and set the separator length to 1 -- this allows the caller
     * to include an additional null string at the end of the substring list.
     */
    if (separatorLength == 0) {
        if (version == JSContext::VERSION_1_2) {
            if (i == length) {
                *matchlen = 1;
                return i;
            }
	    return i + 1;
        }
	return (i == length) ? -1 : i + 1;
    }

    /* Punt to j.l.s.indexOf; return target length if seperator is
     * not found.
     */
    if (*ip >= length) {
        return length;
    }
    jsString_t s;
    
    i = (s = strstr(target + *ip, separator)) ? (s - target) : -1;
    return (i != -1) ? i : length;
}
JSRegExpProxy *
JSNativeString::checkReProxy(JSContext *cx)
{
    JSRegExpProxy *result = cx->getRegExpProxy();

    if (result == NULL) {
        cx->exceptionHandler->throwEx(
                cx,
		JSContext::reportRuntimeError(cx,
				JSContext::getMessage(cx, "msg.no.regexp")));
    }
    return result;
}
/**
 * HTML composition aids.
 */
jsVal_t
JSNativeString::tagify(JSContext *cx, jsString_t begin, jsString_t end,
		jsString_t value)
{
    JSStringBuffer result(cx);

    result.append('<');
    result.append(begin);
    if (value != NULL) {
        result.append('=');
        result.append(value);
    }
    result.append('>');
    result.append(this->string);
    result.append("</");
    result.append((end == NULL) ? begin : end);
    result.append('>');

    return JS_STRING_TO_VAL(JS_NewGCString(cx, result.toString()));
}

void
JSNativeString::defineClass(JSContext *cx, JSScriptable *scope)
{
    JSScriptable *proto = new (cx) JSNativeString(cx);

    JSScriptableObject::defineClass(cx, scope, proto, jsConstructor,
				finishInit);
}
/* ECMA 15.5.4.2: 'the toString function is not generic.' */
jsVal_t
JSNativeString::jsFunction_toString(JSContext *cx)
{
    return JS_STRING_TO_VAL(string);
}
jsVal_t
JSNativeString::jsFunction_valueOf(JSContext *cx)
{
    return JS_STRING_TO_VAL(string);
}
jsVal_t
JSNativeString::jsFunction_bold(JSContext *cx)
{
    return tagify(cx, "B", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_italics(JSContext *cx)
{
    return tagify(cx, "I", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_fixed(JSContext *cx)
{
    return tagify(cx, "TT", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_strike(JSContext *cx)
{
    return tagify(cx, "STRIKE", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_small(JSContext *cx)
{
    return tagify(cx, "SMALL", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_big(JSContext *cx)
{
    return tagify(cx, "BIG", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_blink(JSContext *cx)
{
    return tagify(cx, "BLINK", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_sup(JSContext *cx)
{
    return tagify(cx, "SUP", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_sub(JSContext *cx)
{
    return tagify(cx, "SUB", NULL, NULL);
}
jsVal_t
JSNativeString::jsFunction_fontsize(JSContext *cx, jsString_t value)
{
    return tagify(cx, "FONT SIZE", "FONT", value);
}
jsVal_t
JSNativeString::jsFunction_fontcolor(JSContext *cx, jsString_t value)
{
    return tagify(cx, "FONT COLOR", "FONT", value);
}
jsVal_t
JSNativeString::jsFunction_link(JSContext *cx, jsString_t value)
{
    return tagify(cx, "A HREF", "A", value);
}
jsVal_t
JSNativeString::jsFunction_anchor(JSContext *cx, jsString_t value)
{
    return tagify(cx, "A NAME", "A", value);
}
jsBool_t
JSNativeString::jsGet_length(JSContext *cx, jsVal_t *value)
{
#if defined(USE_UTF8) || defined(USE_SJIS)
    *value = JSScriptRuntime::toValue(cx,
				(double) JSStringRuntime::mb_strlen(string));
#else
    *value = JSScriptRuntime::toValue(cx, (double) strlen(string));
#endif
    return TRUE;
}
jsBool_t
JSNativeString::isInstanceOf(const jsString_t className)
{
    if (className == JSNativeString::className) return TRUE;
    return JSScriptableObject::isInstanceOf(className);
}
jsString_t
JSNativeString::getClassName(JSContext *cx)
{
    return className;
}
/* Make array-style property lookup work for strings.
 * XXX is this ECMA?  A version check is probably needed. In js too.
 */
jsBool_t
JSNativeString::get(JSContext *cx, int index, JSScriptable *start,
		jsVal_t *value)
{
    if (index >= 0 && index < (int) strlen(string)) {
	jsString_t s = JS_Substring(cx, string, index, index + 1);
	*value = JS_STRING_TO_VAL(JS_NewGCString(cx, s));

	JS_DeleteZoneObj(cx, s);
        return TRUE;
    }
    return JSScriptableObject::get(cx, index, start, value);
}

void
JSNativeString::put(JSContext *cx, int index, JSScriptable *start,
		jsVal_t value)
{
    if (index >= 0 && index < (int) strlen(string)) {
        return;
    }
    JSScriptableObject::put(cx, index, start, value);
}
jsVal_t
JSNativeString::unwrap()
{
    return JS_STRING_TO_VAL(string);
}
void
JSNativeString::mark(JSContext *cx)
{
    JSGCHeap *gcHeap = &cx->runtime->gcHeap;

    JSScriptableObject::mark(cx);
    if (string != NULL) {
	gcHeap->markObj(string);
    }
}

JSNativeString::JSNativeString(JSContext *cx, jsString_t string)
{
    this->string = NULL;	// gc safe
    this->string = JS_NewGCString(cx, string);
}
JSNativeString::JSNativeString(JSContext *cx)
{
    string = NULL;	// gc safe
    string = JS_NewGCString(cx, defaultValue);
}

