Index: trunk/FACT++/src/scheduler.cc
===================================================================
--- trunk/FACT++/src/scheduler.cc	(revision 19437)
+++ trunk/FACT++/src/scheduler.cc	(revision 19438)
@@ -1,755 +1,1098 @@
-#include <vector>
-
-#include <boost/regex.hpp>
-
-#include <mysql++/mysql++.h>
+#include "Prediction.h"
+
+#include <boost/algorithm/string/join.hpp>
+
+#include "Database.h"
+
+#include "HeadersToO.h"
+#include "HeadersGCN.h"
+
+#include "EventImp.h"
+#include "LocalControl.h"
+#include "StateMachineDim.h"
 
 #include "Dim.h"
+#include "tools.h"
 #include "Time.h"
-#include "Event.h"
-#include "Connection.h"
-#include "LocalControl.h"
 #include "Configuration.h"
-#include "StateMachineDim.h"
-
-#include "tools.h"
 
 using namespace std;
-using namespace boost::gregorian;
-using namespace boost::posix_time;
-
-// things to be done/checked/changed
-// * --schedule-database should be required
-// * move definition of config parameters to AutoScheduler class
-//   + read in from config
-// * in some (all?) loops iterator over vector can be replaced by counter
-
-// other things to do
-//
-// define what to transmit as info/warn/error
-
-
-// config parameters:
-//   mintime
-//   runtimec
-//   runtimep
-//   repostime
-
-// missing:
-//
-// calculate time for std obs
-// calculate sun set/rise
-//
-// return errors and other otherput from sendcommand to webinterface
-
-// in which cases should the scheduler go in error state?
-//   when db is unavailable
-// does one also need a 'set scheduler to ready' function then?
-// do we want any error state at all?
-
-
-// =========================================================================
-
-template <class T>
-class AutoScheduler : public T
+using namespace Nova;
+
+// -----------------------------------------------------------------------
+
+/*
+void SetupConfiguration(Configuration &conf);
 {
-    bool fNextIsPreview;
-public:
-    enum states_t
-    {
-        kSM_Scheduling=1,
-        kSM_Comitting,
-    };
-
-    struct ObservationParameters
-    {
-        int obskey;
-        int obsmode;
-        int obstype;
-        int splitflag;
-        int telsetup;
-        float fluxweight;
-        float slope;
-        float flux;
-        float ra;
-        float dec;
-        ptime start;
-        ptime stop;
-        time_duration duration_db;
-        string sourcename;
-        int sourcekey;
-    };
-
-    struct FixedObs
-    {
-        int obskey;
-        int sourcekey;
-        string sourcename;
-        int obsmode;
-        int obstype;
-        int telsetup;
-        float ra;
-        float dec;
-        ptime start;
-        ptime stop;
-    };
-
-    // will need other types of obs
-    // FloatingObs (duration < stop-start + splitflag no)
-    // FloatingSplittedObs (duration < stop-start + splitflag yes)
-    // FixedSlot, i.e. just block a time slot
-
-    struct StdObs
-    {
-        int obskey_std;
-        int sourcekey_std;
-        string sourcename_std;
-        int obsmode_std;
-        int obstype_std;
-        int telsetup_std;
-        float fluxweight_std;
-        float slope_std;
-        float flux_std;
-        float ra_std;
-        float dec_std;
-        ptime obsstdstart;
-        ptime obsstdstop;
-    };
-
-    struct ScheduledObs
-    {
-        int obskey_obs;
-        int sourcekey_obs;
-        string sourcename_obs;
-        int obsmode_obs;
-        int obstype_obs;
-        int telsetup_obs;
-        ptime obsstart;
-        ptime obsstop;
-    };
-
-    struct ScheduledRun
-    {
-        //int runnumber; // to be seen, if runnumber is needed
-        int obskey_run;
-        int runtype;
-        int sourcekey_run;
-        string sourcename_run;//for convenience
-        int obsmode_run;
-        int obstype_run;
-        int telsetup_run;
-        ptime runstart;
-        ptime runstop;
-    };
-
-    string fDatabase;
-    string fDBName;
-    int fDurationCalRun; //unit: minutes
-    int fDurationPedRun; //unit: minutes
-    int fDurationRepos; //unit: minutes
-
-    int Schedule()
-    {
-        bool error = false;
-
-        time_duration runtimec(0, fDurationCalRun, 0);
-        time_duration runtimep(0, fDurationPedRun, 0);
-        time_duration repostime(0, fDurationRepos, 0);
-        time_duration mintime(1, 0, 0);
-
-        const ptime startsched(microsec_clock::local_time());
-        const ptime stopsched=startsched+years(1);
-
-        ostringstream str;
-        str << "Scheduling for the period from " << startsched << " to " << stopsched;
-        T::Message(str);
-
-        static const boost::regex expr("([[:word:].-]+):(.+)@([[:word:].-]+)(:([[:digit:]]+))?/([[:word:].-]+)");
-        // 2: user
-        // 4: pass
-        // 5: server
-        // 7: port
-        // 9: db
-
-        boost::smatch what;
-        if (!boost::regex_match(fDatabase, what, expr, boost::match_extra))
-        {
-            ostringstream msg;
-            msg << "Regex to parse database '" << fDatabase << "' empty.";
-            T::Error(msg);
-            return T::kSM_Error;
-        }
-
-        if (what.size()!=7)
-        {
-            ostringstream msg;
-            msg << "Parsing database name failed: '" << fDatabase << "'";
-            T::Error(msg);
-            return T::kSM_Error;
-        }
-
-        const string user   = what[1];
-        const string passwd = what[2];
-        const string server = what[3];
-        const string db     = fDBName.empty() ? what[6] : fDBName;
-        const int    port   = stoi(what[5]);
-
-        ostringstream dbnamemsg;
-        dbnamemsg << "Scheduling started -> using database " << db << ".";
-        T::Message(dbnamemsg);
-
-        str.str("");
-        str << "Connecting to '";
-        if (!user.empty())
-            str << user << "@";
-        str << server;
-        if (port)
-            str << ":" << port;
-        if (!db.empty())
-            str << "/" << db;
-        str << "'";
-        T::Info(str);
-
-        mysqlpp::Connection conn(db.c_str(), server.c_str(), user.c_str(), passwd.c_str(), port);
-        /* throws exceptions
-        if (!conn.connected())
-        {
-            ostringstream msg;
-            msg << "MySQL connection error: " << conn.error();
-            T::Error(msg);
-            return T::kSM_Error;
-        }*/
-
-        // get observation parameters from DB
-        // maybe order by priority?
-        const mysqlpp::StoreQueryResult res =
-            conn.query("SELECT fObservationKEY, fStartTime, fStopTime, fDuration, fSourceName, fSourceKEY, fSplitFlag, fFluxWeight, fSlope, fFlux, fRightAscension, fDeclination, fObservationModeKEY, fObservationTypeKEY , fTelescopeSetupKEY FROM ObservationParameters LEFT JOIN Source USING(fSourceKEY) ORDER BY fStartTime").store();
-        // FIXME: Maybe we have to check for a successfull
-        //        query but an empty result
-        /* thorws exceptions?
-        if (!res)
-        {
-            ostringstream msg;
-            msg << "MySQL query failed: " << query.error();
-            T::Error(msg);
-            return T::kSM_Error;
-        }*/
-
-        str.str("");
-        str << "Found " << res.num_rows() << " Observation Parameter sets.";
-        T::Debug(str);
-
-        ObservationParameters olist[res.num_rows()];
-        vector<FixedObs>     obsfixedlist;
-        vector<StdObs>       obsstdlist;
-        vector<ScheduledObs> obslist;
-        vector<ScheduledRun> runlist;
-
-        // loop over observation parameters from DB
-        // fill these parameters into FixedObs and StdObs
-        int counter=0;
-        int counter2=0;
-        int counter3=0;
-        cout << "Obs: <obskey> <sourcename>(<sourcekey>, <fluxweight>) from <starttime> to <stoptime>" << endl;
-        for (vector<mysqlpp::Row>::const_iterator v=res.begin(); v<res.end(); v++)
-        {
-            cout << "  Obs: " << (*v)[0].c_str() << " " << (*v)[4].c_str() << "(" << (*v)[5].c_str() << flush;
-            cout << ", " << (*v)[7].c_str() << ")" << flush;
-            cout << " from " << (*v)[1].c_str() << " to " << (*v)[2].c_str() << endl;
-
-            //0: obskey
-            //1: startime
-            //2: stoptime
-            //3: duration
-            //4: sourcename
-            //5: sourcekey
-            //6: splitflag
-            //7: fluxweight
-            //8: slope
-            //9: flux
-            //10: ra
-            //11: dec
-            //12: obsmode
-            //13: obstype
-            //14: telsetup
-            stringstream t1;
-            stringstream t2;
-            stringstream t3;
-            t1 << (*v)[1].c_str();
-            t2 << (*v)[2].c_str();
-            t3 << (*v)[3].c_str();
-
-            //boost::posix_time::time_duration mintime(0,conf.Get<int>("mintime"), 0);
-            t1 >> Time::sql >> olist[counter].start;
-            t2 >> Time::sql >> olist[counter].stop;
-            t3 >> olist[counter].duration_db;
-            const time_period period(olist[counter].start, olist[counter].stop);
-
-            olist[counter].sourcename=(*v)[4].c_str();
-            olist[counter].sourcekey=(*v)[5];
-
-            if (!(*v)[0].is_null())
-                olist[counter].obskey=(*v)[0];
-            if (!(*v)[12].is_null())
-                olist[counter].obsmode=(*v)[12];
-            if (!(*v)[13].is_null())
-                olist[counter].obstype=(*v)[13];
-            if (!(*v)[14].is_null())
-                olist[counter].telsetup=(*v)[14];
-            if (!(*v)[6].is_null())
-                olist[counter].splitflag=(*v)[6];
-            if (!(*v)[7].is_null())
-                olist[counter].fluxweight=(*v)[7];
+    po::options_description control("Makeschedule");
+    control.add_options()
+        ("uri", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
+        ;
+
+    // po::positional_options_description p;
+    // p.add("date", 1); // The first positional options
+
+    conf.AddOptions(control);
+    //conf.SetArgumentPositions(p);
+}
+
+void PrintUsage();
+{
+    cout <<
+        "makeschedule - Creates an automatic schedule\n"
+        "\n"
+        "Usage: makeschedule [yyyy-mm-dd]\n";
+    cout << endl;
+}
+
+void PrintHelp();
+{
+    cout << endl;
+}*/
+
+struct CheckVisibility
+{
+    // Input
+    float moon_min     =  10;
+    float moon_max     = 170;
+    float sun_max      = -12;
+    float zd_max       =  75;
+    float current_max  = 110;
+
+    // Output
+    Nova::SolarObjects solarobj;
+    Nova::ZdAzPosn position;
+
+    double moon_dist   = -1;
+    double current     = -1;
+
+    bool valid_zd      = false;
+    bool valid_current = false;
+    bool valid_sun     = false;
+    bool valid_moon    = false;
+
+    bool visible       = false;
+
+    void calc(const Nova::EquPosn &equ, const double &jd)
+    {
+        solarobj  = Nova::SolarObjects(jd);
+        position  = Nova::GetHrzFromEqu(equ, jd);
+        moon_dist = Nova::GetAngularSeparation(equ, solarobj.fMoonEqu);
+
+        current   = FACT::PredictI(solarobj, equ);
+
+        valid_moon    = moon_dist>moon_min && moon_dist<moon_max;
+        valid_zd      = position.zd<zd_max;
+        valid_sun     = solarobj.fSunHrz.alt<sun_max;
+        valid_current = current<current_max;
+
+        visible = valid_moon && valid_zd && valid_sun && valid_current;
+    }
+
+    CheckVisibility(const Nova::EquPosn &equ, const double &jd)
+    {
+        calc(equ, jd);
+    }
+
+    CheckVisibility(const Nova::EquPosn &equ)
+    {
+        calc(equ, Time().JD());
+    }
+
+    CheckVisibility(const double &ra, const double &dec, const double &jd)
+    {
+        Nova::EquPosn equ;
+        equ.ra  = ra;
+        equ.dec = dec;
+        calc(equ, jd);
+    }
+
+    CheckVisibility(const double &ra, const double &dec)
+    {
+        Nova::EquPosn equ;
+        equ.ra  = ra;
+        equ.dec = dec;
+        calc(equ, Time().JD());
+    }
+};
+
+/*
+
+ * Visibility check
+ * Source eintragen
+ * RELOAD SOURCES
+ * Schedule eintragen
+ * Send 'RESCHEDULE' interrupt
+
+*/
+/*
+int mainx(int argc, const char* argv[])
+{
+    Configuration conf(argv[0]);
+    conf.SetPrintUsage(PrintUsage);
+    SetupConfiguration(conf);
+
+    if (!conf.DoParse(argc, argv, PrintHelp))
+        return 127;
+
+    // ------------------ Eval config ---------------------
+
+    const string uri = conf.Get<string>("schedule-database");
+
+    const uint16_t verbose = 1;
+    const bool dry_run = true;
+
+    Time stopwatch;
+
+    Database connection(uri); // Keep alive while fetching rows
+
+    cout << Time()-stopwatch << endl;
+
+    if (verbose>0)
+        cout << "Server Version: " << connection.server_version() << endl;
+
+    const int source_key = 999;      // source_key of the source to be scheduled.
+
+    const uint16_t contingency =   0; // The contingency to delete earlier entries from the Table
+    const uint16_t duration    = 300; // The target duration of the ToO
+    const uint16_t required    =  20; // If the ToO would be less than required, skip_it
+    const uint16_t skip_time   =  20; // If following observation would be less than skip_time, skip it
+
+    if (duration<required)
+    {
+        cerr << "Requested duration is smaller than required time." << endl;
+        return 0;
+    }
+
+    Time day(2019, 1, 24, 17,  0,  0);
+    Time now(2019, 1, 24, 19, 59,  0);
+    Time next    = day+boost::posix_time::hours(14);
+    Time restart = now+boost::posix_time::minutes(duration);
+
+    // Safety margin
+    now -= boost::posix_time::seconds(contingency);
+
+    cout << '\n';
+    cout << "Contingency:   " << contingency << " s\n";
+    cout << "Start of ToO:  " << now.GetAsStr() << '\n';
+    cout << "End   of ToO:  " << restart.GetAsStr() << '\n';
+    cout << "Duration:      " << duration << "  min\n\n";
+
+    cout << "Schedule:\n";
+
+    const string queryS =
+        "SELECT *,"
+        " (fMeasurementTypeKey=4 AND fStart<='"+restart.GetAsStr()+"') AS `Reschedule`,"
+        " (fStart>'"+restart.GetAsStr()+"') AS `Following`,"
+        " (fMeasurementTypeKey=4 AND fStart BETWEEN '"+now.GetAsStr()+"' AND '"+restart.GetAsStr()+"') AS `Delete`"
+        " FROM Schedule"
+        " WHERE fStart BETWEEN '"+day.GetAsStr()+"' AND '"+next.GetAsStr()+"'"
+        " AND fMeasurementID=0"
+        " ORDER BY fStart ASC, fMeasurementID ASC";
+
+    const mysqlpp::StoreQueryResult resS =
+        connection.query(queryS).store();
+
+    vector<string> list;
+
+    int32_t firstdata  = -1;
+    int32_t reschedule = -1;
+    int32_t following  = -1;
+    for (size_t i=0; i<resS.num_rows(); i++)
+    {
+        const mysqlpp::Row &row = resS[i];
+
+        cout << setw(2) << i << " | ";
+        cout << row["Reschedule"]     << " | ";
+        cout << row["Following"]      << " | ";
+        cout << row["fScheduleID"]    << " | ";
+        cout << row["fStart"]         << " | ";
+        if (bool(row["Delete"]))
+        {
+            cout << "     < delete >     | ";
+            list.push_back((string)row["fScheduleID"]);
+        }
+        else
+            cout << row["fLastUpdate"]    << " | ";
+        cout << row["fMeasurementID"] << " | ";
+        cout << row["fUser"]          << " | ";
+        cout << row["fData"]          << " | " << setw(4);
+        cout << row["fSourceKey"]     << " | ";
+        cout << row["fMeasurementTypeKey"] << '\n';
+
+        if (bool(row["Reschedule"]))
+            reschedule = i;
+
+        if (following==-1 && bool(row["Following"]))
+            following = i;
+
+        uint16_t type = row["fMeasurementTypeKey"];
+        if (firstdata==-1 && type==4)
+            firstdata = i;
+    }
+
+    // --------------------------------------------------------------------
+
+    if (firstdata==-1)
+    {
+        cerr << "No data run found." << endl;
+        return 0;
+    }
+
+    // --------------------------------------------------------------------
+
+    if (reschedule>=0)
+        cout << "\nLast data run before restart at " << restart.GetAsStr() << ": idx=" << reschedule << " / ID=" << resS[reschedule]["fScheduleID"] << "\n";
+
+    // --------------------------------------------------------------------
+
+    const Time first((string)resS[firstdata]["fStart"]);          // First data run of the night
+    const Time last( (string)resS[resS.num_rows()-1]["fStart"]);  // Last scheduled observation of the night
+
+    // --------------------------------------------------------------------
+
+    if (restart<=first)
+    {
+        cout << "ToO ends before first data taking... skipped." << endl;
+        return 0;
+    }
+    if (now>=last)
+    {
+        cout << "ToO starts after data taking... skipped." << endl;
+        return 0;
+    }
+
+    // --------------------------------------------------------------------
+
+    if (now<first)
+    {
+        cout << "ToO starts before first data taking... rescheduling start time." << endl;
+        now = first;
+    }
+    if (restart>last)
+    {
+        cout << "ToO ends after data taking... rescheduling end time!" << endl;
+        restart = last;
+    }
+
+    // --------------------------------------------------------------------
+
+    if (restart<now+boost::posix_time::minutes(required))
+    {
+        cout << "Can not schedule more than " << required << "minutes... skipped." << endl;
+        return 0;
+    }
+
+    // --------------------------------------------------------------------
+
+    if (following>=0)
+    {
+        const Time follow((string)resS[following]["fStart"]);
+        if (follow<restart+boost::posix_time::minutes(skip_time))
+        {
+            cout << "Following observation would be less than " << skip_time << " min... skipping rescheduled source." << endl;
+            reschedule = -1;
+        }
+    }
+
+    // ====================================================================
+
+    if (!list.empty())
+    {
+        cout << "\nDelete entries:\n";
+        for (const auto &l : list)
+            cout << l << "\n";
+        cout << endl;
+    }
+
+    // ====================================================================
+
+    vector<string> insert;
+
+    cout << "New entries:\n";
+    cout << "       | auto  | " << now.GetAsStr()     << " |       < start >     | 0 | ToO  | NULL |      | 4\n";
+
+    insert.emplace_back("('"+now.GetAsStr()+"',0,'ToO',NULL,"+to_string(source_key)+",4)");
+
+    // --------------------------------------------------------------------
+
+    if (reschedule>=0 && restart!=last)
+    {
+        cout << "       | auto  | " << restart.GetAsStr() << " |        < end >      | 0 | ToO  | NULL | " << setw(4) << resS[reschedule]["fSourceKey"] << " | 4\n";
+
+        const string fData = (string)resS[reschedule]["fData"];
+        const string fKey  = (string)resS[reschedule]["fSourceKey"];
+
+        const string data  = resS[reschedule]["fData"] ? "'"+fData+"'" : "NULL";
+        insert.emplace_back("('"+restart.GetAsStr()+"',0,'ToO',"+data+","+fKey+",4)");
+    }
+
+    // ====================================================================
+
+    cout << Time()-stopwatch << endl;
+
+    // ====================================================================
+
+    if (insert.empty())
+    {
+        cout << "No new schedule." << endl;
+        return 0;
+    }
+
+    const string queryD = string("DELETE FROM Schedule WHERE fScheduleID IN (")+boost::algorithm::join(list, ",")+")";
+
+    const string queryI =
+        "INSERT (fStart, fMeasurementID, fUser, fData, fSoureKey, fMeasurementTypeKey) "
+        "VALUES\n "+string(boost::algorithm::join(insert, ",\n "));
+
+    if (dry_run)
+    {
+        cout << "\n";
+        if (!list.empty())
+            cout << queryD << "\n\n";
+        if (!insert.empty())
+            cout << queryI << "\n\n";
+        cout << flush;
+        return 0;
+    }
+
+    // Empty interrupt to stop data taking as early as possible
+    Dim::SendCommandNB("DIM_CONTROL/INTERRUPT");
+
+    // Reload sources as early as possible
+    Dim::SendCommandNB("DRIVE_CONTROL/RELOAD_SOURCES");
+
+    // Start pointing procedure as early as possible
+    //Dim::SendCommand("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source);
+
+    try
+    {
+        // ---------------------------------------------------------
+
+        connection.query("LOCK TABLES Schedule WRITE");
+
+        // ---------------------------------------------------------
+
+        if (!list.empty())
+        {
+            const mysqlpp::SimpleResult res = connection.query(queryD).execute();
+            cout << res.rows() << " row(s) deleted.\n" << endl;
+        }
+
+        // ---------------------------------------------------------
+
+        if (!insert.empty())
+        {
+            auto q = connection.query(queryI);
+            q.execute();
+            cout << q.info() << '\n' << endl;
+        }
+
+        // ---------------------------------------------------------
+
+        connection.query("UNLOCK TABLES");
+
+        // ---------------------------------------------------------
+    }
+    catch (const exception &e)
+    {
+        cerr << "SQL query failed: " << e.what() << endl;
+        return 7;
+    }
+
+    Dim::SendCommand("DIM_CONTROL/INTERRUPT", "reschedule");
+
+    return 0;
+}
+*/
+// ------------------------------------------------------------------------
+
+class StateMachineToO : public StateMachineDim
+{
+private:
+    string   fUri;
+    Database fConnection;
+    uint16_t fVerbose;
+    bool     fDryRun;
+
+    Time     fKeepAliveDeadline;
+    uint16_t fKeepAliveInterval;
+
+    bool ScheduleImp(const string &name, const double &ra, const double &dec)
+    {
+        fDryRun = true;
+
+        Time stopwatch;
+
+        // This is just a desperate try which will most likely fail
+        if (!fConnection.connected())
+            fConnection.reconnect();
+
+        /*
+         +-----------------+---------------------+------+-----+---------+----------------+
+         | Field           | Type                | Null | Key | Default | Extra          |
+         +-----------------+---------------------+------+-----+---------+----------------+
+         | fSourceKEY      | int(11)             | NO   | PRI | NULL    | auto_increment |
+         | fSourceName     | varchar(30)         | NO   |     | NULL    |                |
+         | fRightAscension | double(10,6)        | NO   |     | NULL    |                |
+         | fDeclination    | double(10,6)        | NO   |     | NULL    |                |
+         | fEpochKEY       | tinyint(4)          | YES  |     | NULL    |                |
+         | fFlux           | float               | YES  |     | NULL    |                |
+         | fSlope          | float               | YES  |     | NULL    |                |
+         | fSourceTypeKey  | int(11)             | NO   | MUL | NULL    |                |
+         | fWobbleOffset   | float               | NO   |     | 0.6     |                |
+         | fWobbleAngle0   | int(11)             | NO   |     | 90      |                |
+         | fWobbleAngle1   | int(11)             | NO   |     | -90     |                |
+         | fMagnitude      | float(4,2)          | YES  |     | NULL    |                |
+         | fIsToO          | tinyint(3) unsigned | NO   |     | 0       |                |
+         +-----------------+---------------------+------+-----+---------+----------------+
+         */
+
+        const double min_dist = 0.1;
+        const double wobble_offset = 0.6;
+        const double camera_radius = 2.3;
+        const double magnitude_max = 4.5;
+
+        double wobble_angle  = 0;
+
+        /*
+         Mag Cnt
+         -2  1
+         -1  3
+          0  11
+          1  33
+          2  121
+          3  321
+          4  871
+          5  1364
+          6  404
+          7  12
+        */
+
+        // The wobble position lay in the plane whihc is normal the to the plane source-center-star
+        // and goes through
+
+        const string query0 =
+            "SELECT fSourceKEY, fRightAscension, fDeclination, fMagnitude,\n"
+            " ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+") AS Dist\n"
+            " FROM Source\n"
+            " WHERE (fSourceTypeKey=2 OR fSourceTypeKey=3) AND fMagnitude<"+to_string(magnitude_max)+"\n"
+            " HAVING Dist<"+to_string(camera_radius+wobble_offset)+"\n"
+            " ORDER BY fMagnitude ASC";
+
+        Out() << query0 << endl;
+
+        const mysqlpp::StoreQueryResult res0 = fConnection.query(query0).store();
+
+        Out() << Time()-stopwatch << endl;
+
+        Info("Found "+to_string(res0.num_rows())+" stars in the camera field-of-view with magnitude less than "+to_string(magnitude_max));
+
+        for (size_t i=0; i<::min<size_t>(10, res0.num_rows()); i++)
+        {
+            const mysqlpp::Row &row = res0[i];
+
+            // TVector3 souce, star;
+            // source.SetMagThetaPhi(1, rad(90-dec), rad(ra));
+            // star.SetMagThetaPhi(  1, rad(90-dec), rad(ra));
+            // 
+            // star.RotateZ(  -source.Phi());
+            // source.RotateZ(-source.Phi());
+            // 
+            // star.RotateY(  180-source.Theta());
+            // source.RotateY(180-source.Theta());
+            // 
+            // rho = star.Phi();
+
+            const double rad = M_PI/180;
+
+            const double dec0 = rad * dec;
+            const double ra0  = rad * ra;
+
+            const double dec1 = rad * row["fDeclination"];
+            const double ra1  = rad * row["fRightAscension"] * 15;
+
+            const double s2 = sin(ra0-ra1);
+            const double c2 = cos(ra0-ra1);
+
+            const double s0 = cos(dec0);
+            const double c0 = sin(dec0);
+
+            const double c1 = cos(dec1);
+            const double s1 = sin(dec1);
+
+            const double k0 =           -s2*c1;
+            const double k1 = s0*s1 - c0*c2*c1;
+
+            const double rho = atan(k0/k1) / rad; // atan2(k0, k1)/rad
+
+            if (i==0)
+                wobble_angle = rho;
+
+            Info(" "+Tools::Form("Mag=%5.2f  Dist=%5.1f  Phi=%6.1f", (double)row["fMagnitude"], (double)row["Dist"], rho));
+        }
+
+        if (res0.num_rows())
+        {
+            Info("The brightest star (M="+string(res0[0]["fMagnitude"])+") at distance is visible at a wobble angle of "+Tools::Form("%.2f", wobble_angle)+"\u00b0");
+            Info("Wobble angles determined as "+Tools::Form("%.2f", wobble_angle-90)+"\u00b0 and "+Tools::Form("%.2f", wobble_angle+90)+"\u000b");
+        }
+        else
+        {
+            Info("Using default wobble angles.");
+        }
+
+        const string query1 =
+            "SELECT fSourceKey, fSourceName\n"
+            " FROM Source\n"
+            " WHERE ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+")<"+to_string(min_dist)+"\n"
+            " ORDER BY fSourceKey ASC";
+
+        const mysqlpp::StoreQueryResult res1 = fConnection.query(query1).store();
+
+        if (res1.num_rows())
+        {
+            Warn("The following sources in the source table have a distance less than "+to_string(min_dist)+"\u00b0");
+
+            for (size_t i=0; i<res1.num_rows(); i++)
+            {
+                const mysqlpp::Row &row = res1[i];
+                Warn(" "+string(row["fSourceName"])+" ["+to_string(uint32_t(row["fSourceKey"]))+"]");
+            }
+        }
+
+        Out() << Time()-stopwatch << endl;
+
+        int32_t source_key = -1;
+
+        const string query2 =
+            "SELECT fSourceKey FROM Source WHERE fSourceName='"+name+"'";
+
+        const mysqlpp::StoreQueryResult res2 = fConnection.query(query2).store();
+
+        if (res2.num_rows())
+        {
+            source_key = res2[0]["fSourceKey"];
+            Info("A source with the same name (key="+to_string(source_key)+") was found in the Source table.");
+        }
+
+        if (source_key<0)
+        {
+            const string query =
+                "INSERT INTO Source\n"
+                " (fSourceName, fRightAscension, fDeclination, fWobbleAngle0, fWobbleAngle1, fSourceTypeKey, fIsToO) VALUES\n"
+                " ('"+name+"', "+to_string(ra/15.)+", "+to_string(dec)+", "+to_string(wobble_angle-90)+", "+to_string(wobble_angle+90)+", 1, 1)";
+
+            if (!fDryRun)
+            {
+                auto q = fConnection.query(query);
+                q.execute();
+                Info(q.info());
+
+                source_key = q.insert_id();
+
+                Info(string(fDryRun?"[dry-run] ":"")+"The new source got the key "+to_string(source_key));
+            }
             else
-                olist[counter].fluxweight=0;//set fluxweight to 0 for check below
-            if (!(*v)[8].is_null())
-                olist[counter].slope=(*v)[8];
-            if (!(*v)[9].is_null())
-                olist[counter].flux=(*v)[9];
-            if (!(*v)[10].is_null())
-                olist[counter].ra=(*v)[10];
-            if (!(*v)[11].is_null())
-                olist[counter].dec=(*v)[11];
-
-            // time_duration cannot be used, as only up to 99 hours are handeled
-            // const time_duration duration = period.length();
-
-            /*
-            if (olist[counter].stoptime < olist[counter].starttime+mintime)
-                cout << "  ====> WARN: Observation too short. " << endl;
-
-            if (olist[counter].starttime.is_not_a_date_time())
-                cout << "  WARN: starttime not a date_time. " << endl;
+                Out() << query << endl;
+
+        }
+
+        Out() << Time()-stopwatch << endl;
+
+        /*
+         +---------------------+--------------+------+-----+-------------------+-----------------------------+
+         | Field               | Type         | Null | Key | Default           | Extra                       |
+         +---------------------+--------------+------+-----+-------------------+-----------------------------+
+         | fScheduleID         | int(11)      | NO   | PRI | NULL              | auto_increment              |
+         | fStart              | datetime     | NO   | MUL | NULL              |                             |
+         | fLastUpdate         | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+         | fMeasurementID      | int(11)      | NO   |     | NULL              |                             |
+         | fUser               | varchar(32)  | NO   |     | NULL              |                             |
+         | fData               | varchar(256) | YES  |     | NULL              |                             |
+         | fSourceKey          | smallint(6)  | YES  |     | NULL              |                             |
+         | fMeasurementTypeKey | tinyint(4)   | NO   |     | NULL              |                             |
+         +---------------------+--------------+------+-----+-------------------+-----------------------------+
+         */
+
+        const uint16_t contingency =   1; // The contingency to delete earlier entries from the Table
+        const uint16_t duration    =  60; // The target duration of the ToO
+        const uint16_t required    =  20; // If the ToO would be less than required, skip_it
+        const uint16_t skip_time   =  20; // If following observation would be less than skip_time, skip it
+
+        if (duration<required)
+        {
+            Error("Requested duration is smaller than required time.");
+            return false;
+        }
+
+        const Time now;
+
+        const Time sunset  = now.GetPrevSunSet();
+        const Time sunrise = now.GetNextSunRise();
+
+        if (sunset>sunrise || sunrise>sunset+boost::posix_time::hours(24))
+        {
+            Error("Unable to schedule a ToO during daytime.");
+            return false;
+        }
+
+        // Safety margin
+        Time schedtime = now-boost::posix_time::seconds(contingency);
+        Time restart   = now+boost::posix_time::minutes(duration);
+
+        Out() << '\n';
+        Out() << "Contingency:   " << contingency << " s\n";
+        Out() << "Start of ToO:  " << now.GetAsStr() << '\n';
+        Out() << "End   of ToO:  " << restart.GetAsStr() << '\n';
+        Out() << "Duration:      " << duration << " min\n\n";
+
+        Out() << "Schedule:\n";
+
+        const string queryS =
+            "SELECT *,\n"
+            " (fMeasurementTypeKey=4 AND fStart<='"+restart.GetAsStr()+"') AS `Reschedule`,\n"
+            " (fStart>'"+restart.GetAsStr()+"') AS `Following`,\n"
+            " (fMeasurementTypeKey=4 AND fStart BETWEEN '"+schedtime.GetAsStr()+"' AND '"+restart.GetAsStr()+"') AS `Delete`\n"
+            " FROM Schedule\n"
+            " WHERE fStart BETWEEN '"+sunset.GetAsStr()+"' AND '"+sunrise.GetAsStr()+"'\n"
+            " AND fMeasurementID=0\n"
+            " ORDER BY fStart ASC, fMeasurementID ASC";
+
+        const mysqlpp::StoreQueryResult resS = fConnection.query(queryS).store();
+
+        if (resS.num_rows()<2)
+        {
+            Error("Available schedule is too short to be evaluated.");
+            return false;
+        }
+
+        vector<string> list;
+
+        int32_t firstdata  = -1;
+        int32_t reschedule = -1;
+        int32_t following  = -1;
+        for (size_t i=0; i<resS.num_rows(); i++)
+        {
+            const mysqlpp::Row &row = resS[i];
+
+            Out() << setw(2) << i << " | ";
+            Out() << row["Reschedule"]     << " | ";
+            Out() << row["Following"]      << " | ";
+            Out() << row["fScheduleID"]    << " | ";
+            Out() << row["fStart"]         << " | ";
+            if (bool(row["Delete"]))
+            {
+                Out() << "     < delete >     | ";
+                list.push_back((string)row["fScheduleID"]);
+            }
             else
-                cout << "  start:   " << Time::sql << olist[counter].starttime << endl;
-            if (olist[counter].stoptime.is_not_a_date_time())
-                cout << "  WARN: stoptime not a date_time. " << endl;
-            else
-                cout << "  stop:   " << Time::sql << olist[counter].stoptime << endl;
-            if (!(olist[counter].starttime.is_not_a_date_time() || olist[counter].stoptime.is_not_a_date_time()))
-                cout << "  diff:   " << period << endl;
-            if (olist[counter].stoptime < olist[counter].starttime)
-                cout << "  ====> WARN: stop time (" << olist[counter].stoptime << ") < start time (" << olist[counter].starttime << "). " << endl;
-            cout << "diff:   " << duration << flush;
-            cout << "dur_db:   " << olist[counter].duration_db << endl;
-            */
-
-            // always filled: obstype
-            //
-            // fixed observations:
-            //   filled: starttime, stoptime
-            //   not filled: fluxweight
-            //   maybe filled: obsmode, telsetup, source (not filled for FixedSlotObs)
-            //   maybe filled: duration (filled for FloatingObs and FloatingSplittedObs)
-            //   maybe filled: splitflag (filled for FloatingSplittedObs)
-            //
-            // std observations:
-            //   filled: fluxweight, telsetup, obsmore, source
-            //   not filled: starttime, stoptime, duration
-
-            // fixed observations
-            if (!(olist[counter].stop.is_not_a_date_time()
-                  && olist[counter].start.is_not_a_date_time())
-                && olist[counter].fluxweight==0
-               )
+                Out() << row["fLastUpdate"]    << " | ";
+            Out() << row["fMeasurementID"] << " | ";
+            Out() << row["fUser"]          << " | ";
+            Out() << row["fData"]          << " | " << setw(4);
+            Out() << row["fSourceKey"]     << " | ";
+            Out() << row["fMeasurementTypeKey"] << '\n';
+
+            if (bool(row["Reschedule"]))
+                reschedule = i;
+
+            if (following==-1 && bool(row["Following"]))
+                following = i;
+
+            const uint16_t type = row["fMeasurementTypeKey"];
+            if (firstdata==-1 && type==4)
+                firstdata = i;
+        }
+
+        // --------------------------------------------------------------------
+
+        if (firstdata==-1)
+        {
+            Warn("No data run found.");
+            return false;
+        }
+
+        // --------------------------------------------------------------------
+
+        if (reschedule>=0)
+            Out() << "\nLast data run before restart at " << restart.GetAsStr() << ": idx=" << reschedule << " / ID=" << resS[reschedule]["fScheduleID"] << endl;
+
+        // --------------------------------------------------------------------
+
+        const Time first((string)resS[firstdata]["fStart"]);          // First data run of the night
+        const Time last( (string)resS[resS.num_rows()-1]["fStart"]);  // Last scheduled observation of the night
+
+        // --------------------------------------------------------------------
+
+        if (restart<=first)
+        {
+            Warn("ToO ends before first data taking... skipped.");
+            return false;
+        }
+        if (schedtime>=last)
+        {
+            Warn("ToO starts after data taking... skipped.");
+            return false;
+        }
+
+        // --------------------------------------------------------------------
+
+        if (schedtime<first)
+        {
+            Info("ToO starts before first data taking... rescheduling start time.");
+            schedtime = first;
+        }
+        if (restart>last)
+        {
+            Info("ToO ends after data taking... rescheduling end time!");
+            restart = last;
+        }
+
+        // --------------------------------------------------------------------
+
+        if (restart<schedtime+boost::posix_time::minutes(required))
+        {
+            Warn("Could not schedule more than the required "+to_string(required)+" minutes... skipped.");
+            return false;
+        }
+
+        // --------------------------------------------------------------------
+
+        if (following>=0)
+        {
+            const Time follow((string)resS[following]["fStart"]);
+            if (follow<restart+boost::posix_time::minutes(skip_time))
             {
-                obsfixedlist.resize(counter2+1);
-                obsfixedlist[counter2].start=olist[counter].start;
-                obsfixedlist[counter2].stop=olist[counter].stop;
-                obsfixedlist[counter2].sourcename=olist[counter].sourcename;
-                obsfixedlist[counter2].obskey=olist[counter].obskey;
-                obsfixedlist[counter2].obstype=olist[counter].obstype;
-                obsfixedlist[counter2].obsmode=olist[counter].obsmode;
-                obsfixedlist[counter2].telsetup=olist[counter].telsetup;
-                obsfixedlist[counter2].sourcekey=olist[counter].sourcekey;
-                obsfixedlist[counter2].ra=olist[counter].ra;
-                obsfixedlist[counter2].dec=olist[counter].dec;
-                counter2++;
+                Info("Following observation would be less than "+to_string(skip_time)+" min... skipping rescheduled source.");
+                reschedule = -1;
             }
-
-            // std obs
-            if (olist[counter].stop.is_not_a_date_time()
-                  && olist[counter].start.is_not_a_date_time()
-                && olist[counter].fluxweight>0
-               )
-            {
-                obsstdlist.resize(counter3+1);
-                obsstdlist[counter3].sourcename_std=olist[counter].sourcename;
-                obsstdlist[counter3].obskey_std=olist[counter].obskey;
-                obsstdlist[counter3].obsmode_std=olist[counter].obsmode;
-                obsstdlist[counter3].obstype_std=olist[counter].obstype;
-                obsstdlist[counter3].telsetup_std=olist[counter].telsetup;
-                obsstdlist[counter3].sourcekey_std=olist[counter].sourcekey;
-                obsstdlist[counter3].fluxweight_std=olist[counter].fluxweight;
-                obsstdlist[counter3].flux_std=olist[counter].flux;
-                obsstdlist[counter3].slope_std=olist[counter].slope;
-                obsstdlist[counter3].ra_std=olist[counter].ra;
-                obsstdlist[counter3].dec_std=olist[counter].dec;
-                counter3++;
-            }
-
-            counter++;
-        }
-        ostringstream fixedobsmsg;
-        fixedobsmsg << obsfixedlist.size() << " fixed observations found. ";
-        T::Message(fixedobsmsg);
-        cout << obsfixedlist.size() << " fixed observations found. " << endl;
-
-        ostringstream stdobsmsg;
-        stdobsmsg << obsstdlist.size() << " standard observations found. ";
-        T::Message(stdobsmsg);
-        cout << obsstdlist.size() << " standard observations found. " << endl;
-
-        // loop to add the fixed observations to the ScheduledObs list
-        // performed checks:
-        //   * overlap of fixed observations: the overlap is split half-half
-        //   * check for scheduling time range: only take into account fixed obs within the range
-        // missing checks and evaluation
-        //   * check for mintime (pb with overlap checks)
-        //   * check for sun
-        //   * check for moon
-        counter2=0;
-        int skipcounter=0;
-        ptime finalobsfixedstart;
-        ptime finalobsfixedstop;
-        time_duration delta0(0,0,0);
-
-        cout << "Fixed Observations: " << endl;
-        for (struct vector<FixedObs>::const_iterator vobs=obsfixedlist.begin(); vobs!=obsfixedlist.end(); vobs++)
-        {
-            if (obsfixedlist[counter2].start < startsched
-                || obsfixedlist[counter2].stop > stopsched)
-            {
-                ostringstream skipfixedobsmsg;
-                skipfixedobsmsg << "Skip 1 fixed observation (obskey ";
-                skipfixedobsmsg << obsfixedlist[counter2].obskey;
-                skipfixedobsmsg << ") as it is out of scheduling time range.";
-                T::Message(skipfixedobsmsg);
-
-                counter2++;
-                skipcounter++;
-                continue;
-            }
-            counter3=0;
-
-            time_duration delta1=delta0;
-            time_duration delta2=delta0;
-
-            finalobsfixedstart=obsfixedlist[counter2].start;
-            finalobsfixedstop=obsfixedlist[counter2].stop;
-
-            for (struct vector<FixedObs>::const_iterator vobs5=obsfixedlist.begin(); vobs5!=obsfixedlist.end(); vobs5++)
-            {
-                if (vobs5->start < obsfixedlist[counter2].stop
-                    && obsfixedlist[counter2].stop <= vobs5->stop
-                    && obsfixedlist[counter2].start <= vobs5->start
-                    && counter2!=counter3)
-                {
-                    delta1=(obsfixedlist[counter2].stop-vobs5->start)/2;
-                    finalobsfixedstop=obsfixedlist[counter2].stop-delta1;
-
-                    ostringstream warndelta1;
-                    warndelta1 << "Overlap between two fixed observations (";
-                    warndelta1 << obsfixedlist[counter2].obskey << " ";
-                    warndelta1 << vobs5->obskey << "). The stoptime of ";
-                    warndelta1 << obsfixedlist[counter2].obskey << " has been changed.";
-                    T::Warn(warndelta1);
-                }
-                if (vobs5->start <= obsfixedlist[counter2].start
-                    && obsfixedlist[counter2].start < vobs5->stop
-                    && obsfixedlist[counter2].stop >= vobs5->stop
-                    && counter2!=counter3)
-                {
-                    delta2=(vobs5->stop-obsfixedlist[counter2].start)/2;
-                    finalobsfixedstart=obsfixedlist[counter2].start+delta2;
-
-                    ostringstream warndelta2;
-                    warndelta2 << "Overlap between two fixed observations (";
-                    warndelta2 << obsfixedlist[counter2].obskey << " ";
-                    warndelta2 << vobs5->obskey << "). The starttime of ";
-                    warndelta2 << obsfixedlist[counter2].obskey << " has been changed.";
-
-                    T::Warn(warndelta2);
-                }
-                counter3++;
-            }
-
-            const int num=counter2-skipcounter;
-            obslist.resize(num+1);
-            obslist[num].obsstart=finalobsfixedstart;
-            obslist[num].obsstop=finalobsfixedstop;
-            obslist[num].sourcename_obs=obsfixedlist[counter2].sourcename;
-            obslist[num].obsmode_obs=obsfixedlist[counter2].obsmode;
-            obslist[num].obstype_obs=obsfixedlist[counter2].obstype;
-            obslist[num].telsetup_obs=obsfixedlist[counter2].telsetup;
-            obslist[num].sourcekey_obs=obsfixedlist[counter2].sourcekey;
-            obslist[num].obskey_obs=obsfixedlist[counter2].obskey;
-            counter2++;
-
-            cout << "  " << vobs->sourcename <<  " " << vobs->start;
-            cout << " - " << vobs->stop << endl;
-        }
-        ostringstream obsmsg;
-        obsmsg << "Added " << obslist.size() << " fixed observations to ScheduledObs. ";
-        T::Message(obsmsg);
-        cout << "Added " << obslist.size() << " fixed observations to ScheduledObs. " << endl;
-
-        for (int i=0; i<(int)obsstdlist.size(); i++)
-        {
-            for (int j=0; j<(int)obsstdlist.size(); j++)
-            {
-                if (obsstdlist[i].sourcekey_std == obsstdlist[j].sourcekey_std && i!=j)
-                {
-                    cout << "One double sourcekey in std observations: " << obsstdlist[j].sourcekey_std << endl;
-                    ostringstream errdoublestd;
-                    errdoublestd << "One double sourcekey in std observations: " << obsstdlist[j].sourcekey_std << " (" << obsstdlist[j].sourcename_std << ").";
-                    T::Error(errdoublestd);
-                    T::Message("Scheduling stopped.");
-                    return error ? T::kSM_Error : T::kSM_Ready;
-                }
-            }
-        }
-
-        // loop over nights
-        //   calculate sunset and sunrise
-        //   check if there is already scheduled obs in that night
-        //
-
-        // in this loop the standard observations shall be
-        // checked, evaluated
-        // the observation times shall be calculated
-        // and the observations added to the ScheduledObs list
-        cout << "Standard Observations: " << endl;
-        for (struct vector<StdObs>::const_iterator vobs2=obsstdlist.begin(); vobs2!=obsstdlist.end(); vobs2++)
-        {
-            cout << "  " << vobs2->sourcename_std << endl;
-        }
-
-        // in this loop the ScheduledRuns are filled
-        //  (only data runs -> no runtype yet)
-        // might be merged with next loop
-        counter2=0;
-        for (struct vector<ScheduledObs>::const_iterator vobs3=obslist.begin(); vobs3!=obslist.end(); vobs3++)
-        {
-            runlist.resize(counter2+1);
-            runlist[counter2].runstart=obslist[counter2].obsstart;
-            runlist[counter2].runstop=obslist[counter2].obsstop;
-            runlist[counter2].sourcename_run=obslist[counter2].sourcename_obs;
-            runlist[counter2].obsmode_run=obslist[counter2].obsmode_obs;
-            runlist[counter2].obstype_run=obslist[counter2].obstype_obs;
-            runlist[counter2].telsetup_run=obslist[counter2].telsetup_obs;
-            runlist[counter2].sourcekey_run=obslist[counter2].sourcekey_obs;
-            runlist[counter2].obskey_run=obslist[counter2].obskey_obs;
-            counter2++;
-            //cout << (*vobs3).sourcename_obs << endl;
-        }
-
-        //delete old scheduled runs from the DB
-        const mysqlpp::SimpleResult res0 =
-            conn.query("DELETE FROM ScheduledRun").execute();
-        // FIXME: Maybe we have to check for a successfull
-        //        query but an empty result
-        /* throws exceptions
-        if (!res0)
-        {
-            ostringstream msg;
-            msg << "MySQL query failed: " << query0.error();
-            T::Error(msg);
-            return T::kSM_Error;
-        }*/
-
-        // in this loop the ScheduledRuns are inserted to the DB
-        //   before the runtimes are adapted according to
-        //   duration of P-Run, C-Run and repositioning
-        counter3=0;
-        int insertcount=0;
-        ptime finalstarttime;
-        ptime finalstoptime;
-        for (struct vector<ScheduledRun>::const_iterator vobs4=runlist.begin(); vobs4!=runlist.end(); vobs4++)
-        {
-            for (int i=2; i<5; i++)
-            {
-                switch(i)
-                {
-                case 2:
-                    finalstarttime=runlist[counter3].runstart+repostime+runtimec+runtimep;
-                    finalstoptime=runlist[counter3].runstop;
-                    break;
-                case 3:
-                    finalstarttime=runlist[counter3].runstart+repostime;
-                    finalstoptime=runlist[counter3].runstart+runtimep+repostime;
-                    break;
-                case 4:
-                    finalstarttime=runlist[counter3].runstart+runtimep+repostime;
-                    finalstoptime=runlist[counter3].runstart+repostime+runtimep+runtimec;
-                    break;
-                }
-                ostringstream q1;
-                //cout << (*vobs4).sourcename_run << endl;
-                q1 << "INSERT ScheduledRun set fStartTime='" << Time::sql << finalstarttime;
-                q1 << "', fStopTime='" << Time::sql << finalstoptime;
-                q1 << "', fSourceKEY='" << (*vobs4).sourcekey_run;
-                q1 << "', fObservationKEY='" << (*vobs4).obskey_run;
-                q1 << "', fRunTypeKEY='" << i;
-                q1 << "', fTelescopeSetupKEY='" << (*vobs4).telsetup_run;
-                q1 << "', fObservationTypeKEY='" << (*vobs4).obstype_run;
-                q1 << "', fObservationModeKEY='" << (*vobs4).obsmode_run;
-                q1 << "'";
-
-                //cout << "executing query: " << q1.str() << endl;
-
-                const mysqlpp::SimpleResult res1 = conn.query(q1.str()).execute();
-                // FIXME: Maybe we have to check for a successfull
-                //        query but an empty result
-                /* throws exceptions
-                if (!res1)
-                {
-                    ostringstream msg;
-                    msg << "MySQL query failed: " << query1.error();
-                    T::Error(str);
-                    return T::kSM_Error;
-                }*/
-                insertcount++;
-            }
-            counter3++;
-        }
-        ostringstream insertmsg;
-        insertmsg << "Inserted " << insertcount << " runs into the DB.";
-        T::Message(insertmsg);
-        //usleep(3000000);
-        T::Message("Scheduling done.");
-
-        return error;
-    }
-
-    /*
-    // commit probably done by webinterface
-    int Commit()
-    {
-        ostringstream str;
-        str << "Comitting preview (id=" << fSessionId << ")";
-        T::Message(str);
-
-        usleep(3000000);
-        T::Message("Comitted.");
-
-        fSessionId = -1;
-
-        bool error = false;
-        return error ? T::kSM_Error : T::kSM_Ready;
-    }
-    */
-
-    AutoScheduler(ostream &out=cout) : T(out, "SCHEDULER"), fNextIsPreview(true), fDBName("")
-    {
-        AddStateName(kSM_Scheduling, "Scheduling", "Scheduling in progress.");
-
-        AddEvent(kSM_Scheduling, "SCHEDULE", "C", T::kSM_Ready)
-            ("FIXME FIXME FIXME (explanation for the command)"
-             "|database[string]:FIXME FIXME FIMXE (meaning and format)");
-
-        AddEvent(T::kSM_Ready, "RESET", T::kSM_Error)
-            ("Reset command to get out of the error state");
-
-        //AddEvent(kSM_Comitting,  "COMMIT",   T::kSM_Ready);
-
-        T::PrintListOfEvents();
+        }
+
+        // ====================================================================
+
+        if (!list.empty())
+        {
+            Out() << "\nDelete entries:\n";
+            for (const auto &l : list)
+                Out() << l << "\n";
+            Out() << endl;
+        }
+
+        // ====================================================================
+
+        Info("Source will be scheduled.");
+
+        vector<string> insert;
+
+        Out() << "New entries:\n";
+        Out() << "           | auto  | " << schedtime.GetAsStr()     << " |       < start >     | 0 | ToO  | nodrs,grb | " << setw(4) << source_key << " | 4\n";
+
+        insert.emplace_back("('"+schedtime.GetAsStr()+"',0,'ToO','nodrs:true,grb:true',"+to_string(source_key)+",4)");
+
+        // --------------------------------------------------------------------
+
+        if (reschedule>=0 && restart!=last)
+        {
+            Out() << "           | auto  | " << restart.GetAsStr() << " |        < end >      | 0 | ToO  |   NULL    | " << setw(4) << resS[reschedule]["fSourceKey"] << " | 4\n";
+
+            const string fData = (string)resS[reschedule]["fData"];
+            const string fKey  = (string)resS[reschedule]["fSourceKey"];
+
+            const string data  = resS[reschedule]["fData"] ? "'"+fData+"'" : "NULL";
+            insert.emplace_back("('"+restart.GetAsStr()+"',0,'ToO',"+data+","+fKey+",4)");
+        }
+
+        // ====================================================================
+
+        Out() << Time()-stopwatch << endl;
+
+        // ====================================================================
+
+        const string queryD = string("DELETE FROM Schedule WHERE fScheduleID IN (")+boost::algorithm::join(list, ",")+")";
+
+        const string queryI =
+            "INSERT INTO Schedule\n (fStart,fMeasurementID,fUser,fData,fSoureKey,fMeasurementTypeKey)\n"
+            "VALUES\n "+string(boost::algorithm::join(insert, ",\n "));
+
+        if (fDryRun)
+        {
+            Out() << "\n";
+            if (!list.empty())
+                Out() << queryD << "\n\n";
+            if (!insert.empty())
+                Out() << queryI << "\n\n";
+            Out() << flush;
+            return false;
+        }
+
+        // Empty interrupt to stop data taking as early as possible
+        Dim::SendCommandNB("DIM_CONTROL/INTERRUPT");
+
+        // Reload sources as early as possible
+        Dim::SendCommandNB("DRIVE_CONTROL/RELOAD_SOURCES");
+
+        // Start pointing procedure as early as possible
+        //Dim::SendCommand("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source);
+
+        // ---------------------------------------------------------
+
+        fConnection.query("LOCK TABLES Schedule WRITE");
+
+        // ---------------------------------------------------------
+        /*
+         if (!list.empty())
+         {
+         const mysqlpp::SimpleResult res = fConnection.query(queryD).execute();
+         Info(to_string(res.rows())+" row(s) deleted from Schedule.");
+         }
+
+         // ---------------------------------------------------------
+
+         if (!insert.empty())
+         {
+         auto q = fConnection.query(queryI);
+         q.execute();
+         Info(q.info());
+         }
+         */
+        // ---------------------------------------------------------
+
+        fConnection.query("UNLOCK TABLES");
+
+        // ---------------------------------------------------------
+
+        Dim::SendCommand("DIM_CONTROL/INTERRUPT", "reschedule");
+
+        ostringstream out;
+        out << Time()-stopwatch;
+        Info(out);
+
+        return true;
+    }
+
+    bool Schedule(const string &name, const double &ra, const double &dec)
+    {
+        try
+        {
+            return ScheduleImp(name, ra, dec);
+        }
+        catch (const exception &e)
+        {
+            Error(string("SQL query failed: ")+e.what());
+            fConnection.disconnect();
+            return false;
+        }
+    }
+
+    // ========================================================================
+
+
+    // ========================================================================
+
+    bool CheckEventSize(size_t has, const char *name, size_t size)
+    {
+        if (has==size)
+            return true;
+
+        ostringstream msg;
+        msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
+        Fatal(msg);
+        return false;
+    }
+
+    int ScheduleGCN(const EventImp &evt)
+    {
+        if (!CheckEventSize(evt.GetSize(), "ScheduleGCN", 2+4+3*8))
+            return kSM_FatalError;
+
+        const ToO::DataGRB &grb = evt.Ref<ToO::DataGRB>();
+
+        const GCN::PaketType_t *ptr=GCN::kTypes;
+        for (; ptr->type>=0 && ptr->type!=grb.type; ptr++);
+        if (ptr->type==-1)
+        {
+            Warn("Unknown Packet Type: "+to_string(ptr->type));
+            return GetCurrentState();
+        }
+
+        ostringstream out;
+        out << "Received: '" << ptr->name << "' ID=" << grb.type << " NUM=" << grb.trigid << " RA=" << grb.ra/15. << "h DEC=" << grb.dec << "\u00b0 ERR=" << grb.err;
+
+        Info(out);
+        Info(ptr->description);
+
+        const CheckVisibility check(grb.ra, grb.dec);
+
+        Info(string("Source is")+(check.visible?" ":" NOT ")+"visible.");
+
+        Info(string("Sun altitude:   ")+(check.valid_sun    ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.solarobj.fSunHrz.alt)+"\u00b0]");
+        Info(string("Moon distance:  ")+(check.valid_moon   ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.moon_dist)+"\u00b0]");
+        Info(string("Zenith angle:   ")+(check.valid_zd     ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.position.zd)+"\u00b0]");
+        Info(string("Current:        ")+(check.valid_current?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.current)+" \u00b5A]");
+
+        const string name = ptr->instrument+"#"+to_string(grb.trigid);
+        Info("Source name set to '"+name+"'");
+
+        /*
+         * Swift: https://gcn.gsfc.nasa.gov/swift.html
+         relevante Paket-Typen:
+         60   BAT_Alert
+         97   BAT_QL_Pos
+         61   BAT_Pos
+         62   BAT_Pos_Nack
+         Es kann wohl vorkommen, dass ein Alert danach anders klassifiziert ist
+         -> BAT_Trans (84) und es steht dass dann in der GRB notice eine catalog
+         ID im comment steht. Da ist es mir noch nicht so ganz klar, woran man
+         echte Alerts erkennt.
+
+         * Fermi: https://gcn.gsfc.nasa.gov/fermi.html
+         relevante Paket-Typen:
+         110   GBM_Alert           P        B               B                B&A
+         111   GBM_Flt_Pos         P        B               B                B&A
+         112   GBM_Gnd_Pos         P        B               B                B&A
+         115   GBM_Final_Pos       P        B               B                B&A
+         Bei GBM müssen wir usn überlegen wie wir es mit den Positionsupdates
+         machen - das können auch mal ein paar Grad sein. siehe zB
+         https://gcn.gsfc.nasa.gov/other/568833894.fermi
+
+         Wenn ich das richtig sehe, kommt bei GBM_Alert noch keine
+         Position, sndern erst mit GBM_Flt_Pos (in dem Bsp kommt das 3 Sek
+         später).
+
+         * INTEGRAL: https://gcn.gsfc.nasa.gov/integral.html
+         wenn ich es richtig verstehe, sind die interessanten Alerts die WAKEUP
+         (tw kommt ein Positionsupdate via OFFLINE oder REFINED). Es gibt auch
+         noch WEAK, aber das sind scheinbar subthreshold alerts - würde ich
+         jetzt erst mal weglassen. Im letzten Jahr gab es 8 WAKEUP alerts. 2017
+         sogar nur 3, 2016 7, 2015 waren es einige mehr, aber das war V404_Cyg -
+         in dem Fall steht dann aber ein Object-Name dabei - ansonsten steht as
+         "Possbibly real GRB event" - zT steht auch, dass es coincident mit GBM
+         events ist
+
+         * KONUS: https://gcn.gsfc.nasa.gov/konus.html
+         Ist mir noch etwas unklar, was das genau ist - die schicken Lichtkurven
+         von GRBs und Transients - muss man nochmal genauer schauen.
+
+         * MAXI: https://gcn.gsfc.nasa.gov/maxi.html
+         wenn ich das richtig verstehe, sind das nicht nur GRBs -> müsste man
+         nochmal genauer schauen bevor man da was implementiert
+
+         * AGILE: https://gcn.gsfc.nasa.gov/agile.html
+         Wahrscheinlich eher nicht relevant, da steht was von 30min nach dem
+         Burst kommt das 'Wakeup'. Die relevanten Paket-Typen wären 100
+         (Wakeup_Pos), 101 (Ground_Pos) und 102 (Refined_Pos)
+
+         * AMON: https://gcn.gsfc.nasa.gov/amon.html
+         sind bisher nur neutrinos - da macht MAGIC glaub ich schon automatic
+         Follow-Up - müssen wir also kucken was Sinn macht
+
+         * Ligo-Virgo GW: https://gcn.gsfc.nasa.gov/lvc.html
+         da ist natürlich die Error-Region groß - müsste man schauen, was man
+         da will
+
+         Fazit: am relevantesten sind Fermi/GBM und Swift/BAT. Evtl könnte
+         INTEGRAL noch Sinn machen und über AMON und LVC sollte man nachdenken,
+         ob/was man haben will.
+*/
+
+        Schedule(name, grb.ra, grb.dec);
+
+        return fConnection.connected() ? ToO::State::kConnected : ToO::State::kDisconnected;
+        //return GetCurrentState();
     }
 
     int Execute()
     {
-        switch (T::GetCurrentState())
-        {
-        case kSM_Scheduling:
+        Time now;
+        if (now>fKeepAliveDeadline)
+        {
+            static int ernum = 0;
             try
             {
-                return Schedule() ? T::kSM_Error : T::kSM_Ready;
+                // Unfortunately, reconnecting [Takes about 1s] is
+                // the only non-blocking way to ensure an open
+                // connection. Ping would be nice but is blocking.
+                fConnection = Database(fUri);
+                ernum = 0;
             }
-            catch (const mysqlpp::Exception &e)
+            catch (const mysqlpp::ConnectionFailed &e)
             {
-                T::Error(string("MySQL: ")+e.what());
-                return T::kSM_Error;
+                if (ernum!=e.errnum())
+                    Error(e.what());
+                ernum = e.errnum();
             }
 
-            // This does an autmatic reset (FOR TESTING ONLY)
-        case T::kSM_Error:
-            return T::kSM_Ready;
-        }
-        return T::GetCurrentState();
-    }
-
-    int Transition(const Event &evt)
-    {
-        switch (evt.GetTargetState())
-        {
-        case kSM_Scheduling:
-            if (evt.GetSize()>0)
-                fDBName = evt.GetText();
-            break;
-        }
-
-        return evt.GetTargetState();
-    }
+            fKeepAliveDeadline += boost::posix_time::seconds(fKeepAliveInterval);
+        }
+
+        return fConnection.connected() ? ToO::State::kConnected : ToO::State::kDisconnected;
+    }
+
+public:
+    StateMachineToO(ostream &out=cout) : StateMachineDim(out, "SCHEDULER"),
+        fConnection(""), fKeepAliveDeadline(Time())
+    {
+        AddStateName(ToO::State::kDisconnected, "Disconnected",
+                     "The Dim DNS is reachable, but the required subsystems are not available.");
+
+        AddStateName(ToO::State::kConnected, "Connected",
+                     "All needed subsystems are connected to their hardware, no action is performed.");
+
+        AddEvent("GCN", "S:1;I:1;D:1;D:1;D:1")
+            (bind(&StateMachineToO::ScheduleGCN, this, placeholders::_1))
+            (""
+             "|type[int16]:TypeID (see HeaderGCN.h)"
+             "|num[uint32]:Right ascension"
+             "|ra[double]:Right ascension"
+             "|dec[double]:Declination"
+             "|err[double]:Declination");
+    }
+
+    /*
+    bool GetConfig(Configuration &conf, const string &name, const string &sub, uint16_t &rc)
+    {
+        if (conf.HasDef(name, sub))
+        {
+            rc = conf.GetDef<uint16_t>(name, sub);
+            return true;
+        }
+
+        Error("Neither "+name+"default nor "+name+sub+" found.");
+        return false;
+    }*/
 
     int EvalOptions(Configuration &conf)
     {
-        fDatabase       = conf.Get<string>("schedule-database");
-        fDurationCalRun = conf.Get<int>("duration-cal-run");
-        fDurationPedRun = conf.Get<int>("duration-ped-run");
-        fDurationRepos  = conf.Get<int>("duration-repos");
-
-        if (!conf.Has("schedule"))
-            return -1;
-
-        fDBName = conf.Get<string>("schedule");
-        return Schedule();
-    }
-
+        fVerbose = !conf.Get<bool>("quiet");
+        fDryRun = conf.Get<bool>("dry-run");
+        fKeepAliveInterval = conf.Get<uint16_t>("keep-alive");
+        fUri = conf.Get<string>("schedule-database");
+        return -1;
+    }
 };
 
 
 // ------------------------------------------------------------------------
