/*
 * predicate.cc - Copyright 2005 Frerich Raabe   <raabe@kde.org>
 *                Copyright 2010 Maksim Orlovich <maksim@kde.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "predicate.h"
#include "functions.h"

#include <QString>

#include "xml/dom_nodeimpl.h"
#include "xml/dom_nodelistimpl.h"
#include "kjs/operations.h"
#include "kjs/value.h"

#include <math.h>

using namespace DOM;
using namespace khtml;
using namespace khtml::XPath;

Number::Number( double value )
	: m_value( value )
{
}

bool Number::isConstant() const
{
	return true;
}

QString Number::dump() const
{
	return "<number>" + QString::number( m_value ) + "</number>";
}

Value Number::doEvaluate() const
{
	return Value( m_value );
}

String::String( const DOMString &value )
	: m_value( value )
{
}

bool String::isConstant() const
{
	return true;
}

QString String::dump() const
{
	return "<string>" + m_value.string() + "</string>";
}

Value String::doEvaluate() const
{
	return Value( m_value );
}

Value Negative::doEvaluate() const
{
	Value p( subExpr( 0 )->evaluate() );
	return Value( -p.toNumber() );
}

QString Negative::dump() const
{
	return "<negative>" + subExpr( 0 )->dump() + "</number>";
}

QString BinaryExprBase::dump() const
{
	QString s = "<" + opName() + ">";
	s += "<operand>" + subExpr( 0 )->dump() + "</operand>";
	s += "<operand>" + subExpr( 1 )->dump() + "</operand>";
	s += "</" + opName() + ">";
	return s;
}

NumericOp::NumericOp( int _opCode, Expression* lhs, Expression* rhs ) :
	opCode( _opCode )
{
	addSubExpression( lhs );
	addSubExpression( rhs );
}

Value NumericOp::doEvaluate() const
{
	Value lhs( subExpr( 0 )->evaluate() );
	Value rhs( subExpr( 1 )->evaluate() );
	double leftVal = lhs.toNumber(), rightVal = rhs.toNumber();

	switch (opCode) {
		case OP_Add:
			return Value( leftVal + rightVal );
		case OP_Sub:
			return Value( leftVal - rightVal );
		case OP_Mul:
			return Value( leftVal * rightVal );
		case OP_Div:
			if ( rightVal == 0.0 || rightVal == -0.0 ) {
				if ( leftVal == 0.0 || leftVal == -0.0) {
					return Value(); // 0/0 = NaN
				} else {
					// +/- Infinity.
					if (signbit(leftVal) == signbit(rightVal))
						return Value( KJS::Inf );
					else
						return Value( -KJS::Inf );
				}
			} else {
				return Value( leftVal / rightVal );
            }
		case OP_Mod:
			if ( rightVal == 0.0 || rightVal == -0.0 )
				return Value(); //Divide by 0;
			else
				return Value( remainder( leftVal, rightVal ) );
		default:
			assert(0);
		return Value();
	}
}

QString NumericOp::opName() const
{
	switch (opCode) {
		case OP_Add:
			return QLatin1String( "addition" );
		case OP_Sub:
			return QLatin1String( "subtraction" );
		case OP_Mul:
			return QLatin1String( "multiplication" );
		case OP_Div:
			return QLatin1String( "division" );
		case OP_Mod:
			return QLatin1String( "modulo" );
		default:
			assert(0);
			return QString();
	}
}

RelationOp::RelationOp( int _opCode, Expression* lhs, Expression* rhs ) :
	opCode( _opCode )
{
	addSubExpression( lhs );
	addSubExpression( rhs );
}

static void stringify(const Value& val, WTF::Vector<DOMString>* out)
{
	if (val.isString()) {
		out->append(val.toString());
	} else {
		assert(val.isNodeset());

		const DomNodeList& set = val.toNodeset();
		for (unsigned long i = 0; i < set->length(); ++i) {
			DOM::DOMString stringVal = stringValue(set->item(i));
			out->append(stringVal);
		}
	}
}

static void numify(const Value& val, WTF::Vector<double>* out)
{
	if (val.isNumber()) {
		out->append(val.toNumber());
	} else {
		assert(val.isNodeset());

		const DomNodeList& set = val.toNodeset();
		for (unsigned long i = 0; i < set->length(); ++i) {
			DOM::DOMString stringVal = stringValue(set->item(i));
			out->append(Value(stringVal).toNumber());
		}
	}
}

Value RelationOp::doEvaluate() const
{
	Value lhs( subExpr( 0 )->evaluate() );
	Value rhs( subExpr( 1 )->evaluate() );

	if (lhs.isNodeset() || rhs.isNodeset())
	{
		// If both are nodesets, or one is a string our
		// comparisons are based on strings.
		if ((lhs.isNodeset() && rhs.isNodeset()) ||
		    (lhs.isString()  || rhs.isString())) {

			WTF::Vector<DOM::DOMString> leftStrings;
			WTF::Vector<DOM::DOMString> rightStrings;

			stringify(lhs, &leftStrings);
			stringify(rhs, &rightStrings);

			for (unsigned pl = 0; pl < leftStrings.size(); ++pl) {
				for (unsigned pr = 0; pr < rightStrings.size(); ++pr) {
					if (compareStrings(leftStrings[pl], rightStrings[pr]))
						return Value(true);
				} // pr
			} // pl
			return Value(false);
		}

		// If one is a number, we do a number-based comparison
		if (lhs.isNumber() || rhs.isNumber()) {
			WTF::Vector<double> leftNums;
			WTF::Vector<double> rightNums;

			numify(lhs, &leftNums);
			numify(rhs, &rightNums);

			for (unsigned pl = 0; pl < leftNums.size(); ++pl) {
				for (unsigned pr = 0; pr < rightNums.size(); ++pr) {
					if (compareNumbers(leftNums[pl], rightNums[pr]))
						return Value(true);
				} // pr
			} // pl
			return Value(false);
		}

		// Has to be a boolean-based comparison.
		// These ones are simpler, since we just convert the nodeset to a bool		
		assert(lhs.isBoolean() || rhs.isBoolean());

		if (lhs.isNodeset())
			lhs = Value(lhs.toBoolean());
		else
			rhs = Value(rhs.toBoolean());
	} // nodeset comparisons
	

	if (opCode == OP_EQ || opCode == OP_NE) {
		bool equal;
		if ( lhs.isBoolean() || rhs.isBoolean() ) {
			equal = ( lhs.toBoolean() == rhs.toBoolean() );
		} else if ( lhs.isNumber() || rhs.isNumber() ) {
			equal = ( lhs.toNumber() == rhs.toNumber() );
		} else {
			equal = ( lhs.toString() == rhs.toString() );
		}

		if ( opCode == OP_EQ )
			return Value( equal );
		else
			return Value( !equal );

	}

	// For other ops, we always convert to numbers
	double leftVal = lhs.toNumber(), rightVal = rhs.toNumber();
	return Value(compareNumbers(leftVal, rightVal));
}


bool RelationOp::compareNumbers(double leftVal, double rightVal) const
{
	switch (opCode) {
		case OP_GT:
			return leftVal > rightVal;
		case OP_GE:
			return leftVal >= rightVal;
		case OP_LT:
			return leftVal < rightVal;
		case OP_LE:
			return leftVal <= rightVal;
		case OP_EQ:
			return leftVal == rightVal;
		case OP_NE:
			return leftVal != rightVal;
		default:
			assert(0);
			return false;
	}
}

bool RelationOp::compareStrings(const DOM::DOMString& l, const DOM::DOMString& r) const
{
	// String comparisons, as invoked within the nodeset case, are string-based
	// only for == and !=. Everything else still goes to numbers.
	if (opCode == OP_EQ)
		return (l == r);
	if (opCode == OP_NE)
		return (l != r);

	return compareNumbers(Value(l).toNumber(), Value(r).toNumber());
}

QString RelationOp::opName() const
{
	switch (opCode) {
		case OP_GT:
			return QLatin1String( "relationGT" );
		case OP_GE:
			return QLatin1String( "relationGE" );
		case OP_LT:
			return QLatin1String( "relationLT" );
		case OP_LE:
			return QLatin1String( "relationLE" );
		case OP_EQ:
			return QLatin1String( "relationEQ" );
		case OP_NE:
			return QLatin1String( "relationNE" );
		default:
			assert(0);
			return QString();
	}
}

LogicalOp::LogicalOp( int _opCode, Expression* lhs, Expression* rhs ) :
	opCode( _opCode )
{
	addSubExpression( lhs );
	addSubExpression( rhs );
}

bool LogicalOp::shortCircuitOn() const
{
	if (opCode == OP_And)
		return false; //false and foo
	else
		return true;  //true or bar
}

bool LogicalOp::isConstant() const
{
	return subExpr( 0 )->isConstant() &&
	       subExpr( 0 )->evaluate().toBoolean() == shortCircuitOn();
}

QString LogicalOp::opName() const
{
	if ( opCode == OP_And )
		return QLatin1String( "conjunction" );
	else
		return QLatin1String( "disjunction" );
}

Value LogicalOp::doEvaluate() const
{
	Value lhs( subExpr( 0 )->evaluate() );

	// This is not only an optimization, http://www.w3.org/TR/xpath
	// dictates that we must do short-circuit evaluation
	bool lhsBool = lhs.toBoolean();
	if ( lhsBool == shortCircuitOn() ) {
		return Value( lhsBool );
	}

	return Value( subExpr( 1 )->evaluate().toBoolean() );
}

QString Union::opName() const
{
	return QLatin1String("union");
}

Value Union::doEvaluate() const
{
	Value lhs = subExpr( 0 )->evaluate();
	Value rhs = subExpr( 1 )->evaluate();
	if ( !lhs.isNodeset() || !rhs.isNodeset() ) {
		kWarning(6011) << "Union operator '|' works only with nodesets.";
		Expression::reportInvalidExpressionErr();
		return Value( new StaticNodeListImpl );
	}

	DomNodeList lhsNodes = lhs.toNodeset();
	DomNodeList rhsNodes = rhs.toNodeset();
	DomNodeList result = new StaticNodeListImpl;

	for ( unsigned long n = 0; n < lhsNodes->length(); ++n )
		result->append( lhsNodes->item( n ) );

	for ( unsigned long n = 0; n < rhsNodes->length(); ++n )
		result->append( rhsNodes->item( n ) );
	
	return Value( result );
}

Predicate::Predicate( Expression *expr )
	: m_expr( expr )
{
}

Predicate::~Predicate()
{
	delete m_expr;
}

bool Predicate::evaluate() const
{
	Q_ASSERT( m_expr != 0 );

	Value result( m_expr->evaluate() );

	// foo[3] really means foo[position()=3]
	if ( result.isNumber() ) {
		return double( Expression::evaluationContext().position ) == result.toNumber();
	}

	return result.toBoolean();
}

void Predicate::optimize()
{
	m_expr->optimize();
}

QString Predicate::dump() const
{
	return QString() + "<predicate>" + m_expr->dump() + "</predicate>";
}

// kate: indent-width 4; replace-tabs off; tab-width 4; space-indent off;
