/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// Name        :
// Author      : Avi
// Revision    : $Revision: #13 $ 
//
// Copyright 2009-2020 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/test/unit_test.hpp>
#include "boost/filesystem/operations.hpp"
#include <boost/date_time/posix_time/posix_time_types.hpp>

#include "TodayAttr.hpp"
#include "TimeAttr.hpp"
#include "VerifyAttr.hpp"
#include "RepeatAttr.hpp"
#include "LateAttr.hpp"
#include "DayAttr.hpp"
#include "DateAttr.hpp"
#include "CronAttr.hpp"
#include "ClockAttr.hpp"
#include "AutoCancelAttr.hpp"
#include "AutoArchiveAttr.hpp"
#include "NodeAttr.hpp"
#include "Variable.hpp"
#include "ZombieAttr.hpp"
#include "QueueAttr.hpp"
#include "GenericAttr.hpp"
#include "SerializationTest.hpp"
#include "TimeSlot.hpp"
#include "File.hpp"
#include "cereal_boost_time.hpp"

using namespace std;
using namespace ecf;
using namespace boost::posix_time;
using namespace boost::gregorian;
namespace fs = boost::filesystem;

//#define UPDATE_TESTS 1


BOOST_AUTO_TEST_SUITE( ANattrTestSuite )

// These test are used for future release. They help to ensure that we have
// backward compatibility.i.e future release can open file, created by an earlier release
//
BOOST_AUTO_TEST_CASE( test_migration_restore_def_con )
{
   cout << "ANattr:: ...test_migration_restore_def_con\n";

   std::string file_name = File::test_data("ANattr/test/data/migration/default_constructor_1_2_2/","ANattr");
   //BOOST_CHECK_MESSAGE(File::createDirectories(file_name ),"Could not create directory " << file_name);

   // Create migration data
#ifdef UPDATE_TESTS
   doSave(file_name + "VerifyAttr",VerifyAttr());
   doSave(file_name + "TodayAttr",TodayAttr());
   doSave(file_name + "TimeAttr",TimeAttr());
   doSave(file_name + "RepeatDate",RepeatDate());
   doSave(file_name + "RepeatDateList",RepeatDateList());
   doSave(file_name + "RepeatInteger",RepeatInteger());
   doSave(file_name + "RepeatEnumerated",RepeatEnumerated());
   doSave(file_name + "RepeatString",RepeatString());
   doSave(file_name + "LateAttr",LateAttr());
   doSave(file_name + "DayAttr",DayAttr());
   doSave(file_name + "DateAttr",DateAttr());
   doSave(file_name + "CronAttr",CronAttr());
   doSave(file_name + "ClockAttr",ClockAttr());
   doSave(file_name + "AutoCancelAttr",AutoCancelAttr());
   doSave(file_name + "AutoArchiveAttr",AutoArchiveAttr());
   doSave(file_name + "Label",Label());
   doSave(file_name + "Variable",Variable());
   doSave(file_name + "Event",Event());
   doSave(file_name + "Meter",Meter());
   doSave(file_name + "ZombieAttr",ZombieAttr());
   doSave(file_name + "QueueAttr",QueueAttr());
   doSave(file_name + "GenericAttr",GenericAttr());
#endif

   do_restore<VerifyAttr>(file_name + "VerifyAttr",VerifyAttr());
   do_restore<TodayAttr>(file_name + "TodayAttr",TodayAttr());
   do_restore<TimeAttr>(file_name + "TimeAttr",TimeAttr());
   do_restore<RepeatDate>(file_name + "RepeatDate",RepeatDate());
   do_restore<RepeatDateList>(file_name + "RepeatDateList",RepeatDateList());
   do_restore<RepeatInteger>(file_name + "RepeatInteger",RepeatInteger());
   do_restore<RepeatEnumerated>(file_name + "RepeatEnumerated",RepeatEnumerated());
   do_restore<RepeatString>(file_name + "RepeatString",RepeatString());
   do_restore<LateAttr>(file_name + "LateAttr",LateAttr());
   do_restore<DayAttr>(file_name + "DayAttr",DayAttr());
   do_restore<DateAttr>(file_name + "DateAttr",DateAttr());
   do_restore<CronAttr>(file_name + "CronAttr",CronAttr());
   do_restore<AutoCancelAttr>(file_name + "AutoCancelAttr",AutoCancelAttr());
   do_restore<AutoArchiveAttr>(file_name + "AutoArchiveAttr",AutoArchiveAttr());
   do_restore<Label>(file_name + "Label",Label());
   do_restore<Variable>(file_name + "Variable",Variable());
   do_restore<Event>(file_name + "Event",Event());
   do_restore<Meter>(file_name + "Meter",Meter());
   do_restore<ZombieAttr>(file_name + "ZombieAttr",ZombieAttr());
   do_restore<QueueAttr>(file_name + "QueueAttr",QueueAttr());
   do_restore<GenericAttr>(file_name + "GenericAttr",GenericAttr());
}

