//
// 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 _OPTIONS_H
#define _OPTIONS_H

#include <boost/program_options.hpp>
#include <boost/lexical_cast.hpp>
#include <limits>
#undef min
#undef max
#include <iostream>
#include <sstream>
#include "Utils.h"

enum SetStructureImplementation {
  ssHashset,
  ssTreeset
};

std::ostream& operator<<(std::ostream& out, SetStructureImplementation impl);

template <>
struct EnumTraits<SetStructureImplementation>
{
  static const std::string description;

  static const std::pair<std::string, SetStructureImplementation> values[2];
};

/// <summary>
/// This class regroups all possible options.
/// </summary>
/// <remarks>
/// The DifferentBehavior attribute indicates that changing this option may have an effect
/// on the planner's search mechanism. Hence the search graph may be explored differently and
/// different plans may be found.
/// On the other hand, the SameBehavior attribute indicates that changing this attribute should have no effect
/// on the search mechanism, but it may slow or speed up the search, depending on the
/// problem. The same plan should always be returned.
/// </remarks>
template <class Derived>
class Options
{
public:
  Options()
    : m_checkCycles(true),
      m_traceLevel(0),
      m_setStructureImplementation(ssHashset),
      m_timeLimit(std::numeric_limits<double>::max()),
      m_traceFile(&std::cout)
  {
  }

  bool checkCycles() const {
    return m_checkCycles;
  }
  void setCycleCheck(bool checkCycles)  {
    m_checkCycles = checkCycles;
  }
  void setTimeLimit(double timeLimit) {
    m_timeLimit = timeLimit;
  }
  bool hasTimeLimit() const {
    return m_timeLimit != std::numeric_limits<double>::max();
  }
  bool reachedTimeLimit(double t) const {
    return t >= m_timeLimit;
  }
  SetStructureImplementation getSetStructureImplementation() const {
    return m_setStructureImplementation;
  }
  void setStructureImplementation(SetStructureImplementation impl) const {
    m_setStructureImplementation = impl;
  }

  class TraceStream
  {
  public:
    TraceStream(std::ostream& traceFile)
      : m_traceFile(traceFile)
    {}

    void write(const std::string& text) {
      m_traceFile << text << std::endl;
    }

  private:
    std::ostream& m_traceFile;
  };

  bool traceEnabled() const {
    return m_traceLevel > 0;
  }

  TraceStream getTraceStream() const {
    return TraceStream(*m_traceFile);
  }

  void setTraceLevel(size_t traceLevel) {
    m_traceLevel = traceLevel;
  }

  size_t getTraceLevel() const {
    return m_traceLevel;
  }

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

  void _output(std::ostream& stream) const
  {
    static_cast<const Derived*>(this)->outputHeader(stream);
    stream << "Structure implementation: " << enumToString(m_setStructureImplementation) << std::endl;
    stream << "Options: Cycle checking               = " <<  (m_checkCycles ? "True" : "False") << std::endl;
    static_cast<const Derived*>(this)->outputOptions(stream);
    stream << "         Time limit                   = " <<  (hasTimeLimit() ? boost::lexical_cast<std::string>(m_timeLimit) : "None") << std::endl;
  }

  // Options parsing

  template <class Enum>
  static std::string getItemsDescription()
  {
    std::string description;
    const size_t nrValues = sizeof(EnumTraits<Enum>::values) / sizeof(std::pair<std::string, Enum>);
    for (size_t i = 0; i < nrValues; ++i)
    {
      if (i > 0)
        if (nrValues > 2)
          if (i == (nrValues - 1))
            description.append(", or ");
          else
            description.append(", ");
        else
          description.append(" or ");

      description.append("'").append(EnumTraits<Enum>::values[i].first).append("'");
    }
    return description;
  }

