#include <boost/bind.hpp>
#include <boost/array.hpp>
#if BOOST_VERSION < 104400
#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 4))
#undef BOOST_HAS_RVALUE_REFS
#endif
#endif
#include <boost/thread.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/deadline_timer.hpp>

#include "FACT.h"
#include "Dim.h"
#include "Event.h"
#include "Shell.h"
#include "StateMachineDim.h"
#include "Connection.h"
#include "Configuration.h"
#include "Timers.h"
#include "Console.h"
#include "Converter.h"

#include "tools.h"

#include "LocalControl.h"


namespace ba = boost::asio;
namespace bs = boost::system;
namespace dummy = ba::placeholders;

using namespace std;

// ------------------------------------------------------------------------

namespace Drive
{
    struct DimPointing
    {
    } __attribute__((__packed__));

    struct DimTracking
    {
    } __attribute__((__packed__));

    struct DimStarguider
    {
        double fMissZd;
        double fMissAz;

        double fNominalZd;
        double fNominalAz;

        double fCenterX;
        double fCenterY;

        double fBrightness;

        uint16_t fNumCorrelated;
        uint16_t fNumLeds;
        uint16_t fNumRings;
        uint16_t fNumStars;

    } __attribute__((__packed__));

    struct DimTPoint
    {
        double fNominalAlt;
        double fNominalAz;

        double fCurrentAlt;
        double fCurrentAz;

        double fDevZd;
        double fDevAz;

        double fRa;
        double fDec;

        double fCenterX;
        double fCenterY;
        double fCenterMag;

        double fStarX;
        double fStarY;
        double fStarMag;

        double fBrightness;
        double fRealMag;

        uint16_t fNumLeds;
        uint16_t fNumRings;
        uint16_t fNumStars;
        uint16_t fNumCorrelated;

    } __attribute__((__packed__));
};



// ------------------------------------------------------------------------

class ConnectionDrive : public Connection
{
    int  fState;

    bool fIsVerbose;

    // --verbose
    // --hex-out
    // --dynamic-out
    // --load-file
    // --leds
    // --trigger-interval
    // --physcis-coincidence
    // --calib-coincidence
    // --physcis-window
    // --physcis-window
    // --trigger-delay
    // --time-marker-delay
    // --dead-time
    // --clock-conditioner-r0
    // --clock-conditioner-r1
    // --clock-conditioner-r8
    // --clock-conditioner-r9
    // --clock-conditioner-r11
    // --clock-conditioner-r13
    // --clock-conditioner-r14
    // --clock-conditioner-r15
    // ...

    virtual void UpdatePointing(const Time &, const boost::array<double, 2> &)
    {
    }

    virtual void UpdateTracking(const Time &, const boost::array<double, 7> &)
    {
    }

    virtual void UpdateStarguider(const Time &, const Drive::DimStarguider &)
    {
    }

    virtual void UpdateTPoint(const Time &, const Drive::DimTPoint &)
    {
    }

protected:
    map<uint16_t, int> fCounter;

    ba::streambuf fBuffer;

    Time ReadTime(istream &in)
    {
        uint16_t y, m, d, hh, mm, ss, ms;
        in >> y >> m >> d >> hh >> mm >> ss >> ms;

        return Time(y, m, d, hh, mm, ss, ms*1000);
    }

    double ReadAngle(istream &in)
    {
        char     sgn;
        uint16_t d, m;
        float    s;

        in >> sgn >> d >> m >> s;

        const double ret = ((60.0 * (60.0 * (double)d + (double)m) + s))/3600.;
        return sgn=='-' ? -ret : ret;
    }

