/*
 * 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>
#ifdef VX
#include <private/mathP.h>
#endif
#ifdef SUNOS5
#include <ieeefp.h>
#endif
#include "js-scriptruntime.h"
#include "js-functionobject.h"
#include "js-nativemath.h"

/* Internal Macros */
#ifdef VX
#define M_E             2.7182818284590452354
#define M_LOG2E         1.4426950408889634074
#define M_LOG10E        0.43429448190325182765
#define M_LN2           0.69314718055994530942
#define M_LN10          2.30258509299404568402
#define M_PI            3.14159265358979323846
#define M_PI_2          1.57079632679489661923
#define M_PI_4          0.78539816339744830962
#define M_1_PI          0.31830988618379067154
#define M_2_PI          0.63661977236758134308
#define M_2_SQRTPI      1.12837916709551257390
#define M_SQRT2         1.41421356237309504880
#define M_SQRT1_2       0.70710678118654752440
#endif
#if defined( SUNOS5 ) || 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
/* Internal data */
const jsString_t JSNativeMath::className = "Math";

/* Procedures */
jsVal_t
JSNativeMath::mmax(JSContext *cx, JSScriptable *thisObj, int numArgs,
		jsVal_t *args, JSFunction *funObj)
{
    double result = NEGATIVE_INFINITY;

    if (numArgs == 0) {
	return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, result));
    }
    for (int i = 0; i < numArgs; i++) {
        double d = JSScriptRuntime::toNumber(cx, args[i]);

#ifdef VX
        if (isNan(d)) {
#else
        if (isnan(d)) {
#endif
	    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, d));
	}
        result = max(result, d);
    }
    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, result));
#undef	max
}
jsVal_t
JSNativeMath::mmin(JSContext *cx, JSScriptable *thisObj, int numArgs,
		jsVal_t *args, JSFunction *funObj)
{
    double result = POSITIVE_INFINITY;

    if (numArgs == 0) {
	return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, result));
    }
    for (int i = 0; i < numArgs; i++) {
        double d = JSScriptRuntime::toNumber(cx, args[i]);

#ifdef VX
        if (isNan(d)) {
#else
        if (isnan(d)) {
#endif
	    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, d));
	}
        result = min(result, d);
    }
    return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, result));
#undef	min
}

void
JSNativeMath::defineClass(JSContext *cx, JSScriptable *scope) 
{
    class MathAdapter {
	public:
	    static  jsVal_t call(JSContext *cx, double (*method)()) {
		double d = (*method)();

#ifdef FASTER
		return JSScriptRuntime::toValue(cx, d);
#else
		return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, d));
#endif
	    }
	    static  jsVal_t call1(JSContext *cx, jsVal_t arg,
				double (*method)(double)) {
		double d = (*method)(JSScriptRuntime::toNumber(cx, arg));

#ifdef FASTER
		return JSScriptRuntime::toValue(cx, d);
#else
		return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, d));
#endif
	    }
	    static  jsVal_t call2(JSContext *cx, jsVal_t arg1, jsVal_t arg2,
				double (*method)(double, double)) {
		double d = (*method)(JSScriptRuntime::toNumber(cx, arg1),
				JSScriptRuntime::toNumber(cx, arg2));

#ifdef FASTER
		return JSScriptRuntime::toValue(cx, d);
#else
		return JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, d));
#endif
	    }
