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

static const char revid[] = "$Id: XmlValue.cpp,v 1.37 2003/10/31 17:56:27 gmf Exp $";

#include "dbxml_config.h"
#include "dbxml/XmlPortability.hpp"
#include "dbxml/XmlQueryContext.hpp"
#include "dbxml/XmlValue.hpp"
#include "Value.hpp"
#include "QueryContext.hpp"

#include <cmath>

#if defined(XERCES_HAS_CPP_NAMESPACE)
  XERCES_CPP_NAMESPACE_USE
#endif

using namespace DbXml;

static NullValue *none = 0;
// gmf: this call is NOT thread-safe.  Creation races could leak an object or two,
//   but no worse.
static NullValue *getNullValue()
{
	if (none == 0)
		none = new NullValue;
	return none;
}

XmlValue::XmlValue()
	: value_(getNullValue())
{
	value_->acquire();
}

XmlValue::XmlValue(const std::string &v)
	: value_(new StringValue(v))
{
	value_->acquire();
}

XmlValue::XmlValue(const char *v)
	: value_(new StringValue(std::string(v)))
{
	value_->acquire();
}

XmlValue::XmlValue(double v)
	: value_(new NumberValue(v))
{
	value_->acquire();
}

XmlValue::XmlValue(bool v)
	: value_(new BooleanValue(v))
{
	value_->acquire();
}

XmlValue::XmlValue(const XmlDocument &v)
	: value_(new DocumentValue(v))
{
	value_->acquire();
}

XmlValue::XmlValue(XERCES_CPP_NAMESPACE_QUALIFIER DOMNode &n, const XmlDocument *document, QueryContext *qc)
	: value_(new NodeValue(n, document, qc))
{
	value_->acquire();
}

XmlValue::XmlValue(Type type, const std::string &v)
	: value_(getNullValue())
{
	switch (type) {
	case NONE:
		break;
	case STRING:
		value_ = new StringValue(v);
		break;
	case NUMBER:
		value_ = new NumberValue(StringValue(v).asNumber(0));
		break;
	case BOOLEAN:
		value_ = new BooleanValue(StringValue(v).asBoolean(0));
		break;
	case DOCUMENT: {
			XmlDocument d;
			d.setContent(v);
			value_ = new DocumentValue(d);
		}
		break;
	case NODE:
		// JCM - Convert the string into a Node?
		break;
	case VARIABLE:
		value_ = new VariableValue(v);
		break;
	}
	value_->acquire();
}

XmlValue::XmlValue(Type type, const Dbt &dbt)
	: value_(getNullValue())
{
	switch (type) {
	case NONE:
		break;
	case STRING:
		value_ = new StringValue((const char *)dbt.get_data());
		break;
	case NUMBER:
		value_ = new NumberValue(StringValue((const char *)dbt.get_data()).asNumber(0));
		break;
	case BOOLEAN:
		value_ = new BooleanValue(StringValue((const char *)dbt.get_data()).asBoolean(0));
		break;
	case DOCUMENT: {
			XmlDocument d;
			d.setContent((const char *)dbt.get_data());
			value_ = new DocumentValue(d);
		}
		break;
	case NODE:
		// JCM - Convert the string into a Node?
		break;
	case VARIABLE:
		value_ = new VariableValue((const char *)dbt.get_data());
		break;
	}
	value_->acquire();
}

XmlValue::~XmlValue()
{
	if (value_ != 0 && value_ != none)
		value_->release();
}

XmlValue::XmlValue(const XmlValue &o)
	: value_(o.value_)
{
	value_->acquire();
}

XmlValue &XmlValue::operator = (const XmlValue &o)
{
	if (this != &o && value_ != o.value_) {
		if (value_ != 0 && value_ != none)
			value_->release();
		value_ = o.value_;
		if (value_ != 0 && value_ != none)
			value_->acquire();
	}
	return *this;
}

const QueryContext *QC(const XmlQueryContext *context)
{
	return (context == 0 ? 0 : (const QueryContext*)*context);
}

/// What type is the Value: String, Number, Boolean, Node or Document.
XmlValue::Type XmlValue::getType(const XmlQueryContext *context) const
{
	return (value_->getType(QC(context)));
}