    void HandleReceivedReport(const boost::system::error_code& err, size_t bytes_received)
    {
        // Do not schedule a new read if the connection failed.
        if (bytes_received==0 || err)
        {
            if (err==ba::error::eof)
                Warn("Connection closed by remote host (FTM).");

            // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
            // 125: Operation canceled
            if (err && err!=ba::error::eof &&                     // Connection closed by remote host
                err!=ba::error::basic_errors::not_connected &&    // Connection closed by remote host
                err!=ba::error::basic_errors::operation_aborted)  // Connection closed by us
            {
                stringstream str;
                str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
                Error(str);
            }
            PostClose(err!=ba::error::basic_errors::operation_aborted);
            return;
        }

        istream is(&fBuffer);

        string line;
        getline(is, line);

        if (fIsVerbose)
            Out() << line << endl;

        StartReadReport();

        if (line.substr(0, 13)=="STARG-REPORT ")
        {
            istringstream stream(line.substr(16));

            // 0: Error
            // 1: Standby
            // 2: Monitoring
            uint16_t status1;
            stream >> status1;
            const Time t1 = ReadTime(stream);

            uint16_t status2;
            stream >> status2;
            const Time t2 = ReadTime(stream);

            double misszd, missaz;
            stream >> misszd >> missaz;

            const double zd = ReadAngle(stream);
            const double az = ReadAngle(stream);

            double cx, cy;
            stream >> cx >> cy;

            int ncor;
            stream >> ncor;

            double bright, mjd;
            stream >> bright >> mjd;

            int nled, nring, nstars;
            stream >> nled >> nring >> nstars;

            if (stream.fail())
                return;

            Drive::DimStarguider data;

            data.fMissZd = misszd;
            data.fMissAz = missaz;
            data.fNominalZd = zd;
            data.fNominalAz = az;
            data.fCenterX = cx;
            data.fCenterY = cy;
            data.fNumCorrelated = ncor;
            data.fBrightness = bright;
            data.fNumLeds = nled;
            data.fNumRings = nring;
            data.fNumStars = nstars;

            UpdateStarguider(Time(mjd), data);

            return;

        }

        if (line.substr(0, 14)=="TPOINT-REPORT ")
        {
            istringstream stream(line.substr(17));

            uint16_t status1;
            stream >> status1;
            const Time t1 = ReadTime(stream);

            uint16_t status2;
            stream >> status2;
            const Time t2 = ReadTime(stream);

            double az1, alt1, az2, alt2, ra, dec, dzd, daz;
            stream >> az1 >> alt1 >> az2 >> alt2 >> ra >> dec >> dzd >> daz;

            // c: center, s:start
            double mjd, cmag, smag, cx, cy, sx, sy;
            stream >> mjd >> cmag >> smag >> cx >> cy >> sx >> sy;

            int nled, nring, nstar, ncor;
            stream >> nled >> nring >> nstar >> ncor;

            double bright, mag;
            stream >> bright >> mag;

            string name;
            stream >> name;

            if (stream.fail())
                return;

            Drive::DimTPoint tpoint;

            tpoint.fNominalAz  = az1;
            tpoint.fNominalAlt = alt1;
            tpoint.fCurrentAz  = az2;
            tpoint.fCurrentAlt = alt2;
            tpoint.fDevAz      = daz;
            tpoint.fDevZd      = dzd;
            tpoint.fRa         = ra;
            tpoint.fDec        = dec;

            tpoint.fCenterX    = cx;
            tpoint.fCenterY    = cy;
            tpoint.fCenterMag  = cmag;

            tpoint.fStarX      = sx;
            tpoint.fStarY      = sy;
            tpoint.fStarMag    = smag;

            tpoint.fBrightness = bright;

            tpoint.fNumCorrelated = ncor;
            tpoint.fNumLeds       = nled;
            tpoint.fNumRings      = nring;
            tpoint.fNumStars      = nstar;

            tpoint.fRealMag = mag;

            return;
        }

        if (line.substr(0, 13)=="DRIVE-REPORT ")
        {
            // DRIVE-REPORT M1
            // 01 2011 05 14 11 31 19 038
            // 02 1858 11 17 00 00 00 000
            // + 000 00 000 + 000 00 000
            // + 000 00 000
            // 55695.480081
            // + 000 00 000 + 000 00 000
            // + 000 00 000 + 000 00 000
            // 0000.000 0000.000
            // 0 2

            // status
            // year month day hour minute seconds millisec
            // year month day hour minute seconds millisec
            // ra(+ h m s) dec(+ d m s) ha(+ h m s)
            // mjd
            // zd(+ d m s) az(+ d m s)
            // zd(+ d m s) az(+ d m s)
            // zd_err az_err
            // armed(0=unlocked, 1=locked)
            // stgmd(0=none, 1=starguider, 2=starguider off)
            istringstream stream(line.substr(16));

            uint16_t status1;
            stream >> status1;
            const Time t1 = ReadTime(stream);

            uint16_t status2;
            stream >> status2;
            const Time t2 = ReadTime(stream);

            const double ra  = ReadAngle(stream);
            const double dec = ReadAngle(stream);
            const double ha  = ReadAngle(stream);

            double mjd;
            stream >> mjd;

            const double zd1 = ReadAngle(stream);
            const double az1 = ReadAngle(stream);
            const double zd2 = ReadAngle(stream);
            const double az2 = ReadAngle(stream);

            double zd_err, az_err;
            stream >> zd_err;
            stream >> az_err;

            uint16_t armed, stgmd;
            stream >> armed;
            stream >> stgmd;

            if (stream.fail())
                return;

            // Status 0: Error
            // Status 1: Stopped
            // Status 3: Stopping || Moving
            // Status 4: Tracking
            if (status1==0)
                status1 = 99;
            fState = status1==1 ? armed+1 : status1;

            const boost::array<double, 2> point = {{ zd2, az2 }};
            UpdatePointing(t1, point);

            const boost::array<double, 7> track =
            {{
                ra, dec, ha,
                zd1, az1,
                zd_err, az_err
            }};
            UpdateTracking(Time(mjd), track);

            // ---- DIM ----> t1 as event time
            //                status1
            //                mjd
            //                ra/dec/ha
            //                zd/az (nominal)
            //                zd/az (current)
            //                err(zd/az)
            //                [armed] [stgmd]

            // Maybe:
            // POINTING_POSITION --> t1, zd/az (current), [armed, stgmd, status1]
            //
            // if (mjd>0)
            // TRACKING_POSITION --> mjd, zd/az (nominal), err(zd/az)
            //                       ra/dec, ha(not well defined),
            //                       [Nominal + Error == Current]

            // MJD is the time which corresponds to the nominal position
            // t1  is the time which corresponds to the current position/HA

            return;
        }
    }