#define	defineMathFunction(name)					\
	    static jsVal_t m##name(JSContext *cx,			\
				JSScriptable *thisObj, int numArgs,	\
				jsVal_t *args, JSFunction *funObj) {	\
									\
	        return call(cx, JSNativeMath::m##name);			\
	    }
#define	defineMathFunction1(name)					\
	    static jsVal_t m##name(JSContext *cx,			\
				JSScriptable *thisObj, int numArgs,	\
				jsVal_t *args, JSFunction *funObj) {	\
									\
	        return call1(cx, args[0], JSNativeMath::m##name);	\
	    }
#define	defineMathFunction2(name)					\
	    static jsVal_t m##name(JSContext *cx,			\
				JSScriptable *thisObj, int numArgs,	\
				jsVal_t *args, JSFunction *funObj) {	\
									\
	        return call2(cx, args[0], args[1],			\
				JSNativeMath::m##name);			\
	    }

	    defineMathFunction1(atan)
	    defineMathFunction2(atan2)
	    defineMathFunction1(ceil)
	    defineMathFunction1(cos)
	    defineMathFunction1(floor)
	    defineMathFunction(random)
	    defineMathFunction1(sin)
	    defineMathFunction1(sqrt)
	    defineMathFunction1(tan)

	    defineMathFunction1(abs)
	    defineMathFunction1(acos)
	    defineMathFunction1(asin)
	    defineMathFunction1(round)
	    defineMathFunction2(pow)
	    defineMathFunction1(exp)
	    defineMathFunction1(log)
#undef	defineMathFunction1
#undef	defineMathFunction2
    } ;
    static jsFunction_t functions[] = {
#define Item(name, length)      { #name,  JSNativeMath::m##name, length }
#define ItemAdapter(name, clazz, length)	\
				{ #name,  clazz::m##name, length }
	ItemAdapter(atan, MathAdapter, 1),
	ItemAdapter(atan2, MathAdapter, 2),
	ItemAdapter(ceil, MathAdapter, 1),
	ItemAdapter(cos, MathAdapter, 1),
	ItemAdapter(floor, MathAdapter, 1),
	ItemAdapter(random, MathAdapter, 0),
	ItemAdapter(sin, MathAdapter, 1),
	ItemAdapter(sqrt, MathAdapter, 1),
	ItemAdapter(tan, MathAdapter, 1),

	ItemAdapter(abs, MathAdapter, 1),
	ItemAdapter(acos, MathAdapter, 1),
	ItemAdapter(asin, MathAdapter, 1),
	Item(max, 0),
	Item(min, 0),
	ItemAdapter(round, MathAdapter, 1),
	ItemAdapter(pow, MathAdapter, 2),
	ItemAdapter(exp, MathAdapter, 1),
	ItemAdapter(log, MathAdapter, 1),
#undef	ItemAdapter
#undef	Item
    } ;

    JSNativeMath *math = new (cx) JSNativeMath();

    cx->pushLocalVal(JS_OBJECT_TO_VAL(math));
    math->setPrototype(cx, getObjectPrototype(cx, scope));
    math->setParentScope(cx, scope);

    math->defineFunctionProperties(cx, className,
				TableSize(functions), functions,
				JS_PA_DontEnum);

    jsVal_t value;

    (void) math->get(cx, "max", math, &value);
    ((JSFunctionObject *) JS_VAL_TO_OBJECT(value))->setLength(2);
    (void) math->get(cx, "min", math, &value);
    ((JSFunctionObject *) JS_VAL_TO_OBJECT(value))->setLength(2);

    const int attr = JS_PA_DontEnum | JS_PA_Permanent | JS_PA_ReadOnly;

    math->defineProperty(cx, "E", JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_E)),
			attr);
    math->defineProperty(cx, "PI", JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_PI)),
		attr);
    math->defineProperty(cx, "LN10",
		JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_LN10)), attr);
    math->defineProperty(cx, "LN2", JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_LN2)),
		attr);
    math->defineProperty(cx, "LOG2E",
		JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_LOG2E)), attr);
    math->defineProperty(cx, "LOG10E",
		JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_LOG10E)), attr);
    math->defineProperty(cx, "SQRT1_2",
		JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_SQRT1_2)), attr);
    math->defineProperty(cx, "SQRT2",
		JS_DOUBLE_TO_VAL(JS_NewGCDouble(cx, M_SQRT2)), attr);

    JSScriptableObject *global = (JSScriptableObject *) scope;

    global->defineProperty(cx, "Math", JS_OBJECT_TO_VAL(math), JS_PA_DontEnum);
    cx->popLocalVals(1);
}
double
JSNativeMath::matan(double d)
{
    return atan(d);
}
double
JSNativeMath::matan2(double y, double x)
{
    return atan2(y, x);
}
double
JSNativeMath::mceil(double d)
{
    return ceil(d);
}
double
JSNativeMath::mcos(double d)
{
    return cos(d);
}
double
JSNativeMath::mfloor(double d)
{
    return floor(d);
}
double
JSNativeMath::mrandom()
{
#ifdef VX
    return ((double) rand() / (double) (RAND_MAX + 1));
#else
    return ((double) rand() / pow(2, 15));
#endif
}
double
JSNativeMath::msin(double d)
{
    return sin(d);
}
double
JSNativeMath::msqrt(double d)
{
    return sqrt(d);
}
double
JSNativeMath::mtan(double d)
{
    return tan(d);
}

double
JSNativeMath::macos(double d)
{
    return acos(d);
}
double
JSNativeMath::masin(double d)
{
    return asin(d);
}
double
JSNativeMath::mabs(double d)
{
    return fabs(d);
}
double
JSNativeMath::mexp(double d)
{
    return exp(d);
}
double
JSNativeMath::mround(double d)
{
#ifdef VX
    if (isNan(d) || isInf(d)) {
#else
    if (!finite(d)) {
#endif
	return d;
    }
    int64_t l = (int64_t) floor(d + 0.5);

    if (l == 0) {
         // We must propagate the sign of d into the result
        if (d < 0.0) {
            return JSScriptRuntime::negativeZero;
	}
        return d == 0.0 ? d : 0.0;
    }
    return (double) l;
}
double
JSNativeMath::mpow(double x, double y)
{
    return pow(x, y);
}
double
JSNativeMath::mlog(double d)
{
    return log(d);
}

jsString_t
JSNativeMath::getClassName(JSContext *cx)
{
    return className;
}