/// Is the value_ a Number.
bool XmlValue::isNumber(const XmlQueryContext *context) const
{
	return (value_->isNumber(QC(context)));
}

/// Is the value_ a String.
bool XmlValue::isString(const XmlQueryContext *context) const
{
	return (value_->isString(QC(context)));
}

/// Is the value_ a Boolean.
bool XmlValue::isBoolean(const XmlQueryContext *context) const
{
	return (value_->isBoolean(QC(context)));
}

/// Is the value_ a Node
bool XmlValue::isNode(const XmlQueryContext *context) const
{
	return (value_->isNode(QC(context)));
}

/// Is the value_ a Document.
bool XmlValue::isDocument(const XmlQueryContext *context) const
{
	return (value_->isDocument(QC(context)));
}

/// Is the value_ a Variable
bool XmlValue::isVariable(const XmlQueryContext *context) const
{
	return (value_->isVariable(QC(context)));
}

/// Does the value_ have no value_.
bool XmlValue::isNull() const
{
	return (value_ == getNullValue());
}

/// Return the value_ as a Number.
double XmlValue::asNumber(const XmlQueryContext *context) const
{
	return value_->asNumber(QC(context));
}

/// Return the value_ as a String.
std::string XmlValue::asString(const XmlQueryContext *context) const
{
	return value_->asString(QC(context));
}

/// Return the value_ as a Boolean.
bool XmlValue::asBoolean(const XmlQueryContext *context) const
{
	return value_->asBoolean(QC(context));
}

/// Return the value_ as a Node
DOMNode *XmlValue::asNode(const XmlQueryContext *context) const
{
	return value_->asNode(QC(context));
}

/// Return the value_ as a Document.
XmlDocument XmlValue::asDocument(const XmlQueryContext *context) const
{
	return value_->asDocument(QC(context));
}

/// Compare two value_s for equality.
bool XmlValue::equals(const XmlValue &v, const XmlQueryContext *context) const
{
	if (isNull()) {
		return v.isNull();
	} else {
		if (v.isNull()) {
			return false;
		} else {
			return value_->equals(*v.value_, QC(context));
		}
	}
}

// XPath functions for Strings

XmlValue XmlValue::startsWith(const XmlQueryContext *context, const XmlValue &a, const XmlValue &b)
{
	std::string s_a(a.asString(context));
	std::string s_b(b.asString(context));
	// jcm: gcc2.96 does not have the standard compare implementations
	//cqp_a->setBoolean(s_a.compare(0,s_b.length(),s_b)==0);
	return XmlValue(strncmp(s_a.c_str(), s_b.c_str(), s_b.length()) == 0);
}

XmlValue XmlValue::contains(const XmlQueryContext *context, const XmlValue &a, const XmlValue &b)
{
	std::string s_a(a.asString(context));
	std::string s_b(b.asString(context));
	return XmlValue(s_b.length() == 0 || s_a.find(s_b) != std::string::npos);
}

XmlValue XmlValue::string(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue(a.asString(context));
}

XmlValue XmlValue::substringBefore(const XmlQueryContext *context, const XmlValue &a, const XmlValue &b)
{
	std::string s_a(a.asString(context));
	std::string s_b(b.asString(context));
	std::string r;
	std::string::size_type p = (s_b.length() == 0) ? 0 : s_a.find(s_b);
	if (p != std::string::npos) {
		r = s_a.substr(0, p);
	}
	return XmlValue(r);
}

XmlValue XmlValue::substringAfter(const XmlQueryContext *context, const XmlValue &a, const XmlValue &b)
{
	std::string s_a(a.asString(context));
	std::string s_b(b.asString(context));
	std::string r;
	std::string::size_type p = (s_b.length() == 0) ? 0 : s_a.find(s_b);
	if (p != std::string::npos) {
		r = s_a.substr(p + s_b.length());
	}
	return XmlValue(r);
}

