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

#ifndef _STATISTICS_H
#define _STATISTICS_H

#include <sstream>

#include "../Utils/Stopwatch.h"

class IStatistics
{
public:
  IStatistics()
    : m_outcome(None),
      m_successorCount(0),
      m_successorsDiscardedConstraints(0),
      m_successorsDiscardedApplication(0),
      m_successorsDiscardedCycle(0),
      m_successorsCallCount(0),
      m_nodeCount(0)
  {
  }

  /// <summary>
  /// Represents the reason why the planning failed. A value of "None" means
  /// the planning did not fail.
  /// </summary>
  enum
  {
    /// <summary>
    /// The search is still running.
    /// </summary>
    None,
    /// <summary>
    /// The search was stopped by an external event.
    /// </summary>
    Aborted,
  };

  void setOutcome(size_t outcome) {
    m_outcome = outcome;
  }
  bool keepRunning() const {
    return m_outcome == None;
  }

  double getSolveTime() const {
    return m_solveWatch.getTimeElapsed();
  }

  /// <summary>
  /// Start measuring parse time.
  /// </summary>
  void startParse() { m_parseWatch.start(); }
  /// <summary>
  /// Stop measuring parse time.
  /// </summary>
  void stopParse() { m_parseWatch.stop(); }
  /// <summary>
  /// Start measuring solve time.
  /// </summary>
  void startSolve() { m_solveWatch.start(); }
  /// <summary>
  /// Stop measuring solve time.
  /// </summary>
  void stopSolve() { m_solveWatch.stop(); }
  /// <summary>
  /// Start measuring the time it takes to generate and insert successors in the search graph and 
  /// count one more world for which successors were generated.
  /// </summary>
  void startGenerateSuccessors() { m_generateSuccessorsWatch.start(); ++m_successorsCallCount; }
  /// <summary>
  /// Stop measuring the time it takes to generate and insert successors in the search graph.
  /// </summary>
  void stopGenerateSuccessors() { m_generateSuccessorsWatch.stop(); }

  /// <summary>
  /// Count one more successor generated.
  /// </summary>
  void successorGenerated() { ++m_successorCount; }
  /// <summary>
  /// Count one more successor generated and discarded because of violated constraints.
  /// </summary>
  void successorDiscardedConstraints() { successorGenerated(); ++m_successorsDiscardedConstraints; }
  /// <summary>
  /// Count one more successor generated and discarded because of violated action conditions in application.
  /// </summary>
  void successorDiscardedApplication() { successorGenerated(); ++m_successorsDiscardedApplication; }
  /// <summary>
  /// Count one more successor discarded because it caused a cycle.
  /// </summary>
  void successorDiscardedCycle() { ++m_successorsDiscardedCycle; }
  /// <summary>
  /// Count one more node examined.
  /// </summary>
  void nodeExamined() { ++m_nodeCount; }

  void outputHeader(std::ostream& stream) const
  {
    switch (m_outcome)
    {
    case None:
      stream << "Planning in progress..." << std::endl;
      break;
    case Aborted:
      stream << "Planning aborted; the following statistics may not be accurate." << std::endl;
      break;
    }
  }

  void outputTimes(std::ostream& stream) const
  {
    stream << "Parse time: "  << m_parseWatch.totalTimeElapsed() << " sec." << std::endl;
    stream << "Solve time: "  << m_solveWatch.totalTimeElapsed() << " sec." << std::endl;
    stream << "  Successor generation  : "  << m_generateSuccessorsWatch.totalTimeElapsed() << " sec. (" << m_successorsCallCount << " calls, avg " << m_generateSuccessorsWatch.totalTimeElapsed() / m_successorsCallCount << " sec.)" << std::endl;
  }

  void outputNodeDetails(std::ostream& stream) const
  {
    const int successorsKept = m_successorCount - (m_successorsDiscardedApplication + m_successorsDiscardedConstraints + m_successorsDiscardedCycle);

    stream << "Total nodes examined           : " << m_nodeCount << std::endl;
    stream << "Total nodes generated          : " << m_successorCount << " (avg " << ((double)m_successorCount) / m_successorsCallCount << ")" << std::endl;
    stream << "  Kept nodes                   : " << successorsKept  << " (avg " << ((double)successorsKept) / m_successorsCallCount << ")" << std::endl;
    stream << "  Discarded nodes (constraints): " << m_successorsDiscardedConstraints << " (avg " << ((double)m_successorsDiscardedConstraints) / m_successorsCallCount << ")" << std::endl;
    stream << "  Discarded nodes (apply)      : " << m_successorsDiscardedApplication << " (avg " << ((double)m_successorsDiscardedApplication) / m_successorsCallCount << ")" << std::endl;
    stream << "  Discarded nodes (cycle)      : " << m_successorsDiscardedCycle << " (avg " << ((double)m_successorsDiscardedCycle) / m_successorsCallCount << ")" << std::endl;
  }

  friend std::ostream& operator<< (std::ostream& stream, const IStatistics& statistics) {
    statistics._output(stream);
    return stream;
  }

protected:
  virtual void _output(std::ostream&  stream) const = 0;

  /// <summary>
  /// A watch that keeps track of parsing time.
  /// </summary>
  Stopwatch m_parseWatch;
  /// <summary>
  /// A watch that keeps track of solving time.
  /// </summary>
  Stopwatch m_solveWatch;
  /// <summary>
  /// A watch that keeps track of the time it takes to generate and insert successors in the search graph.
  /// </summary>
  Stopwatch m_generateSuccessorsWatch;

  /// <summary>
  /// The total number of generated successors.
  /// </summary>
  int m_successorCount;
  /// <summary>
  /// The number of successor discarded because of violated constraints.
  /// </summary>
  int m_successorsDiscardedConstraints;
  /// <summary>
  /// The number of successors discarded because of violated action conditions during application.
  /// </summary>
  int m_successorsDiscardedApplication;
  /// <summary>
  /// The number of successors discarded because they caused a cycle.
  /// </summary>
  int m_successorsDiscardedCycle;
  /// <summary>
  /// The number of world for which successors were generated.
  /// </summary>
  int m_successorsCallCount;
  /// <summary>
  /// The total number of nodes examined.
  /// </summary>
  int m_nodeCount;

  /// <summary>
  /// The reason why the planning failed.
  /// </summary>
  size_t m_outcome;

};

/// <summary>
/// Represents statistics gathered during the parsing and solving of a PDDL problem.
/// </summary>
template <class Derived>
class Statistics : public IStatistics
{
public:
  void _output(std::ostream& stream) const
  {
    static_cast<const Derived*>(this)->outputHeader(stream);

    static_cast<const Derived*>(this)->outputTimes(stream);

    static_cast<const Derived*>(this)->outputNodeDetails(stream);
  }
};

#endif