#ifndef ARABICA_XPATHIC_XPATH_OBJECT_H
#define ARABICA_XPATHIC_XPATH_OBJECT_H

#include <string>
#include <vector>
#include <utility>
#include <DOM/Node.hpp>
#include <DOM/Attr.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#ifdef __BORLANDC__
#include <math>
#endif
#include <cmath>
#include <Arabica/StringAdaptor.hpp>
#include <text/normalize_whitespace.hpp>
#include "xpath_axis_enumerator.hpp"

namespace Arabica
{
namespace XPath
{

enum ValueType
{
  ANY ,
  BOOL,
  NUMBER,
  STRING,
  NODE_SET
}; // ValueType

///////////////////////////////////////////////////////
///////////////////////////////////////////////////////
namespace impl
{

template<class string_type, class string_adaptor>
DOM::Node<string_type, string_adaptor> node_parent_or_owner(const DOM::Node<string_type, string_adaptor>& node)
{
  if(node.getNodeType() == DOM::Node_base::ATTRIBUTE_NODE)
    return (static_cast<DOM::Attr<string_type, string_adaptor> >(node)).getOwnerElement();
  return node.getParentNode();
} // node_parent_or_owner

template<class string_type, class string_adaptor>
unsigned int node_attribute_index(const DOM::Attr<string_type, string_adaptor>& attr)
{
  DOM::NamedNodeMap<string_type, string_adaptor> attrs = attr.getOwnerElement().getAttributes();
  unsigned int p = 0;
  for(unsigned int pe = attrs.getLength(); p != pe; ++p)
    if(attrs.item(p) == attr)
      break;
  return p+1;
} // node_attribute_index

template<class string_type, class string_adaptor>
unsigned int node_child_position(const DOM::Node<string_type, string_adaptor>& node)
{
  switch(node.getNodeType())
  {
    case NAMESPACE_NODE_TYPE:
      return 0;
    case DOM::Node_base::ATTRIBUTE_NODE:
      return node_attribute_index(static_cast<DOM::Attr<string_type, string_adaptor> >(node));
    default:
    {
      unsigned int pos = 0;
      DOM::Node<string_type, string_adaptor> n = node;
      do
      {
        n = n.getPreviousSibling();
        pos += 1000;
      } while(n != 0);
      return pos;
    } // default
  } // switch ...
} // node_child_position

template<class string_type, class string_adaptor>
DOM::Node<string_type, string_adaptor> ultimate_parent(const DOM::Node<string_type, string_adaptor>& origin)
{
  DOM::Node<string_type, string_adaptor> n = origin;
  DOM::Node<string_type, string_adaptor> p = node_parent_or_owner(n);
  while(p != 0)
  {
    n = p;
    p = node_parent_or_owner(n);
  } // while ...
  return n;
} // ultimate_parent

template<class string_type, class string_adaptor>
int resolve_different_subtrees(const DOM::Node<string_type, string_adaptor>& lhs, 
                               const DOM::Node<string_type, string_adaptor>& rhs)
{
  // if we have something in the document, and a document fragment,
  // sort the doc ahead of the fragment
  DOM::Node<string_type,string_adaptor> lp = ultimate_parent(lhs);
  if(lp.getNodeType() == DOM::Node_base::DOCUMENT_NODE)
    return -1;
  DOM::Node<string_type, string_adaptor> rp = ultimate_parent(rhs);
  if(rp.getNodeType() == DOM::Node_base::DOCUMENT_NODE)
    return 1;

  // otherwise, sort the frags
  return (lp.underlying_impl() < lp.underlying_impl()) ? -1 : 1;
} // resolve_different_subtrees

template<class string_type, class string_adaptor>
std::vector<unsigned int> node_position(const DOM::Node<string_type, string_adaptor>& node)
{
  std::vector<unsigned int> pos;
  DOM::Node<string_type, string_adaptor> n = node;
  do
  {
    pos.push_back(node_child_position(n));
    n = node_parent_or_owner(n);
  } while(n != 0);

  return pos;
} // node_position

template<class string_type, class string_adaptor>
DOM::Node<string_type, string_adaptor> get_owner_document(const DOM::Node<string_type, string_adaptor>& node)
{
  if(node.getNodeType() == DOM::Node_base::DOCUMENT_NODE)
    return node;
  return node.getOwnerDocument();
} // get_owner_document

template<class string_type, class string_adaptor>
int compareNodes(const DOM::Node<string_type, string_adaptor>& lhs, 
                 const DOM::Node<string_type, string_adaptor>& rhs)
{
  if(lhs == rhs)
    return 0;

  // different documents
  if(get_owner_document(lhs) != get_owner_document(rhs))
    return (get_owner_document(lhs).underlying_impl() < get_owner_document(rhs).underlying_impl()) ? 1 : -1;

  // ok, nodes belong to the same document, but do they belong to the document itself, or a document fragment, 
  // or is it just floating free? if they both belong to a document fragment, is it the same fragment?
  if(ultimate_parent(lhs) != ultimate_parent(rhs))
    return resolve_different_subtrees(lhs, rhs);

  std::vector<unsigned int> pos1 = node_position(lhs);
  std::vector<unsigned int> pos2 = node_position(rhs);

  std::vector<unsigned int>::const_reverse_iterator l = pos1.rbegin(), le = pos1.rend();
  std::vector<unsigned int>::const_reverse_iterator r = pos2.rbegin(), re = pos2.rend();

  while(l != le && r != re)
  {
    if(*l != *r)
      return *l - *r;

    ++l;
    ++r;
  } // while

  if(l != le)
    return 1;
  if(r != re)
    return -1;
  return 0;
} // compareNodes

template<class string_type, class string_adaptor>
bool nodes_less_than(const DOM::Node<string_type, string_adaptor>& n1, 
                     const DOM::Node<string_type, string_adaptor>& n2) 
{ 
  return compareNodes(n1, n2) < 0; 
} // nodes_less_than

} // namespace impl

///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////
template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
class NodeSet  
{
public:
  typedef typename std::vector<DOM::Node<string_type, string_adaptor> >::const_iterator const_iterator;
  typedef typename std::vector<DOM::Node<string_type, string_adaptor> >::iterator iterator;
  typedef typename std::vector<DOM::Node<string_type, string_adaptor> >::value_type value_type;