    void StartReadReport()
    {
        boost::asio::async_read_until(*this, fBuffer, '\n',
                                      boost::bind(&ConnectionDrive::HandleReceivedReport, this,
                                                  dummy::error, dummy::bytes_transferred));
    }

    boost::asio::deadline_timer fKeepAlive;

    void KeepAlive()
    {
        PostMessage(string("KEEP_ALIVE"));

        fKeepAlive.expires_from_now(boost::posix_time::seconds(10));
        fKeepAlive.async_wait(boost::bind(&ConnectionDrive::HandleKeepAlive,
                                          this, dummy::error));
    }

    void HandleKeepAlive(const bs::error_code &error)
    {
        // 125: Operation canceled (bs::error_code(125, bs::system_category))
        if (error && error!=ba::error::basic_errors::operation_aborted)
        {
            stringstream str;
            str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
            Error(str);

            PostClose(false);
            return;
        }

        if (!is_open())
        {
            // For example: Here we could schedule a new accept if we
            // would not want to allow two connections at the same time.
            return;
        }

        // Check whether the deadline has passed. We compare the deadline
        // against the current time since a new asynchronous operation
        // may have moved the deadline before this actor had a chance
        // to run.
        if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
            return;

        KeepAlive();
    }


private:
    // This is called when a connection was established
    void ConnectionEstablished()
    {
        StartReadReport();
        KeepAlive();
    }

