/****************************************************************************
** $Id: qsoperations.cpp  beta1   edited Dec 10 14:29 $
**
** Copyright (C) 2001-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the Qt Script for Applications framework (QSA).
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding a valid QSA Beta Evaluation Version license may use
** this file in accordance with the QSA Beta Evaluation Version License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about QSA Commercial License Agreements.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
*****************************************************************************/

#include "qsoperations.h"
#include "qsobject.h"
#include "qstypes.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef HAVE_FLOAT_H   /* just for !Windows */
#define HAVE_FLOAT_H 0
#define HAVE_FUNC__FINITE 0
#endif

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>

#ifndef HAVE_FUNC_ISINF
#ifdef HAVE_IEEEFP_H
#include <ieeefp.h>
#endif
#endif /* HAVE_FUNC_ISINF */

#ifdef HAVE_FLOAT_H
#include <float.h>
#endif

using namespace QS;

bool QS::isNaN( double d )
{
#if defined(HAVE_FUNC_ISNAN)
    return isnan( d );
#elif defined(HAVE_FLOAT_H)
    return _isnan( d ) != 0;
#else
    return !( d == d );
#endif
}

bool QS::isInf( double d )
{
#if defined(HAVE_FUNC_ISINF)
    return isinf( d );
#elif defined(HAVE_FUNC_FINITE)
    return finite( d ) == 0 && d == d;
#elif defined(HAVE_FUNC__FINITE)
    return _finite( d ) == 0 && d == d;
#else
    return FALSE;
#endif
}

// ECMA 11.9.3
bool QS::equal( const QSObject& v1, const QSObject& v2 )
{
    Type t1 = v1.type();
    Type t2 = v2.type();

    if ( t1 == t2 ) {
	if ( t1 == UndefinedType || t1 == NullType )
	    return TRUE;
	if ( t1 == NumberType )
	    return v1.toNumber() == v2.toNumber(); /* TODO: NaN, -0 ? */
	if ( t1 == StringType ) {
	    QString s1 = v1.toString();
	    QString s2 = v2.toString();
	    return ( s1 == s2 ) || ( s1.isEmpty() && s2.isEmpty() );
	}
	if ( t1 == BooleanType )
	    return v1.toBoolean() == v2.toBoolean();
	if ( t1 == HostType ) {
	    QSObject h1 = v1.get( "[[==]]" );
	    QSObject h2 = v2.get( "[[==]]" );
	    if ( !h1.isA( UndefinedType ) && !h2.isA( UndefinedType ) )
		return equal( h1, h2 );
	}
	// ###
// 	return ( v1.imp() == v2.imp() );
	return FALSE;
    }

    // different types
    if ( ( t1 == NullType && t2 == UndefinedType ) ||
	 ( t1 == UndefinedType && t2 == NullType ) )
	return TRUE;
    if ( t1 == NumberType && t2 == StringType ) {
	QSNumber n2( v2.toNumber() );
	return equal( v1, n2 );
    }
    if ( ( t1 == StringType && t2 == NumberType ) || t1 == BooleanType ) {
	QSNumber n1( v1.toNumber() );
	return equal( n1, v2 );
    }
    if ( t2 == BooleanType ) {
	QSNumber n2( v2.toNumber() );
	return equal( v1, n2 );
    }
    if ( ( t1 == StringType || t1 == NumberType ) && t2 >= ObjectType ) {
	QSObject p2 = v2.toPrimitive();
	return equal( v1, p2 );
    }
    if ( t1 >= ObjectType && ( t2 == StringType || t2 == NumberType ) ) {
	QSObject p1 = v1.toPrimitive();
	return equal( p1, v2 );
    }

    return FALSE;
}

bool QS::strictEqual( const QSObject &v1, const QSObject &v2 )
{
    Type t1 = v1.type();
    Type t2 = v2.type();

    if ( t1 != t2 )
	return FALSE;
    if ( t1 == UndefinedType || t1 == NullType )
	return TRUE;
    if ( t1 == NumberType ) {
	double n1 = v1.toNumber();
	double n2 = v2.toNumber();
	if ( isNaN( n1 ) || isNaN( n2 ) )
	    return FALSE;
	if ( n1 == n2 )
	    return TRUE;
	/* TODO: +0 and -0 */
	return FALSE;
    } else if ( t1 == StringType ) {
	QString s1 = v1.toString();
	QString s2 = v2.toString();
	return ( s1 == s2 ) || ( s1.isEmpty() && s2.isEmpty() );
    } else if ( t2 == BooleanType ) {
	return v1.toBoolean() == v2.toBoolean();
    }
//     if ( v1.imp() == v2.imp() )
// 	return TRUE;
    /* TODO: joined objects */

    return FALSE;
}

int QS::relation(const QSObject& v1, const QSObject& v2)
{
    QSObject p1 = v1.toPrimitive( NumberType );
    QSObject p2 = v2.toPrimitive( NumberType );

    if ( p1.isA( StringType ) && p2.isA( StringType ) )
	return p1.toString() < p2.toString() ? 1 : 0;

    double n1 = p1.toNumber();
    double n2 = p2.toNumber();
    /* TODO: check for NaN */
    if ( n1 == n2 )
	return 0;
    /* TODO: +0, -0 and Infinity */
    return n1 < n2;
}

double QS::max( double d1, double d2 )
{
    /* TODO: check for NaN */
    return ( d1 > d2 ) ? d1 : d2;
}

double QS::min( double d1, double d2 )
{
    /* TODO: check for NaN */
    return ( d1 < d2 ) ? d1 : d2;
}

// ECMA 11.6
QSObject QS::add( const QSObject &v1, const QSObject &v2, char oper )
{
    QSObject p1 = v1.toPrimitive();
    QSObject p2 = v2.toPrimitive();

    if ( ( p1.isA( StringType ) || p2.isA( StringType ) ) && oper == '+' ) {
	QString s1 = p1.toString();
	QString s2 = p2.toString();

	return QSString( s1 + s2 );
    }

    double n1 = p1.toNumber();
    double n2 = p2.toNumber();

    if ( oper == '+' )
	return QSNumber( n1 + n2 );
    else
	return QSNumber( n1 - n2 );
}

// ECMA 11.5
QSObject QS::mult( const QSObject &v1, const QSObject &v2, char oper )
{
    double n1 = v1.toNumber();
    double n2 = v2.toNumber();

    double result;

    if ( oper == '*' )
	result = n1 * n2;
    else if ( oper == '/' )
	result = n1 / n2;
    else
	result = fmod( n1, n2 );

    return QSNumber( result );
}

QString QSString::from( double d )
{
    // ### NaN, -0, Inf
    if ( isNaN( d ) )
	return "NaN";
    return QString::number( d, 'G', 16 );
}

double QSString::toDouble( const QString &s )
{
    // ### hex, Infinity
    bool ok;
    double d = s.toDouble( &ok );
    if( !ok ) {
	if( s.stripWhiteSpace().isEmpty() ) {
	    return 0;
	}
	return NaN;
    }
    return d;
}

ulong QSString::toULong( const QString &s, bool *ok )
{
    double d = QSString::toDouble( s );
    bool b = TRUE;

    if ( isNaN(d) || d != ulong(d) ) {
	b = FALSE;
	d = 0;
    }

    if ( ok )
	*ok = b;

    return ulong(d);
}