BOOST_AUTO_TEST_CASE( test_migration_restore )
{
   cout << "ANattr:: ...test_migration_restore\n";

   std::string file_name = File::test_data("ANattr/test/data/migration/1_2_2/","ANattr");
   //BOOST_CHECK_MESSAGE(File::createDirectories(file_name ),"Could not create directory " << file_name);

   std::vector<std::string> theVec; theVec.emplace_back("a");  theVec.emplace_back("b");
   LateAttr lateattr;
   lateattr.addSubmitted(TimeSlot(10,12));
   lateattr.addActive(TimeSlot(10,12));
   lateattr.addComplete(TimeSlot(10,12),true);

   CronAttr cron_attr;
   std::vector<int> weekDays; weekDays.push_back(1); weekDays.push_back(2);
   std::vector<int> daysOfMonth; daysOfMonth.push_back(1); daysOfMonth.push_back(2);
   std::vector<int> months; months.push_back(1); months.push_back(2);
   cron_attr.addWeekDays(weekDays);
   cron_attr.addDaysOfMonth(daysOfMonth);
   cron_attr.addMonths(months);
   cron_attr.addTimeSeries(TimeSlot(0,0),TimeSlot(20,0),TimeSlot(0,1));

   ClockAttr clock_attr(false);
   clock_attr.date(1,1,2009);
   clock_attr.set_gain_in_seconds(3600);

   std::vector<ecf::Child::CmdType> child_cmds = ecf::Child::list();

   Label label("name","value");
   label.set_new_value("new_value");

#ifdef UPDATE_TESTS
   // Create migration data
   doSave(file_name + "VerifyAttr",VerifyAttr(NState::COMPLETE,3));
   doSave(file_name + "TodayAttr",TodayAttr(10,12));
   doSave(file_name + "TimeAttr",TimeAttr(10,12));
   doSave(file_name + "RepeatDate",RepeatDate("date",20110112,20110115));
   doSave(file_name + "RepeatDateList",RepeatDateList("datelist",{19990101,19990102}));
   doSave(file_name + "RepeatInteger",RepeatInteger("integer",0,100,2));
   doSave(file_name + "RepeatEnumerated",RepeatEnumerated("enum",theVec));
   doSave(file_name + "RepeatString",RepeatString("string",theVec));
   doSave(file_name + "LateAttr",lateattr);
   doSave(file_name + "DayAttr",DayAttr(DayAttr::MONDAY));
   doSave(file_name + "DateAttr",DateAttr(12,12,2012));
   doSave(file_name + "CronAttr",cron_attr);
   doSave(file_name + "ClockAttr",clock_attr);
   doSave(file_name + "AutoCancelAttr",AutoCancelAttr(100));
   doSave(file_name + "AutoCancelAttr_1",AutoCancelAttr(TimeSlot(10,12),true));
   doSave(file_name + "AutoArchiveAttr",AutoArchiveAttr(100));
   doSave(file_name + "AutoArchiveAttr_1",AutoArchiveAttr(TimeSlot(10,12),true));
   doSave(file_name + "Label",label);
//   doSave(file_name + "Limit",limit);
   doSave(file_name + "Variable",Variable("var_name","var_value"));
   doSave(file_name + "Event_1",Event(1));
   doSave(file_name + "Event_2",Event("event"));
   doSave(file_name + "Event_3",Event(1,"event",true));
   doSave(file_name + "Meter",Meter("meter",10,100,100));
   doSave(file_name + "ZombieAttr",ZombieAttr(ecf::Child::USER, child_cmds, ecf::User::FOB));
   doSave(file_name + "ZombieAttr1",ZombieAttr(ecf::Child::USER, child_cmds, ecf::User::FOB,500));
   doSave(file_name + "QueueAttr",QueueAttr("queue",theVec));
   doSave(file_name + "GenericAttr", GenericAttr("gen1",theVec));
#endif

   do_restore<VerifyAttr>(file_name + "VerifyAttr",VerifyAttr(NState::COMPLETE,3));
   do_restore<TodayAttr>(file_name + "TodayAttr",TodayAttr(10,12));
   do_restore<TimeAttr>(file_name + "TimeAttr",TimeAttr(10,12));
   do_restore<RepeatDate>(file_name + "RepeatDate",RepeatDate("date",20110112,20110115));
   do_restore<RepeatDateList>(file_name + "RepeatDateList",RepeatDateList("datelist",{19990101,19990102} ));
   do_restore<RepeatInteger>(file_name + "RepeatInteger",RepeatInteger("integer",0,100,2));
   do_restore<RepeatEnumerated>(file_name + "RepeatEnumerated",RepeatEnumerated("enum",theVec));
   do_restore<RepeatString>(file_name + "RepeatString",RepeatString("string",theVec));
   do_restore<LateAttr>(file_name + "LateAttr",lateattr);
   do_restore<DayAttr>(file_name + "DayAttr",DayAttr(DayAttr::MONDAY));
   do_restore<DateAttr>(file_name + "DateAttr",DateAttr(12,12,2012));
   do_restore<CronAttr>(file_name + "CronAttr",cron_attr);
   do_restore<ClockAttr>(file_name + "ClockAttr",clock_attr);
   do_restore<AutoCancelAttr>(file_name + "AutoCancelAttr",AutoCancelAttr(100));
   do_restore<AutoCancelAttr>(file_name + "AutoCancelAttr_1",AutoCancelAttr(TimeSlot(10,12),true));
   do_restore<AutoArchiveAttr>(file_name + "AutoArchiveAttr",AutoArchiveAttr(100));
   do_restore<AutoArchiveAttr>(file_name + "AutoArchiveAttr_1",AutoArchiveAttr(TimeSlot(10,12),true));
   do_restore<Label>(file_name + "Label",label);
   do_restore<Variable>(file_name + "Variable",Variable("var_name","var_value"));
   do_restore<Event>(file_name + "Event_1",Event(1));
   do_restore<Event>(file_name + "Event_2",Event(string("event")));
   do_restore<Event>(file_name + "Event_3",Event(1,string("event"),true));
   do_restore<Meter>(file_name + "Meter",Meter("meter",10,100,100));
   do_restore<ZombieAttr>(file_name + "ZombieAttr",ZombieAttr(ecf::Child::USER, child_cmds, ecf::User::FOB));
   do_restore<ZombieAttr>(file_name + "ZombieAttr1",ZombieAttr(ecf::Child::USER, child_cmds, ecf::User::FOB,500));
   do_restore<QueueAttr>(file_name + "QueueAttr",QueueAttr("queue",theVec));
   do_restore<GenericAttr>(file_name + "GenericAttr",GenericAttr("gen1",theVec));
}