  NodeSet() : 
    nodes_(), 
    forward_(true), 
    sorted_(false) 
  { 
  } // NodeSet

  NodeSet(bool forward) : 
    nodes_(), 
    forward_(forward),
    sorted_(false)
  { 
  } // NodeSet

  NodeSet(const NodeSet<string_type, string_adaptor>& rhs) : 
    nodes_(rhs.nodes_),
    forward_(rhs.forward_), 
    sorted_(rhs.sorted_)
  { 
  } // NodeSet

  NodeSet& operator=(const NodeSet<string_type, string_adaptor>& rhs) 
  {
    nodes_ = rhs.nodes_;
    forward_ = rhs.forward_;
    sorted_ = rhs.sorted_;
    return *this;
  } // operator=

  void swap(NodeSet& rhs) 
  {
    nodes_.swap(rhs.nodes_);
    std::swap(forward_, rhs.forward_);
    std::swap(sorted_, rhs.sorted_);
  } // swap 

  const_iterator begin() const { return nodes_.begin(); }
  const_iterator end() const { return nodes_.end(); }
  iterator begin() { return nodes_.begin(); }
  iterator end() { return nodes_.end(); }
  const DOM::Node<string_type, string_adaptor>& operator[](size_t i) const { return nodes_[i]; }
  size_t size() const { return nodes_.size(); }
  bool empty() const { return nodes_.empty(); }

  template<typename InputIterator> 
  void insert(iterator position, InputIterator first, InputIterator last)
  {
    sorted_ = false;
    nodes_.insert(position, first, last);
  } // insert
  
  void push_back(const DOM::Node<string_type, string_adaptor>& node) 
  {
    nodes_.push_back(node);
    sorted_ = false;
  } // push_back

  void push_back(const NodeSet<string_type, string_adaptor>& nodeSet)
  {
    insert(end(), nodeSet.begin(), nodeSet.end());
  } // push_back

  bool forward() const { return sorted_ && forward_; }
  bool reverse() const { return sorted_ && !forward_; }
  void forward(bool forward) 
  { 
    if(forward_ == forward)
      return;

    forward_ = forward; 
    sorted_ = false;
  } // forward

  void to_document_order() 
  {
    sort();

    if(!forward_)
    {
      std::reverse(nodes_.begin(), nodes_.end());
      forward_ = true;
    } // if(!forward_)
  } // to_document_order