  void addGenericOptions(boost::program_options::options_description& genericOptions)
  {
    namespace po = boost::program_options;

	  genericOptions.add_options()
	    ( "help", "Print this message" )
	    ( "config-file,f", po::value<string>(),
	      "Use configuration file instead of command line \n"
	      "\tUsage: OPTION = VALUE\n"
	      "\tOPTION are listed in 'Configuration' below\n"
	      "[ without the \"--\" characters ]"
	    )
    ;
  }

  void addConfigOptions(boost::program_options::options_description& configOptions)
  {
    namespace po = boost::program_options;

    configOptions.add_options()
	    ( "cycles,c",
	      po::value<bool>(&m_checkCycles)->default_value(m_checkCycles),
	      "Whether to check for cycles in the search graph" )
	    ( "time-limit,t",
        po::value<double>(&m_timeLimit)->default_value(m_timeLimit == std::numeric_limits<double>::max() ? 0 : m_timeLimit),
	      "The limit on available solve time (0 if no limit)" )
	    ( "trace-level,l",
        po::value<size_t>(&m_traceLevel)->default_value(m_traceLevel),
	      "The trace level (0 if no trace)" )
	    ( "set-implementation,s",
        po::value<SetStructureImplementation>(&m_setStructureImplementation)->default_value(m_setStructureImplementation),
        std::string("The implementation of set structure; use " + getItemsDescription<SetStructureImplementation>()).c_str() )
      ;
  }

  void validate()
  {
    if (m_timeLimit < 0.)
      throw std::runtime_error("Time limit must be >= 0");
    else if (m_timeLimit == 0.)
      m_timeLimit = std::numeric_limits<double>::max();
  }

  bool parse(int argc, char* argv[])
  {
    namespace po = boost::program_options;

   // Allowed only on the command line
	  po::options_description genericOptions ( "Generic options" );
    static_cast<Derived*>(this)->addGenericOptions(genericOptions);

	  po::options_description configOptions ( "Configuration" );
    static_cast<Derived*>(this)->addConfigOptions(configOptions);

    po::options_description visibleOptions("Allowed options");
    visibleOptions.add(genericOptions).
                   add(configOptions);

    //po::options_description configurable_options ( "Configurable options" );
    //configurable_options.add ( configOptions );

    try
    {
	    po::variables_map cmdline;
	    po::store ( po::parse_command_line ( argc, argv, visibleOptions ), cmdline );
	    po::notify ( cmdline );

	    if ( cmdline.count ( "help" ) )
	    {
        std::cout << static_cast<const Derived*>(this)->getProgramName() << " Version 1.0" << std::endl;
		    std::cout << std::endl << visibleOptions << std::endl;
        return false;
	    }

	    /** READ OPTIONS FROM CONFIG FILE
		    **/
	    if ( cmdline.count ( "config_file" ) )
	    {
		    po::variables_map cfgfile;
		    ifstream icfg ( cmdline["config_file"].as<string>().c_str(), ios::in );
		    if ( !icfg.is_open() )
		    {
			    cerr << "Error opening " << cmdline["config_file"].as<string>() << endl;
			    return false;
		    }
		    po::store ( po::parse_config_file ( icfg, configOptions ), cfgfile );
		    po::notify ( cfgfile );
		    icfg.close();
		  }
      static_cast<Derived*>(this)->validate();
	  }
	  catch (exception &e) 
    { 
      cerr << "[EXCEPTION] " << e.what() << endl; 
      std::cout << std::endl << genericOptions << std::endl << configOptions << std::endl;
      return false;
    }

    return true;
  }

protected:
  bool m_checkCycles;
  SetStructureImplementation m_setStructureImplementation;
  double m_timeLimit;

  size_t m_traceLevel;
  std::ostream* m_traceFile;
};

template <class Enum>
Enum validateEnum(const std::vector<std::string>& values)
{
  if (values.size() != 1)
    throw std::runtime_error("Invalid " + EnumTraits<Enum>::description);

  return stringToEnum<Enum>(values[0]);
}

void validate(boost::any& v, const std::vector<std::string>& values, SetStructureImplementation*, int);

#endif