// The following test shows that CEREAL ignores new data members it does not recognise.
// This allows new data members, which will be ignored by the old client.
// OLD server to NEW Client(GUI), will not be a problem, since the the new data members are conditional.
// In ecflow 5.4.0/5.5.0 we added a new data members to the DayAttribute class.
// This test shows that when NEW SERVER send DayAttr to OLD Client(GUI), this new data member is ignored.
namespace version_old {
class DayAttr {
public:
	enum Day_t { SUNDAY=0, MONDAY=1, TUESDAY=2, WEDNESDAY=3, THURSDAY=4, FRIDAY=5, SATURDAY=6 };
	DayAttr() = default;
	explicit DayAttr(Day_t day) : day_(day) {}
	bool operator==(const DayAttr& rhs) const { return day_ == rhs.day() && free_ == rhs.free();}

	DayAttr::Day_t day() const { return day_;}
	bool free() const { return free_;}
	void set_free()   { free_ = true;}
private:
	DayAttr::Day_t day_{DayAttr::SUNDAY};
	bool           free_{false};

	friend class cereal::access;
	template<class Archive>
	void serialize(Archive & ar ) {
		ar( CEREAL_NVP(day_));
		CEREAL_OPTIONAL_NVP(ar, free_,    [this](){return free_;});  // conditionally save
	}
};
}

namespace version_new_data_member {
class DayAttr {
public:
	enum Day_t { SUNDAY=0, MONDAY=1, TUESDAY=2, WEDNESDAY=3, THURSDAY=4, FRIDAY=5, SATURDAY=6 };
	DayAttr() = default;
	explicit DayAttr(Day_t day) : day_(day) {}
  	bool operator==(const DayAttr& rhs) const {
  		return day_ == rhs.day_ && expired_ == rhs.expired() && free_ == rhs.free() && date_ == rhs.date_ ;}

