/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// Name        :
// Author      : Avi
// Revision    : $Revision: #33 $ 
//
// Copyright 2009-2017 ECMWF.
// This software is licensed under the terms of the Apache Licence version 2.0 
// which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
// In applying this licence, ECMWF does not waive the privileges and immunities 
// granted to it by virtue of its status as an intergovernmental organisation 
// nor does it submit to any jurisdiction. 
//
// Description :
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/noncopyable.hpp>
#include <boost/python/raw_function.hpp>

#include "PrintStyle.hpp"
#include "DState.hpp"
#include "SState.hpp"
#include "File.hpp"
#include "TimeSlot.hpp"
#include "TimeSeries.hpp"
#include "CheckPt.hpp"
#include "Ecf.hpp"

#include "Edit.hpp"
#include "NodeAttrDoc.hpp"
#include "BoostPythonUtil.hpp"


// See: http://wiki.python.org/moin/boost.python/HowTo#boost.function_objects
template<class K, class T>
	struct pair_to_tuple  {
		typedef pair_to_tuple<K,T> converter;
		typedef std::pair<K,T> ctype;

		static PyObject* convert(ctype const& v)  {
			using namespace boost::python;
			return incref(make_tuple(v.first,v.second).ptr());
		}
		static void register_to_python()  {
			boost::python::to_python_converter<ctype,converter>();
		}
};

using namespace boost::python;
using namespace std;
using namespace ecf;

bool debug_build()
{
#ifdef NDEBUG
   return false;
#else
   return true;
#endif
}

