#include "equals.h"
#include "membertype.h"
#include "member_cache.h"

#include <memory>

using namespace aka2;


bool aka2::element_equals(const void *lhs, const void *rhs, const element_op& op) {
  if (!attributes_equals(lhs, rhs, op))
    return false;
  return op.equals(lhs, rhs);
}

bool aka2::attributes_equals(const void *lhs, const void *rhs, const element_op &op) {
  if (op.get_attribute_types() != 0) {
    const attribute_types &attrtypes = *op.get_attribute_types();
    for (attribute_types::const_iterator it = attrtypes.begin();
	 it != attrtypes.end(); ++it) {
      if (!it->equals(lhs, rhs))
	return false;
    }
  }
  if (op.get_anyattr_type() != 0) {
    const any_member &mtype = *op.get_anyattr_type();
    const_member_cache lhscache(lhs, mtype.op(), mtype.getter());
    const_member_cache rhscache(rhs, mtype.op(), mtype.getter());
    lhscache.prepare();
    rhscache.prepare();
    return mtype.op().equals(lhscache.value(), rhscache.value());
  }
  return true;
}

bool aka2::sequence_equals(const void *lhs, const void *rhs, const sequence_op &sop) {
  if (!attributes_equals(lhs, rhs, sop))
    return false;

  const member_types &mtypes = sop.get_member_types();
  for (member_types::const_iterator it = mtypes.begin();
       it != mtypes.end(); ++it) {
    if (!it->equals(lhs, rhs))
      return false;
  }
  return true;
}


bool aka2::choice_equals(const void *lhs, const void *rhs, const choice_op &cop) {
  assert(cop.get_attribute_types() == 0);

  std::auto_ptr<item_iterator> lhsit(cop.get_iterator(lhs));
  std::auto_ptr<item_iterator> rhsit(cop.get_iterator(rhs));

  typedef std::vector<const_node> nodes;

  nodes lhsnodes, rhsnodes;

  while (lhsit->has_next()) {
    const item *lhsitem = lhsit->next();
    const_node lhsnd = lhsitem->get_node();
    if (lhsnd.op().get_schematype() != array_id) {
      lhsnodes.push_back(lhsnd);
    }
    else {
      const array_op& aop = static_cast<const array_op&>(lhsnd.op());
      if (aop.size(lhsnd.ptr()) != 0)
	lhsnodes.push_back(lhsnd);
    }
  }

  while (rhsit->has_next()) {
    const item *rhsitem = rhsit->next();
    const_node rhsnd = rhsitem->get_node();
    if (rhsnd.op().get_schematype() != array_id) {
      rhsnodes.push_back(rhsnd);
    }
    else {
      const array_op& aop = static_cast<const array_op&>(rhsnd.op());
      if (aop.size(rhsnd.ptr()) != 0)
	rhsnodes.push_back(rhsnd);
    }
  }

  if (lhsnodes.size() != rhsnodes.size())
    return false;

  nodes::iterator lhsnodeit = lhsnodes.begin();
  nodes::iterator rhsnodeit = rhsnodes.begin();

  while (lhsnodeit != lhsnodes.end()) {

    if (&lhsnodeit->op() != &rhsnodeit->op())
      return false;
    if (!element_equals(lhsnodeit->ptr(), rhsnodeit->ptr(), lhsnodeit->op()))
      return false;
    ++lhsnodeit; ++rhsnodeit;
  }
  assert(rhsnodeit == rhsnodes.end());

  return true;

}


bool aka2::all_equals(const void *lhs, const void *rhs, const all_op &aop) {

  if (!attributes_equals(lhs, rhs, aop))
    return false;

  const member_map &mmap = aop.get_member_map();
  for (member_map::const_iterator it = mmap.begin(); it != mmap.end(); ++it) {
    if (!it->second.equals(lhs, rhs))
      return false;
  }
  return true;
}

bool aka2::simpletype_equals(const void *lhs, const void *rhs, const simpletype_op& op) {
  return op.equals(lhs, rhs);
}

bool aka2::array_equals(const void *lhs, const void *rhs, 
			const array_op &aop, const element_op &vop) {

  if (aop.empty(lhs) && aop.empty(rhs))
    return true;

  if (aop.size(lhs) != aop.size(rhs))
    return false;

  std::auto_ptr<array_iterator> lhsit(aop.get_iterator(lhs));
  std::auto_ptr<array_iterator> rhsit(aop.get_iterator(rhs));

  while (lhsit->has_next() && rhsit->has_next()) {
    const void *lhsitem = lhsit->next();
    const void *rhsitem = rhsit->next();
    if (!vop.equals(lhsitem, rhsitem))
      return false;
  }
  return true;
}

bool aka2::simplecontent_equals(const void *lhs, const void *rhs, const simplecontent_op &sop) {
  if (!attributes_equals(lhs, rhs, sop))
    return false;
  const member_type &mtype = sop.get_valuetype();
  return mtype.equals(lhs, rhs);
}

bool aka2::ptrmember_equals(const void *lhs, const void *rhs, const ptrmember_op &pop) {
  if (pop.is_null(lhs) && pop.is_null(rhs))
    return true;
  if (pop.is_null(lhs) || pop.is_null(rhs))
    return false;

  // Both must be non-null value.
  const void *lhsval = pop.dereference(lhs);
  const void *rhsval = pop.dereference(rhs);
  return pop.get_value_op().equals(lhsval, rhsval);
}
