//
// 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 that 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.
//
// Please note that some files were inspired in part by the PDDL4J library:
// http://www.math-info.univ-paris5.fr/~pellier/software/software.php 
//
// Implementation: Simon Chamberland
// Project Manager: Froduald Kabanza
//

#include "stdafx.h"
#include "PartialWorld.h"
#include "../Exp/Formula/Described/AtomicFormulaApplication.h"

#include <stdexcept>

using namespace planning;

static const size_t TRUE_FACTOR = 100000;
static const size_t FALSE_FACTOR = 100;

PartialWorld::PartialWorld()
  : m_hashCode(0)
{
}

PartialWorld::~PartialWorld()
{
}

PartialWorld::PartialWorld(const PartialWorld& other)
  : m_hashCode(other.m_hashCode), m_facts(other.m_facts)
{
}

bool PartialWorld::isSet(const AtomicFormulaApplication* formula) const
{
  PredicateMap::const_iterator it = m_facts.find(formula);
  if (it == m_facts.end())
  {
    // TODO: A predicate not in the dictionary can take any value, hence it returns false.
    // Note however that this introduces a bug when evaluating tautologies 
    // like (a ^ ~a), which evaluate to false instead of true...
    return false;
  }
  else
  {
    return it->second;
  }
}

void PartialWorld::set(const AtomicFormulaApplication* formula)
{
  std::pair<PredicateMap::iterator, bool> p = m_facts.insert(std::make_pair(formula, true));
  if (p.second)
  {
    // Add true predicate
    size_t h = formula->getHashCode();
    m_hashCode += (h * TRUE_FACTOR + h);
  }
  else
  {
    if (!p.first->second)
    {
      // Change predicate from false to true
      m_hashCode += ((TRUE_FACTOR - FALSE_FACTOR) * formula->getHashCode());
      p.first->second = true;
    }
  }
}

void PartialWorld::unset(const AtomicFormulaApplication* formula)
{
  std::pair<PredicateMap::iterator, bool> p = m_facts.insert(std::make_pair(formula, false));
  if (p.second)
  {
    // Add false predicate
    size_t h = formula->getHashCode();
    m_hashCode += (h * FALSE_FACTOR + h);
  }
  else
  {
    if (p.first->second)
    {
      // Change predicate from true to false
      m_hashCode -= ((TRUE_FACTOR - FALSE_FACTOR) * formula->getHashCode());
      p.first->second = false;
    }
  }
}

double PartialWorld::getNumericFluent(const NumericFluentApplication* fluent) const
{
  throw std::runtime_error("PartialWorld::getNumericFluent() is not supported");
}

const Object* PartialWorld::getObjectFluent(const ObjectFluentApplication* fluent) const
{
  throw std::runtime_error("PartialWorld::getNumericFluent() is not supported");
}

void PartialWorld::setNumericFluent(const NumericFluentApplication* fluent, double value)
{
  throw std::runtime_error("PartialWorld::setNumericFluent() is not supported");
}

void PartialWorld::setNumericFluent(const NumericFluentApplication* fluent, 
                            const ArithmeticFunction& function, 
                            double argument)
{
  throw std::runtime_error("PartialWorld::setNumericFluent() is not supported");
}

void PartialWorld::setObjectFluent(const ObjectFluentApplication* fluent, const Object* value)
{
  throw std::runtime_error("PartialWorld::setNumericFluent() is not supported");
}

void PartialWorld::undefineObjectFluent(const ObjectFluentApplication* fluent)
{
  throw std::runtime_error("PartialWorld::undefineObjectFluent() is not supported");
}


/// <summary>
/// Returns the negation of this world, which is achieved by negating all
/// its facts.
/// </summary>
/// <returns>The negation of this world.</returns>
PartialWorld* PartialWorld::negate() const
{
  PartialWorld* other = new PartialWorld(*this);
  for (PredicateMap::iterator it = other->m_facts.begin();
       it != other->m_facts.end();
       ++it)
  {
    it->second = !it->second;
    const size_t h = it->first->getHashCode();
    if (it->second)
      other->m_hashCode += ((TRUE_FACTOR - FALSE_FACTOR) * h);
    else
      other->m_hashCode -= ((TRUE_FACTOR - FALSE_FACTOR) * h);
  }
  return other;
}

/// <summary>
/// Joins this world with another partial world, resulting in a world where all facts are
/// merged.
/// Note that this function does not modify the current world, and it returns null if some 
/// facts are inconsistent between the two worlds, for example (on a b) and (not (on a b)).
/// </summary>
/// <param name="other">Other world to join this world with.</param>
/// <returns>A new world in which the facts are merged.</returns>
PartialWorld* PartialWorld::join(const PartialWorld& other) const
{
  PartialWorld* newWorld = new PartialWorld(*this);

  for (PredicateMap::const_iterator it = other.m_facts.begin();
       it != other.m_facts.end() && (newWorld != NULL);
       ++it)
  {
    std::pair<PredicateMap::iterator, bool> p = newWorld->m_facts.insert(*it);
    if (!p.second)
    {
      if (it->second != p.first->second)
      {
        delete newWorld;
        newWorld = NULL;
      }
      // else nothing to do
    }
    else
    {
      const size_t h = it->first->getHashCode();
      newWorld->m_hashCode += ((p.first->second ? 100000 : 100) * h + h);
    }
  }
  return newWorld;
}

PartialWorld::predicate_const_iterator PartialWorld::predicatesBegin() const
{
  return predicate_const_iterator(m_facts.begin());
}

PartialWorld::predicate_const_iterator PartialWorld::predicatesEnd() const
{
  return predicate_const_iterator(m_facts.end());
}

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

void PartialWorld::_output(std::ostream& out) const
{
  // TODO
  out << "PartialWorld";
}

int PartialWorld::compareTo(const IClosedWorld& world) const
{
  const PartialWorld& other = static_cast<const PartialWorld&>(world);

  COMPARE(m_hashCode, other.m_hashCode);

  COMPARE(m_facts.size(), other.m_facts.size());

  for (PredicateMap::const_iterator 
        it1 = m_facts.begin(), 
        it2 = other.m_facts.begin();
       it1 != m_facts.end();
       ++it1, ++it2)
  {
    COMPARE(it1->first, it2->first);
    COMPARE(it1->second, it2->second);
  }

  return 0;
}

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