  void sort()
  {
    if(sorted_)
      return;

    if(forward_)
      std::sort(nodes_.begin(), nodes_.end(), impl::nodes_less_than<string_type, string_adaptor>);
    else
      std::sort(nodes_.rbegin(), nodes_.rend(), impl::nodes_less_than<string_type, string_adaptor>);

    nodes_.erase(std::unique(nodes_.begin(), nodes_.end()), nodes_.end());
    sorted_ = true;
  } // sort

  const DOM::Node<string_type, string_adaptor>& top()  
  {
    sort();
    if(forward_)
      return (*this)[0];
    return (*this)[nodes_.size()-1];
  } // top()

private:
  std::vector<DOM::Node<string_type, string_adaptor> > nodes_;
  bool forward_;
  bool sorted_;
}; // NodeSet

template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
class XPathValue_impl
{
protected:
  XPathValue_impl() { }

public:
  virtual ~XPathValue_impl() { }

  virtual bool asBool() const = 0;
  virtual double asNumber() const = 0;
  virtual string_type asString() const = 0;
  virtual const NodeSet<string_type, string_adaptor>& asNodeSet() const = 0;

  virtual ValueType type() const = 0;

private:
  XPathValue_impl(const XPathValue_impl&);
  bool operator==(const XPathValue_impl&) const;
  XPathValue_impl& operator=(const XPathValue_impl&);
}; // class XPathValue_impl

template<class string_type, class string_adaptor> class XPathValuePtr;

template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
class XPathValue
{ 
public:
  explicit XPathValue() : ptr_() { }
  explicit XPathValue(const XPathValue_impl<string_type, string_adaptor>* v) : ptr_(v) { }
  XPathValue(const XPathValue& rhs) : ptr_(rhs.ptr_) { }
  XPathValue& operator=(const XPathValue& rhs) 
  {
    ptr_ = rhs.ptr_;
    return *this;
  } // operator=

  bool asBool() const { return ptr_->asBool(); }
  double asNumber() const { return ptr_->asNumber(); }
  string_type asString() const { return ptr_->asString(); }
  const NodeSet<string_type, string_adaptor>& asNodeSet() const { return ptr_->asNodeSet(); }

  ValueType type() const { return ptr_->type(); }

  operator bool() const { return ptr_.get(); }
  bool operator==(int dummy) const { return (dummy == 0) && (ptr_.get() == 0); } 
  bool operator!=(int dummy) const { return !(operator==(dummy)); }

private:
  bool operator==(const XPathValue&) const;

  typedef boost::shared_ptr<const XPathValue_impl<string_type, string_adaptor> > ValuePtr;
  ValuePtr ptr_;

  explicit XPathValue(ValuePtr ptr) : ptr_(ptr) { }

  friend class XPathValuePtr<string_type, string_adaptor>;
}; // class XPathValue

template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
class XPathValuePtr
{ 
public:
  explicit XPathValuePtr() : ptr_() { }
  explicit XPathValuePtr(const XPathValue_impl<string_type, string_adaptor>* v) : ptr_(v) { }
  XPathValuePtr(const XPathValue<string_type, string_adaptor>& rhs) : ptr_(rhs.ptr_) { }
  XPathValuePtr(const XPathValuePtr& rhs) : ptr_(rhs.ptr_) { }
  XPathValuePtr& operator=(const XPathValue<string_type, string_adaptor>& rhs) 
  {
    ptr_ = rhs.ptr_;
    return *this;
  } // operator=
  XPathValuePtr& operator=(const XPathValuePtr& rhs) 
  {
    ptr_ = rhs.ptr_;
    return *this;
  } // operator=

  const XPathValue_impl<string_type, string_adaptor>* operator->() const { return ptr_.get(); }

  operator bool() const { return (ptr_.get() != 0); }
  operator XPathValue<string_type, string_adaptor>() const { return XPathValue<string_type, string_adaptor>(ptr_); }

private:
  bool operator==(const XPathValuePtr&) const;