+
 #include "Main.h"
 
-template<class T, class S>
+template<class T>
 int RunShell(Configuration &conf)
 {
-    return Main::execute<T, AutoScheduler<S>>(conf);
+    return Main::execute<T, StateMachineToO>(conf);
 }
 
 void SetupConfiguration(Configuration &conf)
 {
-    po::options_description control("Scheduler options");
+    po::options_description control("Scheduler");
     control.add_options()
-        ("no-dim",    po_switch(),    "Disable dim services")
-        ("schedule-database", var<string>()
-#if BOOST_VERSION >= 104200
-         ->required()
-#endif
-                                           ,  "Database link as in\n\tuser:password@server[:port]/database.")
-        ("schedule",          var<string>(),  "")
-        ("mintime",           var<int>(),     "minimum observation time")
-        ("duration-cal-run",  var<int>()
-#if BOOST_VERSION >= 104200
-         ->required()
-#endif
-                                           ,     "duration of calibration run [min]")
-        ("duration-ped-run",  var<int>()
-#if BOOST_VERSION >= 104200
-         ->required()
-#endif
-                                           ,     "duration of pedestal run [min]")
-        ("duration-repos",    var<int>()
-#if BOOST_VERSION >= 104200
-         ->required()
-#endif
-                                           ,     "duration of repositioning [min]")
+        ("quiet,q", po_bool(), "")
+        ("dry-run", po_bool(), "Switch off any alteration of the database")
+        ("keep-alive", var<uint16_t>(uint16_t(300)), "Interval in seconds to ping (reconnect) the database server")
+        ("schedule-database", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
         ;
 
-    po::positional_options_description p;
-    p.add("schedule", 1); // The first positional options
-
     conf.AddOptions(control);
-    conf.SetArgumentPositions(p);
 }
 
@@ -757,13 +1100,8 @@
 {
     cout <<
-        "The scheduler... TEXT MISSING\n"
+        "The scheduler program is able to schedule a new observation.\n"
         "\n"
-        "The default is that the program is started without user intercation. "
-        "All actions are supposed to arrive as DimCommands. Using the -c "
-        "option, a local shell can be initialized. With h or help a short "
-        "help message about the usuage can be brought to the screen.\n"
-        "\n"
-        "Usage: scheduler [-c type] [OPTIONS] <schedule-database>\n"
-        "  or:  scheduler [OPTIONS] <schedule-database>\n";
+        "Usage: scheduler [-c type] [OPTIONS]\n"
+        "  or:  scheduler [OPTIONS]\n";
     cout << endl;
 }
@@ -771,6 +1109,5 @@
 void PrintHelp()
 {
-    /* Additional help text which is printed after the configuration
-     options goes here */
+    Main::PrintHelp<StateMachineToO>();
 }
 
@@ -782,53 +1119,16 @@
     SetupConfiguration(conf);
 
-    po::variables_map vm;
-    try
-    {
-        vm = conf.Parse(argc, argv);
-    }
-#if BOOST_VERSION > 104000
-    catch (po::multiple_occurrences &e)
-    {
-        cerr << "Program options invalid due to: " << e.what() << " of '" << e.get_option_name() << "'." << endl;
-        return -1;
-    }
-#endif
-    catch (exception& e)
-    {
-        cerr << "Program options invalid due to: " << e.what() << endl;
-        return -1;
-    }
-
-//    try
-    {
-        // No console access at all
-        if (!conf.Has("console"))
-        {
-            if (conf.Get<bool>("no-dim"))
-                return RunShell<LocalStream, StateMachine>(conf);
-            else
-                return RunShell<LocalStream, StateMachineDim>(conf);
-        }
-        // Cosole access w/ and w/o Dim
-        if (conf.Get<bool>("no-dim"))
-        {
-            if (conf.Get<int>("console")==0)
-                return RunShell<LocalShell, StateMachine>(conf);
-            else
-                return RunShell<LocalConsole, StateMachine>(conf);
-        }
-        else
-        {
-            if (conf.Get<int>("console")==0)
-                return RunShell<LocalShell, StateMachineDim>(conf);
-            else
-                return RunShell<LocalConsole, StateMachineDim>(conf);
-        }
-    }
-/*    catch (std::exception& e)
-    {
-        std::cerr << "Exception: " << e.what() << "\n";
-    }*/
+    if (!conf.DoParse(argc, argv, PrintHelp))
+        return 127;
+
+    if (!conf.Has("console"))
+        return RunShell<LocalStream>(conf);
+
+    if (conf.Get<int>("console")==0)
+        return RunShell<LocalShell>(conf);
+    else
+        return RunShell<LocalConsole>(conf);
 
     return 0;
 }
+