void export_Core()
{
   // For use in test only
   def("debug_build",debug_build);


   // see: https://github.com/boostorg/python/blob/master/test/raw_ctor.cpp
   // Uses a raw constructor approach to support pass arbitrary number arguments on the python side.
   // using no_init postpones defining __init__ function until after raw_function for proper overload resolution order,
   // since later defs get higher priority.
   class_<Edit>("Edit", NodeAttrDoc::variable_doc(), no_init)
             .def("__init__", raw_function(&Edit::init,0)) // raw_constructor -> will call -> def(init<dict>() )
             .def(init<dict>())                 //
             .def(init<dict,dict>())            //
             .def("__str__",  &Edit::to_string) // __str__
             ;

	class_<File, boost::noncopyable >("File", "Utility class, Used in test only.")
            .def("find_server",&File::find_ecf_server_path, "Provides pathname to the server") .staticmethod("find_server")
            .def("find_client",&File::find_ecf_client_path, "Provides pathname to the client") .staticmethod("find_client")
            .def("source_dir", &File::root_source_dir, "Path name to ecflow source directory") .staticmethod("source_dir")
            .def("build_dir",  &File::root_build_dir, "Path name to ecflow build directory")   .staticmethod("build_dir")
 	;

	enum_<PrintStyle::Type_t>("Style",
	                          "Style is used to control printing output for the definition\n\n"
	                          "- DEFS:  This style outputs the definition file in a format that is parse-able.\n"
	                          "         and can be re-loaded back into the server.\n"
	                          "         Externs are automatically added.\n"
                             "         This excludes the edit history.\n"
	                          "- STATE: The output includes additional state information for debug\n"
	                          "         This excludes the edit history\n"
	                          "- MIGRATE: Output includes structure and state, allow migration to future ecflow versions\n"
                             "         This includes edit history. If file is reloaded no checking is done\n\n"
	                          "The following shows a summary of the features associated with each choice\n"
	                          "\n"
	                          "   ===================== ==== ===== =======\n"
	                          "   Functionality         DEFS STATE MIGRATE\n"
                             "   ===================== ==== ===== =======\n"
	                          "   Auto generate externs  Yes  Yes   No\n"
	                          "   Checking on reload     Yes  Yes   No\n"
	                          "   Edit History           No   No    Yes\n"
	                          "   Show trigger AST       No   Yes   No\n"
                             "   ===================== ==== ===== =======\n"
	)
	.value("NOTHING",  PrintStyle::NOTHING)
	.value("DEFS",     PrintStyle::DEFS)
   .value("STATE",    PrintStyle::STATE)
   .value("MIGRATE",  PrintStyle::MIGRATE)
	;

   class_<PrintStyle, boost::noncopyable >("PrintStyle",
                                           "Singleton used to control the print Style. See :py:class:`ecflow.Style`\n\n"
                                           "\nUsage::\n\n"
                                           "   old_style = PrintStyle.get_style()\n"
                                           "   PrintStyle.set_style(PrintStyle.STATE)\n"
                                           "   ...\n"
                                           "   print(defs)                     # show the node state\n"
                                           "   PrintStyle.set_style(old_style) # reset previous style\n"
                                           ,
                                           no_init)
   .def("get_style", &PrintStyle::getStyle,"Returns the style, static method")
   .staticmethod("get_style")
   .def("set_style", &PrintStyle::setStyle,"Set the style, static method")
   .staticmethod("set_style")
   ;

   enum_<CheckPt::Mode>("CheckPt",
            "CheckPt is enum that is used to control check pointing in the `ecflow_server`_\n\n"
            "- NEVER  : Switches of check pointing\n"
            "- ON_TIME: `check point`_ file is saved periodically, specified by checkPtInterval. This is the default.\n"
            "- ALWAYS : `check point`_ file is saved after any state change, *not* recommended for large definitions\n"
            "- UNDEFINED : None of the the above, used to provide default argument\n"
   )
   .value("NEVER",  CheckPt::NEVER)
   .value("ON_TIME",CheckPt::ON_TIME)
   .value("ALWAYS", CheckPt::ALWAYS)
   .value("UNDEFINED", CheckPt::UNDEFINED)
   ;

	class_<Ecf, boost::noncopyable >("Ecf",
			"Singleton used to control ecf debugging\n\n",
			no_init)
	.def("debug_equality", &Ecf::debug_equality,"Returns true if debugging of equality is enabled")
	.staticmethod("debug_equality")
	.def("set_debug_equality", &Ecf::set_debug_equality,"Set debugging for equality")
	.staticmethod("set_debug_equality")
   .def("debug_level", &Ecf::debug_level,"Returns integer showing debug level. debug_level > 0 will disable some warning messages")
   .staticmethod("debug_level")
   .def("set_debug_level", &Ecf::set_debug_level,"Set debug level. debug_level > 0 will disable some warning messages")
   .staticmethod("set_debug_level")
 	;

	enum_<NState::State>("State",
			"Each `node`_ can have a status, which reflects the life cycle of a node.\n\n"
			"It varies as follows:\n\n"
			"- When the definition file is loaded into the `ecflow_server`_ the `task`_ status is `unknown`_\n"
			"- After begin command the `task`_ s are either `queued`_, `complete`_, `aborted`_ or `suspended`_ ,\n"
			"  a suspended task means that the task is really `queued`_ but it must be resumed by\n"
			"  the user first before it can be `submitted`_. See :py:class:`ecflow.DState`\n"
			"- Once the `dependencies`_ are resolved a task is submitted and placed into the `submitted`_ state,\n"
			"  however if the submission fails, the task is placed in a `aborted`_ state.\n"
			"- On a successful submission the task is placed into the `active`_ state\n"
			"- Before a job ends, it may send other message to the server such as:\n"
			"  Set an `event`_, Change a `meter`_, Change a `label`_, send a message to log file\n\n"
			"Jobs end by becoming either `complete`_ or `aborted`_"
			)
	.value("unknown",  NState::UNKNOWN)
	.value("complete", NState::COMPLETE)
	.value("queued",   NState::QUEUED)
	.value("aborted",  NState::ABORTED)
	.value("submitted",NState::SUBMITTED)
 	.value("active",   NState::ACTIVE)
 	;

	enum_<DState::State>("DState",
	                     "A DState is like a ecflow.State, except for the addition of SUSPENDED\n\n"
	                     "Suspended stops job generation, and hence is an attribute of a Node.\n"
	                     "DState can be used for setting the default state of node when it is\n"
	                     "begun or re queued. DState is used for defining `defstatus`_.\n"
	                     "See :py:class:`ecflow.Node.add_defstatus` and :py:class:`ecflow.Defstatus`\n"
	                     "The default state of a `node`_ is `queued`_.\n"
	                     "\nUsage::\n\n"
	                     "   task = ecflow.Task('t1')\n"
                        "   task.add_defstatus(ecflow.DState.complete)"
                        "   task = ecflow.Task('t2')\n"
                        "   task += Defstatus('complete')\n"
                        "   task = Task('t3',\n"
	                     "               Defstatus('complete')) # create in place\n"
			)
	.value("unknown",  DState::UNKNOWN)
	.value("complete", DState::COMPLETE)
	.value("queued",   DState::QUEUED)
	.value("aborted",  DState::ABORTED)
	.value("submitted",DState::SUBMITTED)
	.value("suspended",DState::SUSPENDED)
	.value("active",   DState::ACTIVE)
	;

   class_<Defstatus>("Defstatus",
                     "A `node`_ can be set with a default status other the `queued`_\n\n"
                     "The default state of a `node`_ is `queued`_.\n"
                     "This defines the state to take at 'begin' or 're-queue' time\n"
                     "See :py:class:`ecflow.Node.add_defstatus` and :py:class:`ecflow.DState`\n"
                     ,
                     init<DState::State>())
            .def(init<std::string>())                              // constructor
            .def("state",  &Defstatus::state)
            .def("__str__",  &Defstatus::to_string) // __str__
            ;

	enum_<SState::State>("SState",
	         "A SState holds the `ecflow_server`_ state\n\n"
	         "See `server states`_"
	)
	.value("HALTED",   SState::HALTED)
	.value("SHUTDOWN", SState::SHUTDOWN)
	.value("RUNNING",  SState::RUNNING)
	;


	class_<TimeSlot>("TimeSlot",
			"Represents a time slot.\n\n"
	        "It is typically used as an argument to a :py:class:`TimeSeries` or\n"
			"other time dependent attributes of a node.\n"
			"\n"
			"\nConstructor::\n\n"
			"   TimeSlot(hour,min)\n"
			"      int hour:   represent an hour:\n"
			"      int minute: represents a minute:\n"
			"\nUsage::\n\n"
			"   ts = TimeSlot(10,11)\n"
			,
			init<int,int>()
		)
	.def("__str__",     &TimeSlot::toString)       // __str__
   .def("__copy__",copyObject<TimeSlot>)          // __copy__ uses copy constructor
	.def(self == self )                            // __eq__
	.def("hour",   &TimeSlot::hour)                // return int
	.def("minute", &TimeSlot::minute)              // return int
	.def("empty",  &TimeSlot::isNULL)              // return bool
	;

	// single slot, | start, finish, incr,  bool relative to suite start
	class_<TimeSeries>("TimeSeries",
			"A TimeSeries can hold a single time slot or a series.\n\n"
	      "Time series can be created relative to the `suite`_ start or start of a repeating node.\n"
	      "A Time series can be used as argument to the :py:class:`ecflow.Time`, :py:class:`ecflow.Today` and :py:class:`ecflow.Cron` attributes of a node.\n"
			"If a time the job takes to complete is longer than the interval, a 'slot' is missed\n"
			"e.g time 10:00 20:00 01:00, if the 10.00 run takes more than an hour the 11.00 is missed\n\n"
			"\nConstructor::\n\n"
			"   TimeSeries(single,relative_to_suite_start)\n"
			"      TimeSlot single :  A single point in a 24 clock \n"
			"      optional bool relative_to_suite_start : depend on suite begin time or\n"
			"                                              start of repeating node. Default is false\n"
			"\n"
			"   TimeSeries(hour,minute,relative_to_suite_start)\n"
			"      int hour   :  hour in 24 clock \n"
			"      int minute :  minute < 59 \n"
			"      bool relative_to_suite_start<optional> : depend on suite begin time or\n"
			"                                              start of repeating node. Default is false\n"
			"\n"
			"   TimeSeries(start,finish,increment,relative_to_suite_start)\n"
			"      start TimeSlot :     The start time  \n"
			"      finish TimeSlot :    The finish time, when used in a series. This must greater than the start.\n"
			"      increment TimeSlot : The increment. This must be less that difference between start and finish\n"
			"      bool relative_to_suite_start<optional> : The time is relative suite start, or start of repeating node.\n"
			"                                               The default is false\n"
			"\nExceptions:\n\n"
			"- Raises IndexError when an invalid time series is specified\n"
 			"\nUsage::\n\n"
			"   time_series = TimeSeries(TimeSlot(10,11),False)\n"			,
			init<TimeSlot, optional<bool> >())
 	.def( init<int,int,optional<bool> >())
	.def( init<TimeSlot,TimeSlot,TimeSlot,optional<bool> >())
 	.def(self == self )                                // __eq__
 	.def("__str__",     &TimeSeries::toString)         // __str__
   .def("__copy__",copyObject<TimeSeries>)            // __copy__ uses copy constructor
	.def("has_increment", &TimeSeries::hasIncrement,"distinguish between a single time slot and a series. returns true for a series")  // false if single time slot
	.def("start",         &TimeSeries::start , return_value_policy<copy_const_reference>(),"returns the start time") // returns a time slot
	.def("finish",        &TimeSeries::finish, return_value_policy<copy_const_reference>(),"returns the finish time if time series specified, else returns a NULL time slot") // returns a time slot
	.def("incr",          &TimeSeries::incr,   return_value_policy<copy_const_reference>()," returns the increment time if time series specified, else returns a NULL time slot") // returns a time slot
	.def("relative",      &TimeSeries::relative,"returns a boolean where true means that the time series is relative")
 	;

	using namespace boost::python;
 	pair_to_tuple<std::string,std::string>::register_to_python();
}
