//
// See the file LICENSE for redistribution information.
//
// Copyright (c) 2002-2003
//	Sleepycat Software.  All rights reserved.
//

static const char revid[] = "$Id: Value.cpp,v 1.60 2003/10/13 16:41:38 merrells Exp $";

#include "dbxml_config.h"
#include "dbxml/XmlPortability.hpp"
#include "Value.hpp"
#include "QueryContext.hpp"
#include "TypeConversions.hpp"
#include "UTF8.hpp"
#include "SyntaxManager.hpp"

#include <float.h>

#ifdef HAVE_IEEEFP_H
#include <ieeefp.h>
#endif

#if defined(DBXML_DOM_XERCES2)
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/util/XMLString.hpp>
#if defined(XERCES_HAS_CPP_NAMESPACE)
  XERCES_CPP_NAMESPACE_USE
#endif
#endif

#include <math.h>

// Some portability tricks from Xalan
static const double s_NaN = sqrt( -2.01);
static const double s_positiveInfinity = HUGE_VAL;
static const double s_negativeInfinity = -s_positiveInfinity;
static const double s_positiveZero = 0.0;
static const double s_negativeZero = -s_positiveZero;

using namespace std;
using namespace DbXml;

// Borrowed from Pathan internals
class v2Utils
{
public:
	static XMLCh *serialiseNode(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode *toWrite);
};

// StringValue

double StringValue::asNumber(const QueryContext *context) const
{
	UNUSED(context);
	if (SyntaxManager::getInstance()->getSyntax(Syntax::NUMBER)->test(s_.c_str(), s_.length())) {
		if (s_.compare("NaN")==0)
			return s_NaN;
		else if (s_.compare("-Infinity")==0)
			return s_negativeInfinity;
		else if (s_.compare("Infinity")==0)
			return s_positiveInfinity;
		else
			return atof(s_.c_str());
	} else {
		return s_NaN;
	}
}
string StringValue::asString(const QueryContext *context) const
{
	UNUSED(context);

	return s_;
}
bool StringValue::asBoolean(const QueryContext *context) const
{
	UNUSED(context);

	// jcm: The xpath spec says that an empty sting is false, and a non-empty
	// string is true. But, the Boolean::string() xpath function returns 'true'
	// for true, and 'false' for false.... this is a bit silly, so we diverge
	// from the spec here.
	//
	return (!s_.empty() && s_.compare("false") != 0);
}
bool StringValue::equals(const Value &v, const QueryContext *context) const
{
	return v.isString(context) && s_.compare(v.asString(context)) == 0;
}

// NumberValue

double NumberValue::asNumber(const QueryContext *context) const
{
	UNUSED(context);

	return n_;
}
string NumberValue::asString(const QueryContext *context) const
{
	UNUSED(context);

	string r;
	if (isNan(n_)) {
		r.assign("NaN");
	} else if (isInfinite(n_)) {
		if (n_ < 0) {
			r.assign("-Infinity");
		} else {
			r.assign("Infinity");
		}
	} else {
		r = DbXml::toString(n_);
	}
	return r;
}
bool NumberValue::asBoolean(const QueryContext *context) const
{
	UNUSED(context);

	return !isNan(n_) && n_ != 0;
}
bool NumberValue::equals(const Value &v, const QueryContext *context) const
{
	return v.isNumber(context) && n_ == v.asNumber(context);
}

bool NumberValue::isNan(double n)
{
	return memcmp(&n, &s_NaN, sizeof(n)) == 0;
}

double NumberValue::round(double n)
{
	if (NumberValue::isNan(n) || ::fabs(n) == 0.0L) {
		// leave n as it is
	} else if (n < 0.0L && n > -0.5L) {
		n = -0.0L;
	} else {
		n = ::floor(n + 0.5L);
	}
	return n;
}

bool NumberValue::isInfinite(double n)
{
	return (memcmp(&n, &s_positiveInfinity, sizeof (n)) == 0) ||
	       (memcmp(&n, &s_negativeInfinity, sizeof (n)) == 0);
}

// BooleanValue

double BooleanValue::asNumber(const QueryContext *context) const
{
	UNUSED(context);

	return b_ ? 1 : 0;
}
string BooleanValue::asString(const QueryContext *context) const
{
	UNUSED(context);

	return b_ ? "true" : "false";
}
bool BooleanValue::asBoolean(const QueryContext *context) const
{
	UNUSED(context);

	return b_;
}
bool BooleanValue::equals(const Value &v, const QueryContext *context) const
{
	return v.isBoolean(context) && b_ == v.asBoolean(context);
}

// NodeValue

NodeValue::NodeValue(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode &n, const XmlDocument *d, QueryContext *context)
	: Value(XmlValue::NODE), 
	n_(n),
	d_(0),
	context_(context)
{
	if(context_) {
		context_->acquire();
	}
	if(d) {
		d_= new XmlDocument(*d);
	}
}

