//
// 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
//

#ifndef _CUSTOM_WORLD_H
#define _CUSTOM_WORLD_H

#include "../../IClosedWorld.h"
#include "../FactsContainer/FactsContainer.h"
#include "../FluentsContainer/FluentsContainer.h"

namespace planning
{
  /// <summary>
  /// A custom world is an updatable world with a custom facts container and a custom
  /// fluents container.
  /// As is, a custom world cannot evaluate formulas on its own: indeed, some facts container
  /// implementation like hashset return unknown when a certain predicate ID is not contained
  /// in their internal set of IDs, causing both actual unknown predicates and false predicates
  /// to be evaluated to unknown.
  /// </summary>
  template <class FactsContainerType, class FluentsContainerType>
  class CustomWorld : public IClosedWorld
  {
  public:
    CustomWorld(IntegerInterval factsInterval, 
                IntegerInterval numericInterval,
                IntegerInterval objectInterval);

    CustomWorld(const CustomWorld &other);

    /// <summary>
    /// Copies this custom world.
    /// </summary>
    /// <returns>A copy of this custom world.</returns>
    virtual CustomWorld* copy() const;

    virtual bool isSet(const planning::AtomicFormulaApplication* formula) const;

    virtual void set(const planning::AtomicFormulaApplication* formula);

    virtual void unset(const planning::AtomicFormulaApplication* formula);

    virtual double getNumericFluent(const planning::NumericFluentApplication* fluent) const;

    virtual const planning::Object* getObjectFluent(const planning::ObjectFluentApplication* fluent) const;

    virtual void setNumericFluent(const planning::NumericFluentApplication* fluent, double value);

    virtual void setNumericFluent(const planning::NumericFluentApplication* fluent, 
                                  const planning::ArithmeticFunction& function, 
                                  double argument);

    virtual void setObjectFluent(const planning::ObjectFluentApplication* fluent, 
                                 const planning::Object* value);

    virtual void undefineObjectFluent(const planning::ObjectFluentApplication* fluent);

    virtual size_t getHashCode() const;

    virtual int compareTo(const IClosedWorld& other) const;

    const FactsContainerType& getFactsContainer() const {
      return m_factsContainer;
    }

    const FluentsContainerType& getFluentsContainer() const {
      return m_fluentsContainer;
    }

  protected:
    virtual void _output(std::ostream& out) const;

    FactsContainerType m_factsContainer;
    FluentsContainerType m_fluentsContainer;

    //#region IConstantWorld Interface

    ///// <summary>
    ///// Checks whether the specified described atomic formula holds in this world.
    ///// </summary>
    ///// <param name="formula">A described (and ground) atomic formula.</param>
    ///// <returns>True, false, or unknown.</returns>
    //public override FuzzyBool IsSet(AtomicFormulaApplication formula)
    //{
    //  System.Diagnostics.Debug.Assert(formula.AllConstantArguments);

    //  return this.m_factsContainer.IsSet(formula.FormulaID);
    //}

    ///// <summary>
    ///// Returns the value of the specified numeric fluent in this world.
    ///// </summary>
    ///// <param name="fluent">A described (and ground) numeric fluent.</param>
    ///// <returns>Unknown, undefined, or the value of the numeric fluent.</returns>
    //public override FuzzyDouble InternalGetNumericFluent(NumericFluentApplication fluent)
    //{
    //  System.Diagnostics.Debug.Assert(fluent.AllConstantArguments);

    //  return this.m_fluentsContainer.GetNumericFluent(fluent.FormulaID);
    //}

    ///// <summary>
    ///// Returns the value of the specified object fluent in this world.
    ///// </summary>
    ///// <param name="fluent">A described (and ground) object fluent.</param>
    ///// <returns>Unknown, undefined, or a constant representing the value of the 
    ///// object fluent.</returns>
    //public override FuzzyConstantExp InternalGetObjectFluent(ObjectFluentApplication fluent)
    //{
    //  System.Diagnostics.Debug.Assert(fluent.AllConstantArguments);

    //  return this.m_fluentsContainer.GetObjectFluent(fluent.FormulaID);
    //}

    //#endregion

    //#region IWorld Interface

