/*
 * 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) {
        if (result != NULL) {
            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);
        }

        if (result != NULL) {
            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;
                }

                if (result != NULL) {
                    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(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);
}