NodeValue::~NodeValue()
{
	if(context_) {
		context_->release();
	}
	delete d_;
}

double NodeValue::asNumber(const QueryContext *context) const
{
	return atof(asString(context).c_str());
}

std::string NodeValue::asString(const QueryContext *context) const
{
	UNUSED(context);
	XMLCh *xmlstr = v2Utils::serialiseNode(&n_);
	std::string r= XMLChToUTF8(xmlstr).str();
	XMLString::release(&xmlstr);
	return r;
}

bool NodeValue::asBoolean(const QueryContext *context) const
{
	UNUSED(context);

	// jcm: The xpath spec says that an empty sting is false, and a non-empty
	// string is true. But, the Boolean::string() xpath function returns 'true'
	// for true, and 'false' for false.... this is a bit silly, so we diverge
	// from the spec here.
	//
	std::string s(asString(context));
	return (!s.empty() && s.compare("false") != 0);
}

XERCES_CPP_NAMESPACE_QUALIFIER DOMNode *NodeValue::asNode(const QueryContext *context) const
{
	UNUSED(context);

	return &n_;
}

XmlDocument NodeValue::asDocument(const QueryContext *context) const
{
	UNUSED(context);

	if(!d_)	{
		throw XmlException(XmlException::INVALID_VALUE, "Can't convert XmlValue Node to Document");
	}

	return *d_;
}

bool NodeValue::equals(const Value &v, const QueryContext *context) const
{
	return v.isNode(context) && n_.isEqualNode(&((NodeValue&)v).n_);
}

// DocumentValue

string DocumentValue::asString(const QueryContext *context) const
{
	UNUSED(context);

	const Dbt *content = d_.getContent();
	string s;
	if (content != 0) {
		s.append((const char *)content->get_data(), content->get_size());
	}
	return s;
}

bool DocumentValue::equals(const Value &v, const QueryContext *context) const
{
	return v.isDocument(context) && false; // JCM - implement???
}

// VariableValue

XmlValue::Type VariableValue::getType(const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value == 0 ? XmlValue::VARIABLE : value->getType(context));
}

/// What syntax type is the Value: String, or Number.
Syntax::Type VariableValue::getSyntaxType(const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value == 0 ? Syntax::NONE : value->getSyntaxType(context));
}

/// Is the value a Number.
bool VariableValue::isNumber(const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value != 0 && value->isNumber(context));
}

/// Is the value a String.
bool VariableValue::isString(const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value != 0 && value->isString(context));
}

/// Is the value a Boolean.
bool VariableValue::isBoolean(const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value != 0 && value->isBoolean(context));
}

/// Is the value a Node
bool VariableValue::isNode(const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value != 0 && value->isNode(context));
}

/// Is the value a Document.
bool VariableValue::isDocument(const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value != 0 && value->isDocument(context));
}

/// Return the value as a Number.
double VariableValue::asNumber(const QueryContext *context) const
{
	Value *value = lookup(context);
	if (value == 0) {
		throw XmlException(XmlException::NO_VARIABLE_BINDING, "The variable '" + name_ + "' is not bound to a value.");
	}
	return value->asNumber(context);
}

/// Return the value as a String.
std::string VariableValue::asString(const QueryContext *context) const
{
	if (context != 0) {
		Value *value = lookup(context);
		if (value == 0) {
			throw XmlException(XmlException::NO_VARIABLE_BINDING, "The variable '" + name_ + "' is not bound to a value.");
		}
		return value->asString(context);
	} else {
		return "$" + name_;
	}
}

/// Return the value as a Boolean.
bool VariableValue::asBoolean(const QueryContext *context) const
{
	Value *value = lookup(context);
	if (value == 0) {
		throw XmlException(XmlException::NO_VARIABLE_BINDING, "The variable '" + name_ + "' is not bound to a value.");
	}
	return value->asBoolean(context);
}

/// Return the value as a Node
DOMNode *VariableValue::asNode(const QueryContext *context) const
{
	Value *value = lookup(context);
	if (value == 0) {
		throw XmlException(XmlException::NO_VARIABLE_BINDING, "The variable '" + name_ + "' is not bound to a value.");
	}
	return value->asNode(context);
}

/// Return the value as a Document.
XmlDocument VariableValue::asDocument(const QueryContext *context) const
{
	Value *value = lookup(context);
	if (value == 0) {
		throw XmlException(XmlException::NO_VARIABLE_BINDING, "The variable '" + name_ + "' is not bound to a value.");
	}
	return value->asDocument(context);
}

bool VariableValue::equals(const Value &v, const QueryContext *context) const
{
	Value *value = lookup(context);
	return (value == 0 ? false : value->equals(v, context));
}

Value *VariableValue::lookup(const QueryContext *context) const
{
	if (context != 0) {
		XmlValue value;
		if (context->getVariableValue(name_, value)) {
			return value;
		}
	}
	return 0;
}