  typedef boost::shared_ptr<const XPathValue_impl<string_type, string_adaptor> > ValuePtr;
  ValuePtr ptr_;
}; // class XPathValuePtr


////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
const double NaN = std::sqrt(-2.0);
const double Zero = 0.0;
const double Negative_Zero = -Zero;
const double Infinity = HUGE_VAL;
const double Negative_Infinity = -Infinity;

inline bool isNaN(double value) { return (value != value); } 
inline bool isInfinity(double value) { return (value == Infinity); }
inline bool isNegativeInfinity(double value) { return (value == Negative_Infinity); }
inline bool isInfinite(double value) { return isInfinity(value) || isNegativeInfinity(value); }

namespace impl
{
inline double roundNumber(double value) 
{ 
  if(!(isNaN(value) || isInfinite(value) || (std::fabs(value) == 0)))
    if((value < 0.0) && (value > -0.5))
      value = -0.0;
    else
      value = std::floor(value + 0.5);
  return value;
} // roundNumber

template<class string_type, class string_adaptor>
double stringAsNumber(const string_type& str)
{
  try {
    static string_type PLUS = string_adaptor::construct_from_utf8("+");

    string_type n_str = Arabica::text::normalize_whitespace<string_type, string_adaptor>(str);
    // '+1.5' is not a number according to XPath spec, counter intuitive as that is
    if(string_adaptor::find(n_str, PLUS) == 0)
      return NaN;
    
    return boost::lexical_cast<double>(n_str);
  } // try
  catch(const boost::bad_lexical_cast&) {
    return NaN;
  } // catch
} // stringAsNumber

template<class string_type, class string_adaptor>
bool nodeIsText(const DOM::Node<string_type, string_adaptor>& node)
{
  return (node.getNodeType() == DOM::Node_base::TEXT_NODE) ||
         (node.getNodeType() == DOM::Node_base::CDATA_SECTION_NODE);
} // nodeIsText

template<class string_type, class string_adaptor>
string_type nodeStringValue(const DOM::Node<string_type, string_adaptor>& node)
{
  switch(node.getNodeType())
  {
  case DOM::Node_base::DOCUMENT_NODE:
  case DOM::Node_base::DOCUMENT_FRAGMENT_NODE:
  case DOM::Node_base::ELEMENT_NODE:
    {
      std::basic_ostringstream<typename string_adaptor::value_type> os;
      AxisEnumerator<string_type, string_adaptor> ae(node, DESCENDANT);
      while(*ae != 0)
      {
        if(nodeIsText<string_type, string_adaptor>(*ae))
          os << nodeStringValue(*ae);
        ++ae;
      } // while
      return string_adaptor::construct(os.str().c_str());
    } // case

  case DOM::Node_base::ATTRIBUTE_NODE:
  case DOM::Node_base::PROCESSING_INSTRUCTION_NODE:
  case DOM::Node_base::COMMENT_NODE:
  case NAMESPACE_NODE_TYPE:
    return node.getNodeValue();

  case DOM::Node_base::TEXT_NODE:
  case DOM::Node_base::CDATA_SECTION_NODE:
    {
      DOM::Node<string_type, string_adaptor> next = node.getNextSibling();
      if((next == 0) ||
	 !nodeIsText<string_type, string_adaptor>(next))
	return node.getNodeValue();

      std::basic_ostringstream<typename string_adaptor::value_type> os;
      os << node.getNodeValue() 
	 << nodeStringValue<string_type, string_adaptor>(next);
      return string_adaptor::construct(os.str().c_str());
    } // case

  default:
    throw std::runtime_error("Don't know how to calculate string-value of " + 
      string_adaptor().asStdString(node.getNodeName()));
  } // switch
} // nodeStringValue

template<class string_type, class string_adaptor>
double nodeNumberValue(const DOM::Node<string_type, string_adaptor>& node)
{
  return stringAsNumber<string_type, string_adaptor>(nodeStringValue<string_type, string_adaptor>(node));
} // nodeNumberValue

} // namespace impl

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

namespace impl {
template<typename RT, typename string_type, typename string_adaptor> struct value_of_node {
  RT operator()(const DOM::Node<string_type, string_adaptor>& node) { return nodeStringValue<string_type, string_adaptor>(node); }
};
template<typename string_type, typename string_adaptor> struct value_of_node<double, string_type, string_adaptor> {
  double operator()(const DOM::Node<string_type, string_adaptor>& node) { return nodeNumberValue<string_type, string_adaptor>(node); }
}; 

template<class Op, class string_type, class string_adaptor>
class compareNodeWith
{
  typedef typename Op::first_argument_type T;


public:
  compareNodeWith(const T& value) : value_(value) { }
  compareNodeWith(const compareNodeWith& rhs) : value_(rhs.value_) { }