    /*
    void HandleReadTimeout(const bs::error_code &error)
    {
        if (error && error!=ba::error::basic_errors::operation_aborted)
        {
            stringstream str;
            str << "Read timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
            Error(str);

            PostClose();
            return;

        }

        if (!is_open())
        {
            // For example: Here we could schedule a new accept if we
            // would not want to allow two connections at the same time.
            return;
        }

        // Check whether the deadline has passed. We compare the deadline
        // against the current time since a new asynchronous operation
        // may have moved the deadline before this actor had a chance
        // to run.
        if (fInTimeout.expires_at() > ba::deadline_timer::traits_type::now())
            return;

        Error("Timeout reading data from "+URL());

        PostClose();
    }*/


public:

    static const uint16_t kMaxAddr;

public:
    ConnectionDrive(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
        fState(0), fIsVerbose(true), fKeepAlive(ioservice)
    {
        SetLogStream(&imp);
    }

    void SetVerbose(bool b)
    {
        fIsVerbose = b;
    }

    int GetState() const { return IsConnected() ? fState+1 : 1; }
};

const uint16_t ConnectionDrive::kMaxAddr = 0xfff;

// ------------------------------------------------------------------------

#include "DimDescriptionService.h"

class ConnectionDimDrive : public ConnectionDrive
{
private:

    DimDescribedService fDimPointing;
    DimDescribedService fDimTracking;

    template<size_t N>
        void Update(DimDescribedService &svc, const Time &t, const boost::array<double, N> &arr) const
    {
        svc.setTimestamp(int(t.UnixTime()), t.ms());
        svc.setData(const_cast<double*>(arr.data()), arr.size()*sizeof(double));
        svc.updateService();
    }

    virtual void UpdatePointing(const Time &t,
                                const boost::array<double, 2> &arr)
    {
        Update(fDimPointing, t, arr);
    }

    virtual void UpdateTracking(const Time &t,
                                const boost::array<double, 7> &arr)
    {
        Update(fDimTracking, t, arr);
    }

public:
    ConnectionDimDrive(ba::io_service& ioservice, MessageImp &imp) :
        ConnectionDrive(ioservice, imp),
        fDimPointing("FTM_CONTROL/POINTING_POSITION", "D:2", ""),
        fDimTracking("FTM_CONTROL/TRACKING_POSITION", "D:7", "")
    {
    }

    // A B [C] [D] E [F] G H [I] J K [L] M N O P Q R [S] T U V W [X] Y Z
};

// ------------------------------------------------------------------------

template <class T, class S>
class StateMachineDrive : public T, public ba::io_service, public ba::io_service::work
{
    int Wrap(boost::function<void()> f)
    {
        f();
        return T::GetCurrentState();
    }

    boost::function<int(const EventImp &)> Wrapper(boost::function<void()> func)
    {
        return boost::bind(&StateMachineDrive::Wrap, this, func);
    }

private:
    S fDrive;

    enum states_t
    {
        kStateDisconnected = 1,
        kStateConnected,
        kStateArmed,
        kStateMoving,
        kStateTracking,
    };

    // Status 0: Error
    // Status 1: Unlocked
    // Status 2: Locked
    // Status 3: Stopping || Moving
    // Status 4: Tracking

    bool CheckEventSize(size_t has, const char *name, size_t size)
    {
        if (has==size)
            return true;

        stringstream msg;
        msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
        T::Fatal(msg);
        return false;
    }

    enum Coordinates
    {
        kPoint,
        kTrackSlow,
        kTrackFast
    };

    string AngleToStr(double angle)
    {
        /* Handle sign */
        const char sgn = angle<0?'-':'+';

        /* Round interval and express in smallest units required */
        double a = round(3600. * fabs(angle)); // deg to seconds

        /* Separate into fields */
        const double ad = trunc(a/3600.);
        a -= ad * 3600.;
        const double am = trunc(a/60.);
        a -= am * 60.;
        const double as = trunc(a);

        /* Return results */
        ostringstream str;
        str << sgn << " " << uint16_t(ad) << " " << uint16_t(am) << " " << as;
        return str.str();
    }

