/*
 * 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 <math.h>
#include <limits.h>
#if defined(VX) || defined(LINUX)
#include <float.h>
#endif
#include "js-objectadapter.h"
#include "js-scriptruntime.h"
#include "js-exceptionhandler.h"
#include "js-nativeglobal.h"
#include "js-functionobject.h"
#include "js-utils.h"
#include "js-dtoa.h"
#include "js-nativenumber.h"

/* Internal data */
const jsString_t	JSNativeNumber::className = "Number";

/* Procedures */
void
JSNativeNumber::finishInit(JSContext *cx, JSScriptable *scope,
			JSFunctionObject *ctor, JSScriptable *proto)
{
    class NumberAdapter: JSObjectAdapter {
	public:
#define	defineNumberFunction(name)					\
	    static jsVal_t jsFunction_##name(JSContext *cx,		\
				JSScriptable *thisObj, int numArgs,	\
				jsVal_t *args, JSFunction *funObj) {	\
	        JSScriptable *scriptable;				\
									\
                scriptable = getScriptable(cx, thisObj,			\
				JSNativeNumber::className, funObj);	\
                                                                        \
                JSNativeNumber *_thisObj				\
				= (JSNativeNumber *) scriptable;	\
									\
                return _thisObj->jsFunction_##name(cx);			\
	    }
#define	defineNumberFunction1(name)					\
	    static jsVal_t jsFunction_##name(JSContext *cx,		\
				JSScriptable *thisObj, int numArgs,	\
				jsVal_t *args, JSFunction *funObj) {	\
	        JSScriptable *scriptable;				\
									\
                scriptable = getScriptable(cx, thisObj,			\
				JSNativeNumber::className, funObj);	\
                                                                        \
                JSNativeNumber *_thisObj				\
				= (JSNativeNumber *) scriptable;	\
									\
                return _thisObj->jsFunction_##name(cx, args[0]);	\
	    }

	    defineNumberFunction1(toString)
	    defineNumberFunction(valueOf)
	    defineNumberFunction1(toLocaleString)
	    defineNumberFunction1(toFixed)
	    defineNumberFunction1(toExponential)
	    defineNumberFunction1(toPrecision)
#undef	defineNumberFunction1
#undef	defineNumberFunction
    } ;
    static jsFunction_t	functions[] = {
#define	ItemAdapter(name, clazz, length)	\
				{ #name,  clazz::jsFunction_##name, length }
	ItemAdapter(toString, NumberAdapter, 1),
	ItemAdapter(valueOf, NumberAdapter, 0),
	ItemAdapter(toLocaleString, NumberAdapter, 1),
	ItemAdapter(toFixed, NumberAdapter, 1),
	ItemAdapter(toExponential, NumberAdapter, 1),
	ItemAdapter(toPrecision, NumberAdapter, 1),
#undef	ItemAdapter
    } ;

    ((JSScriptableObject *) proto)->defineFunctionProperties(cx, className,
				TableSize(functions), functions,
				JS_PA_DontEnum);
    
    const int attr = JS_PA_DontEnum | JS_PA_Permanent | JS_PA_ReadOnly;
    static struct {
	const jsString_t	name;
	double			value;
    } nameValues[] = {
	{ "NaN",		0.0			},
	{ "POSITIVE_INFINITY",	POSITIVE_INFINITY	},
	{ "NEGATIVE_INFINITY",	NEGATIVE_INFINITY	},
	{ "MAX_VALUE",		DBL_MAX			},
	{ "MIN_VALUE",		DBL_MIN			},
    } ;

    nameValues[0].value = JSScriptRuntime::NaN;
#ifndef VX
    *((uint64_t *) &nameValues[4].value) = 1;
#endif
    for (int i = 0; i < (int) TableSize(nameValues); i++) {
        ctor->defineProperty(cx, nameValues[i].name,
		JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, nameValues[i].value)),
		attr);
    }
}
jsVal_t
JSNativeNumber::jsConstructor(JSContext *cx, int numArgs,
			jsVal_t *args, JSFunction *ctorObj, jsBool_t inNewExpr)
{
    double d = numArgs >= 1
                   ? JSScriptRuntime::toNumber(cx, args[0])
                   : defaultValue;

    if (inNewExpr) {
        // new Number(val) creates a new Number object.
        return JS_OBJECT_TO_VAL(new (cx) JSNativeNumber(d));
    }
    // Number(val) converts val to a number value.
    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, d));
}
#ifndef DTOA
void
JSNativeNumber::normalize(jsString_t buf)
{
#if defined(SUNOS5)
    static jsChar_t Infinity[] = "Infinity";
    static jsChar_t Inf[] = "Inf";
    jsString_t s;

    s = strstr(buf, Inf);
    if (s != NULL) {
	memmove(s + TableSize(Infinity) - 1, s + TableSize(Inf) - 1,
		(strlen(s + TableSize(Inf) - 1) + 1) * sizeof (*s));
	memcpy(s, Infinity, (TableSize(Infinity) - 1) * sizeof (*s));
    }
#endif
}
int
JSNativeNumber::getPrecision(JSContext *cx, jsVal_t arg, int precisionMin,
			int precisionMax)
{
    int precision;

    if (arg == JS_VAL_VOID) {
        precision = 0;
    } else {
        precision = JSScriptRuntime::toInt32(cx, arg);
        if (precision < precisionMin || precision > precisionMax) {
             cx->exceptionHandler->throwEx(
                     cx,
                     JSNativeGlobal::constructError(cx, "RangeError",
				JSScriptRuntime::getMessage(
						cx, "msg.bad.precision"),
				this));
        }
    }
    return precision;
}
#else /* !DTOA */
jsVal_t
JSNativeNumber::num_to(JSContext *cx, jsVal_t arg, int zeroArgMode,
		int oneArgMode, int precisionMin, int precisionMax,
		int precisionOffset)
{
    int precision;

    if (arg == JS_VAL_VOID) {
        precision = 0;
        oneArgMode = zeroArgMode;
    } else {
        precision = JSScriptRuntime::toInt32(cx, arg);
        if (precision < precisionMin || precision > precisionMax) {
             cx->exceptionHandler->throwEx(
                     cx,
                     JSNativeGlobal::constructError(cx, 
				"RangeError",
				JSScriptRuntime::getMessage(cx,
					"msg.bad.precision %d", precision),
				this));
        }
    }
    JSStringBuffer result(cx);

    JSDToA::JS_dtostr(cx, &result, oneArgMode, precision + precisionOffset,
		doubleValue);
    return JS_STRING_TO_VAL(JS_NewGCString(cx, result.toString()));
}   
#endif /* DTOA */
jsString_t
JSNativeNumber::toString(JSContext *cx, jsVal_t base)
{
    int i = base == JS_VAL_VOID
            ? 10 
            : JSScriptRuntime::toInt32(cx, base);
    return JSScriptRuntime::numberToString(cx, doubleValue, i);
}

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

    JSScriptableObject::defineClass(cx, scope, proto, jsConstructor,
				finishInit);
}
jsVal_t
JSNativeNumber::jsFunction_toString(JSContext *cx, jsVal_t base) 
{
    jsString_t s = toString(cx, base);

    jsVal_t result =  JS_STRING_TO_VAL(JS_NewGCString(cx, s));

    JS_DeleteZoneObj(cx, s);
    return result;
}
jsVal_t
JSNativeNumber::jsFunction_valueOf(JSContext *cx)
{
    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, doubleValue));
}
jsVal_t
JSNativeNumber::jsFunction_toLocaleString(JSContext *cx, jsVal_t arg)
{
    return jsFunction_toString(cx, JS_VAL_VOID);
}