  bool operator()(const DOM::Node<string_type, string_adaptor>& node) 
  {
    value_of_node<T, string_type, string_adaptor> nv;
    return Op()(nv(node), value_);
  } // operator()

private:
  T value_;
  bool operator==(const compareNodeWith&);
  compareNodeWith& operator=(const compareNodeWith&);
}; // class compareNodeWith

template<class string_type, class string_adaptor, class predicate1, class predicate2>
bool nodeSetsCompare(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  const NodeSet<string_type, string_adaptor>& lns = lhs.asNodeSet();
  const NodeSet<string_type, string_adaptor>& rns = rhs.asNodeSet();

  if((lns.size() == 0) || (rns.size() == 0))
    return false;

  std::set<string_type> values;
  typename NodeSet<string_type, string_adaptor>::const_iterator l = lns.begin();
  string_type lvalue = nodeStringValue<string_type, string_adaptor>(*l);

  predicate1 p1;
  for(typename NodeSet<string_type, string_adaptor>::const_iterator r = rns.begin(), rend = rns.end(); r != rend; ++r)
  {
    string_type rvalue = nodeStringValue<string_type, string_adaptor>(*r);
    if(p1(lvalue, rvalue))
      return true;
    values.insert(rvalue);
  } // for ...

  ++l;
  predicate2 p2;
  for(typename NodeSet<string_type, string_adaptor>::const_iterator lend = lns.end(); l != lend; ++l)
    if(p2(values.find(nodeStringValue<string_type, string_adaptor>(*l)),  values.end()))
      return true;

  return false;
} // nodeSetsEqual

template<class string_type, class string_adaptor>
bool nodeSetsEqual(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  return nodeSetsCompare<string_type, string_adaptor, 
                         std::equal_to<string_type>,
                         std::not_equal_to<typename std::set<string_type>::const_iterator> >(lhs, rhs);
}

template<class string_type, class string_adaptor>
bool nodeSetsNotEqual(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  return nodeSetsCompare<string_type, string_adaptor, 
                         std::not_equal_to<string_type>,
                         std::equal_to<typename std::set<string_type>::const_iterator> >(lhs, rhs);
}

template<class string_type, class string_adaptor>
bool nodeSetAndValueEqual(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  const NodeSet<string_type, string_adaptor>& lns = lhs.asNodeSet();

  switch(rhs.type())
  {
  case BOOL:
    {
      bool l = !lns.empty();
      bool r = rhs.asBool();

      return l == r;
    } // case BOOL
  case STRING:
    return std::find_if(lns.begin(), 
                        lns.end(), 
                        compareNodeWith<std::equal_to<string_type>, string_type, string_adaptor>(rhs.asString())) != lns.end();

  case NUMBER:
    return std::find_if(lns.begin(), 
                        lns.end(), 
                        compareNodeWith<std::equal_to<double>, string_type, string_adaptor>(rhs.asNumber())) != lns.end();

  default:
    throw std::runtime_error("Node set == not yet implemented for type " + boost::lexical_cast<std::string>(rhs.type()));
  } // switch
} // nodeSetAndValueEqual

template<class string_type, class string_adaptor>
bool nodeSetAndValueNotEqual(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  const NodeSet<string_type, string_adaptor>& lns = lhs.asNodeSet();

  switch(rhs.type())
  {
  case BOOL:
    {
      bool l = !lns.empty();
      bool r = rhs.asBool();

      return l != r;
    } // case BOOL
  case STRING:
    return std::find_if(lns.begin(), 
                        lns.end(), 
                        compareNodeWith<std::not_equal_to<string_type>, string_type, string_adaptor>(rhs.asString())) != lns.end();

  case NUMBER:
    return std::find_if(lns.begin(), 
                        lns.end(), 
                        compareNodeWith<std::not_equal_to<double>, string_type, string_adaptor>(rhs.asNumber())) != lns.end();

  default:
    throw std::runtime_error("Node set == not yet implemented for type " + boost::lexical_cast<std::string>(rhs.type()));
  } // switch
} // nodeSetAndValueNotEqual

template<class string_type, class string_adaptor> 
double minValue(const NodeSet<string_type, string_adaptor>& ns)
{
  double v = nodeNumberValue<string_type, string_adaptor>(ns[0]);
  for(typename NodeSet<string_type, string_adaptor>::const_iterator i = ns.begin(), ie = ns.end(); i != ie; ++i)
  {
    double vt = nodeNumberValue<string_type, string_adaptor>(*i);
    if(isNaN(vt))
      continue;
    if(!(vt > v)) // looks weird, but should account for infinity
      v = vt;
  } // for ...
  return v;
} // minValue

template<class string_type, class string_adaptor> 
double maxValue(const NodeSet<string_type, string_adaptor>& ns)
{
  double v = nodeNumberValue<string_type, string_adaptor>(ns[0]);
  for(typename NodeSet<string_type, string_adaptor>::const_iterator i = ns.begin(), ie = ns.end(); i != ie; ++i)
  {
    double vt = nodeNumberValue<string_type, string_adaptor>(*i);
    if(isNaN(vt))
      continue;
    if(!(vt < v))
      v = vt;
  } // for ...
  return v;
} // maxValue

template<class Op, class string_type, class string_adaptor>
bool compareNodeSets(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  return Op()(minValue<string_type, string_adaptor>(lhs.asNodeSet()), maxValue<string_type, string_adaptor>(rhs.asNodeSet()));
} // compareNodeSets

template<class Op, class string_type, class string_adaptor>
bool compareNodeSetWith(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  const NodeSet<string_type, string_adaptor>& lns = lhs.asNodeSet();
  return std::find_if(lns.begin(), 
                      lns.end(), 
                      compareNodeWith<Op, string_type, string_adaptor>(rhs.asNumber())) != lns.end();
} // compareNodeSetAndValue

template<class string_type, class string_adaptor> 
bool areEqual(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  ValueType lt = lhs.type();
  ValueType rt = rhs.type();

  if((lt == NODE_SET) && (rt == NODE_SET))
    return nodeSetsEqual<string_type, string_adaptor>(lhs, rhs);

  if(lt == NODE_SET)
    return nodeSetAndValueEqual<string_type, string_adaptor>(lhs, rhs);
  if(rt == NODE_SET)
    return nodeSetAndValueEqual<string_type, string_adaptor>(rhs, lhs);

  if((lt == BOOL) || (rt == BOOL))
    return lhs.asBool() == rhs.asBool();

  if((lt == NUMBER) || (rt == NUMBER))
    return lhs.asNumber() == rhs.asNumber();

  if((lt == STRING) || (rt == STRING))
    return lhs.asString() == rhs.asString();

  return false;
} // areEqual

template<class string_type, class string_adaptor> 
bool areNotEqual(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  ValueType lt = lhs.type();
  ValueType rt = rhs.type();

  if((lt == NODE_SET) && (rt == NODE_SET))
    return nodeSetsNotEqual<string_type, string_adaptor>(lhs, rhs);

  if(lt == NODE_SET)
    return nodeSetAndValueNotEqual<string_type, string_adaptor>(lhs, rhs);

  if(rt == NODE_SET)
    return nodeSetAndValueNotEqual<string_type, string_adaptor>(rhs, lhs);

  return !areEqual<string_type, string_adaptor>(lhs, rhs);
} // areNotEqual

template<class string_type, class string_adaptor> 
bool isLessThan(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  ValueType lt = lhs.type();
  ValueType rt = rhs.type();

  if((lt == NODE_SET) && (rt == NODE_SET))
    return compareNodeSets<std::less<double>, string_type, string_adaptor>(lhs, rhs);

  if(lt == NODE_SET)
    return compareNodeSetWith<std::less<double>, string_type, string_adaptor>(lhs, rhs);

  if(rt == NODE_SET)
    return compareNodeSetWith<std::greater<double>, string_type, string_adaptor>(rhs, lhs);

  return lhs.asNumber() < rhs.asNumber();
} // isLessThan

template<class string_type, class string_adaptor> 
bool isLessThanEquals(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  ValueType lt = lhs.type();
  ValueType rt = rhs.type();

  if((lt == NODE_SET) && (rt == NODE_SET))
    return compareNodeSets<std::less_equal<double>, string_type, string_adaptor>(lhs, rhs);

  if(lt == NODE_SET)
    return compareNodeSetWith<std::less_equal<double>, string_type, string_adaptor>(lhs, rhs);

  if(rt == NODE_SET)
    return compareNodeSetWith<std::greater_equal<double>, string_type, string_adaptor>(rhs, lhs);

  return lhs.asNumber() <= rhs.asNumber();
} // isLessThanEquals

template<class string_type, class string_adaptor> 
bool isGreaterThan(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  return isLessThan<string_type, string_adaptor>(rhs, lhs);
} // isGreaterThan

template<class string_type, class string_adaptor> 
bool isGreaterThanEquals(const XPathValue<string_type, string_adaptor>& lhs, const XPathValue<string_type, string_adaptor>& rhs)
{
  return isLessThanEquals<string_type, string_adaptor>(rhs, lhs);
} // isGreaterThanEquals

} // namespace impl
} // namespace XPath
} // namespace Arabica

#endif