    int SendCommand(const string &str)
    {
        fDrive.PostMessage(str);
        return T::GetCurrentState();
    }

    int SendCoordinates(const EventImp &evt, const Coordinates type)
    {
        if (!CheckEventSize(evt.GetSize(), "SendCoordinates", 16))
            return T::kSM_FatalError;

        const double *dat = evt.Ptr<double>();

        string command;

        switch (type)
        {
        case kPoint:      command += "ZDAZ ";  break;
        case kTrackSlow:  command += "RADEC "; break;
        case kTrackFast:  command += "GRB ";   break;
        }

        command += AngleToStr(dat[0]) + ' ' + AngleToStr(dat[1]);

        return SendCommand(command);
    }

    int SetVerbosity(const EventImp &evt)
    {
        if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
            return T::kSM_FatalError;

        fDrive.SetVerbose(evt.GetBool());

        return T::GetCurrentState();
    }

    int Disconnect()
    {
        // Close all connections
        fDrive.PostClose(false);

        /*
         // Now wait until all connection have been closed and
         // all pending handlers have been processed
         poll();
         */

        return T::GetCurrentState();
    }

    int Reconnect(const EventImp &evt)
    {
        // Close all connections to supress the warning in SetEndpoint
        fDrive.PostClose(false);

        // Now wait until all connection have been closed and
        // all pending handlers have been processed
        poll();

        if (evt.GetBool())
            fDrive.SetEndpoint(evt.GetString());

        // Now we can reopen the connection
        fDrive.PostClose(true);

        return T::GetCurrentState();
    }