	DayAttr::Day_t day() const { return day_;}
	bool expired() const { return expired_;}
	bool free() const { return free_;}
	boost::gregorian::date date() const { return date_;}

	void set_free()    { free_ = true;}
	void set_expired() { expired_ = true;}
	void set_date(boost::gregorian::date date) { date_ = std::move(date);}
private:
	DayAttr::Day_t day_{DayAttr::SUNDAY};
	bool           free_{false};
	bool           expired_{false};  // new data member 5.4.0
	boost::gregorian::date date_;    // new data member 5.5.0

	friend class cereal::access;
	template<class Archive>
	void serialize(Archive & ar ) {
		ar( CEREAL_NVP(day_));
		CEREAL_OPTIONAL_NVP(ar, free_,    [this](){return free_;});     // conditionally save
		CEREAL_OPTIONAL_NVP(ar, expired_, [this](){return expired_;});  // conditionally save
		CEREAL_OPTIONAL_NVP(ar, date_,    [this](){return !date_.is_special();}); // conditionally save, new to ecflow 5.5.0,
	}
};
}

BOOST_AUTO_TEST_CASE( test_day_migration )
{
   cout << "ANattr:: ...test_day_migration\n";
   // OLD -> NEW  i.e OLD SERVER --> NEW CLIENT
   {
      const version_old::DayAttr t = version_old::DayAttr();
      ecf::save("test_day_migration",t);
   }
   {
      version_new_data_member::DayAttr t;
      ecf::restore("test_day_migration",t);
      BOOST_CHECK_MESSAGE(t == version_new_data_member::DayAttr(),"Should be the same");
   }


   // NEW->OLD  i.e NEW SERVER --> OLD CLIENT
   // IMPORTANT: This shows that CEREAL ignore data members is does NOT RECOGNIZE.
   {
	   version_new_data_member::DayAttr def;
	   ecf::save("test_day_migration_def",def);

	   version_new_data_member::DayAttr def_free; def_free.set_free();
	   ecf::save("test_day_migration_free",def_free);

	   version_new_data_member::DayAttr def_expired; def_expired.set_expired();
	   ecf::save("test_day_migration_expired",def_expired);

	   version_new_data_member::DayAttr def_free_and_expired; def_free_and_expired.set_expired(); def_free_and_expired.set_free();
	   ecf::save("test_day_migration_free_and_expired",def_free_and_expired);

	   version_new_data_member::DayAttr def_free_expired_date;
	   def_free_expired_date.set_expired();
	   def_free_expired_date.set_free();
	   def_free_expired_date.set_date(boost::gregorian::date(2020,06,28));  // sunday
	   ecf::save("test_day_migration_free_expired_date",def_free_expired_date);
   }
   {
	   version_old::DayAttr def = version_old::DayAttr();
	   ecf::restore("test_day_migration_def",def);
	   BOOST_CHECK_MESSAGE(def == version_old::DayAttr(),"Should be the same");

	   version_old::DayAttr def_free = version_old::DayAttr();
	   ecf::restore("test_day_migration_free",def_free);
	   version_old::DayAttr cdef_free; cdef_free.set_free();
	   BOOST_CHECK_MESSAGE(def_free == cdef_free,"Should be the same");

	   version_old::DayAttr def_expired = version_old::DayAttr();
	   ecf::restore("test_day_migration_expired",def_expired);
	   BOOST_CHECK_MESSAGE(def_expired == version_old::DayAttr(), "No expired, should be same as default");

	   version_old::DayAttr def_free_and_expired = version_old::DayAttr();
	   ecf::restore("test_day_migration_free_and_expired",def_free_and_expired);
	   BOOST_CHECK_MESSAGE(def_free_and_expired == cdef_free,"No expired,But free should be set,Should be the same");

	   version_old::DayAttr def_free_expired_date = version_old::DayAttr();
	   ecf::restore("test_day_migration_free_expired_date",def_free_expired_date);
	   BOOST_CHECK_MESSAGE(def_free_and_expired == cdef_free,"No expired,No date,But free should be set,Should be the same");
   }

   // remove the generated filea, comment out to debug.
   boost::filesystem::remove("test_day_migration");
   boost::filesystem::remove("test_day_migration_def");
   boost::filesystem::remove("test_day_migration_free");
   boost::filesystem::remove("test_day_migration_expired");
   boost::filesystem::remove("test_day_migration_free_and_expired");
   boost::filesystem::remove("test_day_migration_free_expired_date");
}

BOOST_AUTO_TEST_SUITE_END()