    ///// <summary>
    ///// Sets the specified atomic formula to true.
    ///// </summary>
    ///// <param name="formula">An atomic formula with constant arguments.</param>
    //public override void Set(AtomicFormulaApplication formula)
    //{
    //  System.Diagnostics.Debug.Assert(formula.AllConstantArguments);

    //  this.m_factsContainer.Set(formula.FormulaID);
    //}

    ///// <summary>
    ///// Sets the specified atomic formula to false.
    ///// </summary>
    ///// <param name="formula">A atomic formula with constant arguments.</param>
    //public override void Unset(AtomicFormulaApplication formula)
    //{
    //  System.Diagnostics.Debug.Assert(formula.AllConstantArguments);

    //  this.m_factsContainer.Unset(formula.FormulaID);
    //}

    ///// <summary>
    ///// Sets the new value of the specified numeric fluent.
    ///// </summary>
    ///// <param name="fluent">A numeric fluent with constant arguments.</param>
    ///// <param name="value">The new value of the numeric fluent.</param>
    //public override void SetNumericFluent(NumericFluentApplication fluent, double value)
    //{
    //  System.Diagnostics.Debug.Assert(fluent.AllConstantArguments);

    //  this.m_fluentsContainer.SetNumericFluent(fluent.FormulaID, value);
    //}

    ///// <summary>
    ///// Sets the new value of the specified object fluent.
    ///// </summary>
    ///// <param name="fluent">A object fluent with constant arguments.</param>
    ///// <param name="value">The constant representing the new value of the object fluent.
    ///// </param>
    //public override void SetObjectFluent(ObjectFluentApplication fluent, Constant value)
    //{
    //  System.Diagnostics.Debug.Assert(fluent.AllConstantArguments);

    //  this.m_fluentsContainer.SetObjectFluent(fluent.FormulaID, value);
    //}

    ///// <summary>
    ///// Sets the specified object fluent to undefined.
    ///// </summary>
    ///// <param name="fluent">A object fluent with constant arguments.</param>
    //public override void UndefineObjectFluent(ObjectFluentApplication fluent)
    //{
    //  System.Diagnostics.Debug.Assert(fluent.AllConstantArguments);

    //  this.m_fluentsContainer.UndefineObjectFluent(fluent.FormulaID);
    //}

    //#endregion

    //#region Object Interface Overrides

    ///// <summary>
    ///// Returns whether this world is equal to another object.
    ///// </summary>
    ///// <param name="obj">The other object to test for equality.</param>
    ///// <returns>Whether this world is equal to the other object.</returns>
    //public override bool Equals(object obj)
    //{
    //  if (obj == this)
    //  {
    //    return true;
    //  }
    //  else
    //  {
    //    CustomWorld other = (CustomWorld)obj;
    //    return this.m_factsContainer.Equals(other.m_factsContainer) &&
    //           this.m_fluentsContainer.Equals(other.m_fluentsContainer);
    //  }
    //}

    ///// <summary>
    ///// Returns the hash code of this world.
    ///// </summary>
    ///// <returns>The hash code of this world.</returns>
    //public override int GetHashCode()
    //{
    //  return this.m_factsContainer.GetHashCode() + this.m_fluentsContainer.GetHashCode();
    //}

    //#endregion

    //#region IComparable<ExtendedOpenWorld> Members

    ///// <summary>
    ///// Compares this custom world with another extended world.
    ///// </summary>
    ///// <param name="other">The other extended world to compare this custom world to.</param>
    ///// <returns>An integer representation the total order relation between the two worlds.
    ///// </returns>
    //public override int CompareTo(ExtendedOpenWorld other)
    //{
    //  CustomWorld otherWorld = (CustomWorld)other;

    //  int value = this.GetHashCode().CompareTo(other.GetHashCode());
    //  if (value != 0)
    //    return value;

    //  if ((value = this.m_factsContainer.CompareTo(otherWorld.m_factsContainer)) != 0)
    //    return value;

    //  return this.m_fluentsContainer.CompareTo(otherWorld.m_fluentsContainer);
    //}

    //#endregion
  };

  template <class FactsContainerType, class FluentsContainerType>
  CustomWorld<FactsContainerType, FluentsContainerType>::CustomWorld(IntegerInterval factsInterval, 
                                                             IntegerInterval numericInterval,
                                                             IntegerInterval objectInterval)
    : m_factsContainer(factsInterval), 
      m_fluentsContainer(numericInterval, objectInterval)
  {
  }