    int Execute()
    {
        // Dispatch (execute) at most one handler from the queue. In contrary
        // to run_one(), it doesn't wait until a handler is available
        // which can be dispatched, so poll_one() might return with 0
        // handlers dispatched. The handlers are always dispatched/executed
        // synchronously, i.e. within the call to poll_one()
        poll_one();

        return fDrive.GetState();
    }


public:
    StateMachineDrive(ostream &out=cout) :
        T(out, "DRIVE_CONTROL"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
        fDrive(*this, *this)
    {
        // ba::io_service::work is a kind of keep_alive for the loop.
        // It prevents the io_service to go to stopped state, which
        // would prevent any consecutive calls to run()
        // or poll() to do nothing. reset() could also revoke to the
        // previous state but this might introduce some overhead of
        // deletion and creation of threads and more.

        // State names
        AddStateName(kStateDisconnected, "Disconnected",
                     "");

        AddStateName(kStateConnected, "Connected",
                     "");

        AddStateName(kStateArmed, "Armed",
                     "");

        AddStateName(kStateMoving, "Moving",
                     "");

        AddStateName(kStateTracking, "Tracking",
                     "");

        // kStateIdle
        // kStateArmed
        // kStateMoving
        // kStateTracking

        // Init
        // -----------
        // "ARM lock"
        // "STGMD off"

        /*
         [ ] WAIT   -> WM_WAIT
         [x] STOP!  -> WM_STOP
         [x] RADEC  ra(+ d m s.f)  dec(+ d m s.f)
         [x] GRB    ra(+ d m s.f)  dec(+ d m s.f)
         [x] ZDAZ   zd(+ d m s.f)  az (+ d m s.f)
         [ ] CELEST id offset angle
         [ ] MOON   wobble offset
         [ ] PREPS  string
         [ ] TPOIN  star mag
         [ ] ARM    lock/unlock
         [ ] STGMD  on/off
         */

        // Drive Commands
        T::AddEvent("MOVE_TO", "D:2", kStateArmed)  // ->ZDAZ
            (boost::bind(&StateMachineDrive::SendCoordinates, this, _1, kPoint))
            (""
             "|zd[deg]:"
             "|az[deg]:");

        T::AddEvent("TRACK", "D:2", kStateArmed)   // ->RADEC/GRB
            (boost::bind(&StateMachineDrive::SendCoordinates, this, _1, kTrackSlow))
            (""
             "|ra[h]:"
             "|dec[deg]:");

        T::AddEvent("MOON", kStateArmed)
            (boost::bind(&StateMachineDrive::SendCommand, this, "MOON 0 0"))
            ("");
        T::AddEvent("VENUS", kStateArmed)
            (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 2 0 0"))
            ("");
        T::AddEvent("MARS", kStateArmed)
            (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 4 0 0"))
            ("");
        T::AddEvent("JUPITER", kStateArmed)
            (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 5 0 0"))
            ("");
        T::AddEvent("SATURN", kStateArmed)
            (boost::bind(&StateMachineDrive::SendCommand, this, "CELEST 6 0 0"))
            ("");

        T::AddEvent("TPOINT")
            (boost::bind(&StateMachineDrive::SendCommand, this, "TPOIN FACT 0"))
            ("");

        T::AddEvent("STOP")
            (boost::bind(&StateMachineDrive::SendCommand, this, "STOP!"))
            ("");

        T::AddEvent("ARM", kStateConnected)
            (boost::bind(&StateMachineDrive::SendCommand, this, "ARM lock"))
            ("");


        // Verbosity commands
        T::AddEvent("SET_VERBOSE", "B")
            (boost::bind(&StateMachineDrive::SetVerbosity, this, _1))
            ("set verbosity state"
             "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");

        // Conenction commands
        AddEvent("DISCONNECT", kStateConnected, kStateArmed)
            (boost::bind(&StateMachineDrive::Disconnect, this))
            ("disconnect from ethernet");

        AddEvent("RECONNECT", "O", kStateDisconnected, kStateConnected, kStateArmed)
            (boost::bind(&StateMachineDrive::Reconnect, this, _1))
            ("(Re)connect ethernet connection to FTM, a new address can be given"
             "|[host][string]:new ethernet address in the form <host:port>");

        fDrive.StartConnect();
    }

    void SetEndpoint(const string &url)
    {
        fDrive.SetEndpoint(url);
    }

    bool SetConfiguration(const Configuration &conf)
    {
        SetEndpoint(conf.Get<string>("addr"));

        fDrive.SetVerbose(!conf.Get<bool>("quiet"));

        return true;
    }
};

// ------------------------------------------------------------------------

void RunThread(StateMachineImp *io_service)
{
    // This is necessary so that the StateMachien Thread can signal the
    // Readline to exit
    io_service->Run();
    Readline::Stop();
}

/*
template<class S, class T>
int RunDim(Configuration &conf)
{
    WindowLog wout;

    ReadlineColor::PrintBootMsg(wout, conf.GetName(), false);

    if (conf.Has("log"))
        if (!wout.OpenLogFile(conf.Get<string>("log")))
            wout << kRed << "ERROR - Couldn't open log-file " << conf.Get<string>("log") << ": " << strerror(errno) << endl;

    // Start io_service.Run to use the StateMachineImp::Run() loop
    // Start io_service.run to only use the commandHandler command detaching
    StateMachineDrive<S, T> io_service(wout);
    if (!io_service.SetConfiguration(conf))
        return -1;

    io_service.Run();

    return 0;
}
*/

template<class T, class S, class R>
int RunShell(Configuration &conf)
{
    static T shell(conf.GetName().c_str(), conf.Get<int>("console")!=1);

    WindowLog &win  = shell.GetStreamIn();
    WindowLog &wout = shell.GetStreamOut();

    if (conf.Has("log"))
        if (!wout.OpenLogFile(conf.Get<string>("log")))
            win << kRed << "ERROR - Couldn't open log-file " << conf.Get<string>("log") << ": " << strerror(errno) << endl;

    StateMachineDrive<S, R> io_service(wout);
    if (!io_service.SetConfiguration(conf))
        return -1;

    shell.SetReceiver(io_service);

    boost::thread t(boost::bind(RunThread, &io_service));
    // boost::thread t(boost::bind(&StateMachineDrive<S>::Run, &io_service));

    if (conf.Has("exec"))
    {
        const vector<string> v = conf.Get<vector<string>>("exec");
        for (vector<string>::const_iterator it=v.begin(); it!=v.end(); it++)
            shell.Execute(*it);
    }

    shell.Run();                 // Run the shell
    io_service.Stop();           // Signal Loop-thread to stop
    // io_service.Close();       // Obsolete, done by the destructor

    // Wait until the StateMachine has finished its thread
    // before returning and destroying the dim objects which might
    // still be in use.
    t.join();

    return 0;
}

void SetupConfiguration(Configuration &conf)
{
    const string n = conf.GetName()+".log";

    po::options_description config("Program options");
    config.add_options()
        ("dns",       var<string>("localhost"), "Dim nameserver host name (Overwites DIM_DNS_NODE environment variable)")
        ("log,l",     var<string>(n), "Write log-file")
        ("no-dim,d",  po_switch(),    "Disable dim services")
        ("console,c", var<int>(),     "Use console (0=shell, 1=simple buffered, X=simple unbuffered)")
        ("exec,e",    vars<strings>(),"Execute one or more scrips at startup")
        ;

    po::options_description control("FTM control options");
    control.add_options()
        ("addr,a",  var<string>("localhost:7404"),  "Network address of FTM")
        ("quiet,q", po_bool(),  "Disable printing contents of all received messages (except dynamic data) in clear text.")
        ;

    conf.AddEnv("dns", "DIM_DNS_NODE");

    conf.AddOptions(config);
    conf.AddOptions(control);
}

/*
 Extract usage clause(s) [if any] for SYNOPSIS.
 Translators: "Usage" and "or" here are patterns (regular expressions) which
 are used to match the usage synopsis in program output.  An example from cp
 (GNU coreutils) which contains both strings:
  Usage: cp [OPTION]... [-T] SOURCE DEST
    or:  cp [OPTION]... SOURCE... DIRECTORY
    or:  cp [OPTION]... -t DIRECTORY SOURCE...
 */
void PrintUsage()
{
    cout <<
        "The drivectrl is an interface to cosy.\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: drivectrl [-c type] [OPTIONS]\n"
        "  or:  drivectrl [OPTIONS]\n";
    cout << endl;
}

void PrintHelp()
{
    /* Additional help text which is printed after the configuration
     options goes here */

    /*
     cout << "bla bla bla" << endl << endl;
     cout << endl;
     cout << "Environment:" << endl;
     cout << "environment" << endl;
     cout << endl;
     cout << "Examples:" << endl;
     cout << "test exam" << endl;
     cout << endl;
     cout << "Files:" << endl;
     cout << "files" << endl;
     cout << endl;
     */
}

int main(int argc, const char* argv[])
{
    Configuration conf(argv[0]);
    conf.SetPrintUsage(PrintUsage);
    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;
    }

    if (conf.HasVersion() || conf.HasPrint())
        return -1;

    if (conf.HasHelp())
    {
        PrintHelp();
        return -1;
    }

    Dim::Setup(conf.Get<string>("dns"));

    //try
    {
        // No console access at all
        if (!conf.Has("console"))
        {
            if (conf.Get<bool>("no-dim"))
                return RunShell<LocalStream, StateMachine, ConnectionDrive>(conf);
            else
                return RunShell<LocalStream, StateMachineDim, ConnectionDimDrive>(conf);
        }
        // Cosole access w/ and w/o Dim
        if (conf.Get<bool>("no-dim"))
        {
            if (conf.Get<int>("console")==0)
                return RunShell<LocalShell, StateMachine, ConnectionDrive>(conf);
            else
                return RunShell<LocalConsole, StateMachine, ConnectionDrive>(conf);
        }
        else
        {
            if (conf.Get<int>("console")==0)
                return RunShell<LocalShell, StateMachineDim, ConnectionDimDrive>(conf);
            else
                return RunShell<LocalConsole, StateMachineDim, ConnectionDimDrive>(conf);
        }
    }
    /*catch (std::exception& e)
    {
        cerr << "Exception: " << e.what() << endl;
        return -1;
    }*/

    return 0;
}
