//
// Copyright (c) 2009 Froduald Kabanza and the Université de Sherbrooke.
// Use of this software is permitted for non-commercial research purposes, and
// it may be copied or applied only for that use. All copies must include this
// copyright message.
// 
// This is a research prototype and it has not gone through intensive tests and
// is delivered as is. It may still contain bugs. Froduald Kabanza and the
// Université de Sherbrooke disclaim any responsibility for damage that may be
// caused by using it.
//
// Implementation: Simon Chamberland
// Project Manager: Froduald Kabanza
//

#include "stdafx.h"
#include "ArrayFluentsContainer.h"

#include <string.h>

using namespace planning;

const Object ArrayFluentsContainer::s_undefinedObject("Undefined-object", PrimitiveType::getObjectType());

ArrayFluentsContainer::ArrayFluentsContainer(IntegerInterval numericInterval,
                                             IntegerInterval objectInterval)
  : m_numericFluents(new double[numericInterval.getLength()]), m_numericInterval(numericInterval),
    m_objectFluents(new const Object*[objectInterval.getLength()]), m_objectInterval(objectInterval),
    m_hashCode(0)
{
  const double nan = std::numeric_limits<double>::quiet_NaN();
  assert((boost::math::isnan)(nan));

  for (double* it = m_numericFluents; it != m_numericFluents + m_numericInterval.getLength(); ++it)
    *it = nan;

  for (const Object** it = m_objectFluents; it != m_objectFluents + m_objectInterval.getLength(); ++it)
    *it = &s_undefinedObject;
}

ArrayFluentsContainer::ArrayFluentsContainer(const ArrayFluentsContainer &other)
  : m_numericFluents(new double[other.m_numericInterval.getLength()]), m_numericInterval(other.m_numericInterval),
    m_objectFluents(new const Object*[other.m_objectInterval.getLength()]), m_objectInterval(other.m_objectInterval),
    m_hashCode(other.m_hashCode)
{
  memcpy(m_numericFluents, other.m_numericFluents, sizeof(double) * m_numericInterval.getLength());
  memcpy(m_objectFluents, other.m_objectFluents, sizeof(const Object*) * m_objectInterval.getLength());
}

ArrayFluentsContainer::~ArrayFluentsContainer()
{
  delete[] m_numericFluents;
  delete[] m_objectFluents;
}

ArrayFluentsContainer* ArrayFluentsContainer::copy() const
{
  return new ArrayFluentsContainer(*this);
}

double ArrayFluentsContainer::getNumericFluent(FormulaId id) const
{
  const double& value = m_numericFluents[id - m_numericInterval.getStart()];
  if ( (boost::math::isnan)(value) )
    throw UndefinedFluentException();

  return value;
}

const Object* ArrayFluentsContainer::getObjectFluent(FormulaId id) const
{
  const Object* object = m_objectFluents[id - m_objectInterval.getStart()];
  if (object == &s_undefinedObject)
    throw UndefinedFluentException();

  return object;
}

void ArrayFluentsContainer::setNumericFluent(FormulaId id, double value)
{
  if ( !(boost::math::isfinite)(value) )
    throw NumericException();

  double& oldValue = m_numericFluents[id - m_numericInterval.getStart()];
  const size_t hashId = m_uintHasher(id);

  if ( !(boost::math::isnan)(oldValue) )
  {
    m_hashCode -= (hashId * m_doubleHasher(oldValue));
    //m_hashCode -= (hashId + m_doubleHasher(oldValue));
  }
  m_hashCode += (hashId * m_doubleHasher(value));
  //m_hashCode += (hashId + m_doubleHasher(value));
  oldValue = value;
}

void ArrayFluentsContainer::setNumericFluent(FormulaId id, 
                                     const ArithmeticFunction& function, 
                                     double argument)
{
  double& value = m_numericFluents[id - m_numericInterval.getStart()];
  if ( (boost::math::isnan)(value) )
  {
    // Cannot use function with an undefined fluent
    throw UndefinedFluentException();
  }
  const size_t hashId = m_uintHasher(id);
  m_hashCode -= (hashId * m_doubleHasher(value));
  //m_hashCode -= (hashId + m_doubleHasher(value));
  value = function(value, argument);
  if ( !(boost::math::isfinite)(value) )
    throw NumericException();

  m_hashCode += (hashId * m_doubleHasher(value));
  //m_hashCode += (hashId + m_doubleHasher(value));
}

void ArrayFluentsContainer::setObjectFluent(FormulaId id, const Object* value)
{
  const Object*& oldValue = m_objectFluents[id - m_objectInterval.getStart()];
  const size_t hashId = m_uintHasher(id);

  if (oldValue != &s_undefinedObject)
  {
    m_hashCode -= (hashId * oldValue->getHashCode());
    //m_hashCode -= (hashId + oldValue->getHashCode());
  }
  m_hashCode += (hashId * value->getHashCode());
  //m_hashCode += (hashId + value->getHashCode());
  oldValue = value;
}

void ArrayFluentsContainer::undefineObjectFluent(FormulaId id)
{
  const Object*& oldValue = m_objectFluents[id - m_objectInterval.getStart()];
  if (oldValue != &s_undefinedObject)
  {
    const size_t hashId = m_uintHasher(id);
    m_hashCode -= (hashId * oldValue->getHashCode());
    //m_hashCode -= (hashId + oldValue->getHashCode());
    oldValue = &s_undefinedObject;
  }
}

size_t ArrayFluentsContainer::getHashCode() const
{
  return m_hashCode;
}

int ArrayFluentsContainer::compareTo(const FluentsContainer& other) const
{
  ArrayFluentsContainer& container = (ArrayFluentsContainer&) other;
  assert(m_numericInterval.getLength() == container.m_numericInterval.getLength());
  assert(m_objectInterval.getLength() == container.m_objectInterval.getLength());

  COMPARE(m_hashCode, container.m_hashCode);

  {
    const double *ptr1_end = m_numericFluents + m_numericInterval.getLength();
    for (double *ptr1 = m_numericFluents, *ptr2 = container.m_numericFluents; 
         ptr1 != ptr1_end;
         ++ptr1, ++ptr2)
    {
      COMPARE(*ptr1, *ptr2);
    }
  }

  {
    const Object **ptr1_end = m_objectFluents + m_objectInterval.getLength();
    for (const Object **ptr1 = m_objectFluents, **ptr2 = container.m_objectFluents; 
         ptr1 != ptr1_end;
         ++ptr1, ++ptr2)
    {
      COMPARE(*ptr1, *ptr2);
    }
  }

  return 0;
}