  template <class FactsContainerType, class FluentsContainerType>
  CustomWorld<FactsContainerType, FluentsContainerType>::CustomWorld(const CustomWorld &other)
    : m_factsContainer(other.m_factsContainer), 
      m_fluentsContainer(other.m_fluentsContainer)
  {
  }

  template <class FactsContainerType, class FluentsContainerType>
  CustomWorld<FactsContainerType, FluentsContainerType>* CustomWorld<FactsContainerType, FluentsContainerType>::copy() const
  {
    return new CustomWorld(*this);
  }

  template <class FactsContainerType, class FluentsContainerType>
  bool CustomWorld<FactsContainerType, FluentsContainerType>::isSet(const planning::AtomicFormulaApplication* formula) const
  {
    return m_factsContainer.isSet(formula->getFormulaId());
  }

  template <class FactsContainerType, class FluentsContainerType>
  void CustomWorld<FactsContainerType, FluentsContainerType>::set(const planning::AtomicFormulaApplication* formula)
  {
    m_factsContainer.set(formula->getFormulaId());
  }

  template <class FactsContainerType, class FluentsContainerType>
  void CustomWorld<FactsContainerType, FluentsContainerType>::unset(const planning::AtomicFormulaApplication* formula)
  {
    m_factsContainer.unset(formula->getFormulaId());
  }

  template <class FactsContainerType, class FluentsContainerType>
  double CustomWorld<FactsContainerType, FluentsContainerType>::getNumericFluent(const planning::NumericFluentApplication* fluent) const
  {
    return m_fluentsContainer.getNumericFluent(fluent->getFormulaId());
  }

  template <class FactsContainerType, class FluentsContainerType>
  const planning::Object* CustomWorld<FactsContainerType, FluentsContainerType>::getObjectFluent(const planning::ObjectFluentApplication* fluent) const
  {
    return m_fluentsContainer.getObjectFluent(fluent->getFormulaId());
  }

  template <class FactsContainerType, class FluentsContainerType>
  void CustomWorld<FactsContainerType, FluentsContainerType>::setNumericFluent(const planning::NumericFluentApplication* fluent, double value)
  {
    m_fluentsContainer.setNumericFluent(fluent->getFormulaId(), value);
  }

  template <class FactsContainerType, class FluentsContainerType>
  void CustomWorld<FactsContainerType, FluentsContainerType>::setNumericFluent(const planning::NumericFluentApplication* fluent, 
                                                                       const planning::ArithmeticFunction& function, 
                                                                       double argument)
  {
    m_fluentsContainer.setNumericFluent(fluent->getFormulaId(), function, argument);
  }

  template <class FactsContainerType, class FluentsContainerType>
  void CustomWorld<FactsContainerType, FluentsContainerType>::setObjectFluent(const planning::ObjectFluentApplication* fluent, 
                                                                      const planning::Object* value)
  {
    m_fluentsContainer.setObjectFluent(fluent->getFormulaId(), value);
  }

  template <class FactsContainerType, class FluentsContainerType>
  void CustomWorld<FactsContainerType, FluentsContainerType>::undefineObjectFluent(const planning::ObjectFluentApplication* fluent)
  {
    m_fluentsContainer.undefineObjectFluent(fluent->getFormulaId());
  }

  template <class FactsContainerType, class FluentsContainerType>
  size_t CustomWorld<FactsContainerType, FluentsContainerType>::getHashCode() const
  {
    return m_factsContainer.getHashCode();
  }

  template <class FactsContainerType, class FluentsContainerType>
  void CustomWorld<FactsContainerType, FluentsContainerType>::_output(std::ostream& out) const
  {
    // TODO
    out << "CustomWorld";
  }


  template <class FactsContainerType, class FluentsContainerType>
  int CustomWorld<FactsContainerType, FluentsContainerType>::compareTo(const IClosedWorld& other) const
  {
    const CustomWorld& world = static_cast<const CustomWorld&>(other);

    COMPARE(m_factsContainer, world.m_factsContainer);

    // Would be cool to compare the number of fluents BEFORE comparing the entire facts set

    COMPARE(m_fluentsContainer, world.m_fluentsContainer);

    return m_factsContainer.compareTo(world.m_factsContainer);
  }
}

#endif