jsVal_t
JSNativeNumber::jsFunction_toFixed(JSContext *cx, jsVal_t arg)
{
#ifndef DTOA
    int precision = getPrecision(cx, arg, -20, MAX_PRECISION);

    if (doubleValue >= 1.0e20) {
	return jsFunction_toString(cx, JS_VAL_VOID);
    }
    jsChar_t buf[128], format[16];

    sprintf(format, "%%.%df", precision);
    sprintf(buf, format, doubleValue);
    normalize(buf);

    return JS_STRING_TO_VAL(JS_NewGCString(cx, buf));
#else /* !DTOA */
    return num_to(cx, arg, JSDToA::DTOSTR_FIXED, JSDToA::DTOSTR_FIXED,
		-20, MAX_PRECISION, 0);
#endif /* DTOA */
}
jsVal_t
JSNativeNumber::jsFunction_toExponential(JSContext *cx, jsVal_t arg)
{
#ifndef DTOA
    int precision = getPrecision(cx, arg, 0, MAX_PRECISION);

    jsChar_t buf[128], format[16];

    sprintf(format, "%%.%de", precision);
    sprintf(buf, format, doubleValue);
    normalize(buf);

    return JS_STRING_TO_VAL(JS_NewGCString(cx, buf));
#else /* !DTOA */
    return num_to(cx, arg, JSDToA::DTOSTR_STANDARD_EXPONENTIAL,
		JSDToA::DTOSTR_EXPONENTIAL, 0, MAX_PRECISION, 1);
#endif /* DTOA */
}
jsVal_t
JSNativeNumber::jsFunction_toPrecision(JSContext *cx, jsVal_t arg)
{
#ifndef DTOA
    int precision = getPrecision(cx, arg, 1, MAX_PRECISION);

    jsChar_t buf[128], format[16];

    sprintf(format, "%%.%dg", precision);
    sprintf(buf, format, doubleValue);
    normalize(buf);

    return JS_STRING_TO_VAL(JS_NewGCString(cx, buf));
#else /* !DTOA */
    return num_to(cx, arg, JSDToA::DTOSTR_STANDARD, JSDToA::DTOSTR_PRECISION,
		1, MAX_PRECISION, 0);
#endif /* DTOA */
}
jsString_t
JSNativeNumber::toString(JSContext *cx)
{
    return toString(cx, JS_VAL_VOID);
}
jsBool_t
JSNativeNumber::isInstanceOf(const jsString_t className)
{
    if (className == JSNativeNumber::className) return TRUE;
    return JSScriptableObject::isInstanceOf(className);
}
jsString_t
JSNativeNumber::getClassName(JSContext *cx)
{
    return JSNativeNumber::className;
}
JSNativeNumber::JSNativeNumber()
{
    doubleValue = defaultValue;
}
JSNativeNumber::JSNativeNumber(double number)
{
    doubleValue = number;
}