XmlValue XmlValue::substring(const XmlQueryContext *context, const XmlValue &a, const XmlValue &b)
{
	std::string s_a(a.asString(context));
	double n_b(b.asNumber(context));
	std::string r;
	if (!NumberValue::isNan(n_b)) {
		n_b = NumberValue::round(n_b) - 1;
		if (n_b < 0.0L)
			n_b = 0.0L;
		if (n_b < s_a.size()) {
			r = s_a.substr((std::string::size_type)n_b);
		}
	}
	return XmlValue(r);
}

XmlValue XmlValue::substring(const XmlQueryContext *context, const XmlValue &a, const XmlValue &b, const XmlValue &c)
{
	std::string s_a(a.asString(context));
	double n_b(b.asNumber(context));
	double n_c(c.asNumber(context));
	std::string r;
	if (!NumberValue::isInfinite(n_b) && !NumberValue::isNan(n_c)) {
		n_b = NumberValue::round(n_b);
		if (NumberValue::isInfinite(n_c)) {
			if (n_b < 1.0)
				n_b = 1.0;
			r = s_a.substr((size_t)n_b - 1);
		} else {
			n_c = NumberValue::round(n_c);
			double b(n_b);
			double e(n_b + n_c);
			if (b < 1.0)
				b = 1.0;
			if (b > s_a.size() + 1)
				b = s_a.size() + 1;
			if (e < 1.0)
				e = 1.0;
			if (e > s_a.size() + 1)
				e = s_a.size() + 1;
			r = s_a.substr((size_t)b - 1, (size_t)(e - b));
		}
	}
	return XmlValue(r);
}

XmlValue XmlValue::stringLength(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue((double)a.asString(context).length());
}

XmlValue XmlValue::translate(const XmlQueryContext *context, const XmlValue &a, const XmlValue &b, const XmlValue &c)
{
	std::string s_a(a.asString(context));
	std::string s_b(b.asString(context));
	std::string s_c(c.asString(context));
	std::string r;
	std::string::const_iterator i_a;
	for (i_a = s_a.begin();i_a != s_a.end();++i_a) {
		std::string::size_type n_b = s_b.find(*i_a);
		if (n_b != std::string::npos) {
			if (n_b < s_c.length()) {
				r += s_c[n_b];
			}
		}
	}
	return XmlValue(r);
}

XmlValue XmlValue::normalizeSpace(const XmlQueryContext *context, const XmlValue &a)
{
	std::string s_a(a.asString(context));
	std::string r;
	std::string::const_iterator i_a;
	int state = 0;
	for (i_a = s_a.begin();i_a != s_a.end();++i_a) {
		bool ws = (*i_a == 0x9) || (*i_a == 0xA) || (*i_a == 0xD) || (*i_a == 0x20);
		switch (state) {
		case 0:
			if (!ws) {
				state = 1;
				r += *i_a;
			}
			break;
		case 1:
			if (ws) {
				state = 2;
				r += ' ';
			} else {
				r += *i_a;
			}
			break;
		case 2:
			if (!ws) {
				state = 1;
				r += *i_a;
			}
			break;
		}
	}
	std::string::reverse_iterator i_r = r.rbegin();
	if (i_r != r.rend() && *i_r == ' ') {
		r.resize(r.size() - 1);
	}
	return XmlValue(r);
}

// XPath functions for Booleans

XmlValue XmlValue::boolean(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue(a.asBoolean(context));
}

XmlValue XmlValue::booleanNot(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue(!a.asBoolean(context));
}

XmlValue XmlValue::booleanTrue(const XmlQueryContext *context)
{
	UNUSED(context);

	return XmlValue(true);
}

XmlValue XmlValue::booleanFalse(const XmlQueryContext *context)
{
	UNUSED(context);

	return XmlValue(false);
}

// XPath functions for Numbers

XmlValue XmlValue::number(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue(a.asNumber(context));
}

XmlValue XmlValue::floor(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue(::floor(a.asNumber(context)));
}

XmlValue XmlValue::ceiling(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue(::ceil(a.asNumber(context)));
}

XmlValue XmlValue::round(const XmlQueryContext *context, const XmlValue &a)
{
	return XmlValue(NumberValue::round(a.asNumber(context)));
}
