#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 "Dim.h"
#include "Event.h"
#include "Shell.h"
#include "StateMachineDim.h"
#include "Connection.h"
#include "Configuration.h"
#include "Console.h"
#include "Converter.h"

#include "tools.h"

#include "LocalControl.h"
#include "HeadersFTM.h"


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

using namespace std;

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

class ConnectionFTM : public Connection
{
    vector<uint16_t> fBuffer;

    bool fHasHeader;
    int  fState;

    bool fIsVerbose;
    bool fIsDynamicOut;
    bool fIsHexOutput;

//    string fDefaultSetup;

    // --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
    // ...

protected:
    map<uint16_t, int> fCounter;

    FTM::Header      fHeader;
    FTM::FtuList     fFtuList;
    FTM::StaticData  fStaticData;
    FTM::DynamicData fDynamicData;
    FTM::Error       fError;

    virtual void UpdateFirstHeader()
    {
        // FIXME: Message() ?
        Out() << endl << kBold << "First header received:" << endl;
        Out() << fHeader;
        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fHeader, 16) << endl;
    }

    virtual void UpdateHeader()
    {
        // emit service with trigger counter from header
        if (!fIsVerbose)
            return;

        if (fHeader.fType==FTM::kDynamicData && !fIsDynamicOut)
            return;

        Out() << endl << kBold << "Header received:" << endl;
        Out() << fHeader;
        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fHeader, 16) << endl;
    }

    virtual void UpdateFtuList()
    {
        if (!fIsVerbose)
            return;

        Out() << endl << kBold << "FtuList received:" << endl;
        Out() << fFtuList;
        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fFtuList, 16) << endl;
    }

    virtual void UpdateStaticData()
    {
        if (!fIsVerbose)
            return;

        Out() << endl << kBold << "Static data received:" << endl;
        Out() << fStaticData;
        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fStaticData, 16) << endl;
    }

    virtual void UpdateDynamicData()
    {
        if (!fIsDynamicOut)
            return;

        Out() << endl << kBold << "Dynamic data received:" << endl;
        Out() << fDynamicData;
        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fDynamicData, 16) << endl;
    }

    virtual void UpdateError()
    {
        if (!fIsVerbose)
            return;

        Out() << endl << kRed << "Error received:" << endl;
        Out() << fError;
        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fError, 16) << endl;
    }

    virtual void UpdateCounter()
    {
        if (!fIsVerbose)
            return;

        if (!fIsDynamicOut)
            return;

        Out() << "Received: ";
        Out() << "H=" << fCounter[FTM::kHeader] << "  ";
        Out() << "S=" << fCounter[FTM::kStaticData] << "  ";
        Out() << "D=" << fCounter[FTM::kDynamicData] << "  ";
        Out() << "F=" << fCounter[FTM::kFtuList] << "  ";
        Out() << "E=" << fCounter[FTM::kErrorList] << "  ";
        Out() << "R=" << fCounter[FTM::kRegister] << endl;
    }

    bool CheckConsistency()
    {
        bool warn1 = false;
        if (fStaticData.IsEnabled(FTM::StaticData::kPedestal) != (fStaticData.GetSequencePed()  >0) ||
            fStaticData.IsEnabled(FTM::StaticData::kLPint)    != (fStaticData.GetSequenceLPint()>0) ||
            fStaticData.IsEnabled(FTM::StaticData::kLPext)    != (fStaticData.GetSequenceLPext()>0))
        {
            warn1 = true;
            fStaticData.Enable(FTM::StaticData::kPedestal, fStaticData.GetSequencePed()>0);
            fStaticData.Enable(FTM::StaticData::kLPint,    fStaticData.GetSequenceLPint()>0);
            fStaticData.Enable(FTM::StaticData::kLPext,    fStaticData.GetSequenceLPext()>0);
        }

        bool warn2 = false;
        const uint16_t ref = fStaticData[0].fPrescaling;
        for (int i=1; i<40; i++)
        {
            if (fStaticData[i].fPrescaling != ref)
            {
                warn2 = true;
                fStaticData[i].fPrescaling = ref;
            }
        }

        if (warn1)
            Warn("GeneralSettings not consistent with trigger sequence.");
        if (warn2)
            Warn("Prescaling not consistent for all boards.");

        return !warn1 && !warn2;
    }

private:
    void HandleReceivedData(const bs::error_code& err, size_t bytes_received, int /*type*/)
    {
        // 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
            {
                ostringstream str;
                str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
                Error(str);
            }
            PostClose(err!=ba::error::basic_errors::operation_aborted);
            return;
        }

        // If we have not yet received a header we expect one now
        // This could be moved to a HandleReceivedHeader function
        if (!fHasHeader)
        {
            if (bytes_received!=sizeof(FTM::Header))
            {
                ostringstream str;
                str << "Excepted " << sizeof(FTM::Header) << " bytes (FTM::Header) but received " << bytes_received << ".";
                Error(str);
                PostClose(false);
                return;
            }

            fHeader = fBuffer;

            // Check the data integrity
            if (fHeader.fDelimiter!=FTM::kDelimiterStart)
            {
                ostringstream str;
                str << "Invalid header received: start delimiter wrong, received ";
                str << hex << fHeader.fDelimiter << ", expected " << FTM::kDelimiterStart << ".";
                Error(str);
                PostClose(false);
                return;
            }

            fHasHeader = true;

            // Convert FTM state into FtmCtrl state
            switch (fHeader.fState)
            {
            case FTM::kFtmIdle:
            case FTM::kFtmConfig:
                fState = FTM::kIdle;
                break;

            case FTM::kFtmCalib:
            case FTM::kFtmRunning:
                fState = FTM::kTakingData;
                break;
            }

            if (++fCounter[FTM::kHeader]==1)
                UpdateFirstHeader();

            UpdateCounter();
            UpdateHeader();

            // Start reading of data
            switch (fHeader.fType)
            {
            case FTM::kStaticData:
            case FTM::kDynamicData:
            case FTM::kFtuList:
            case FTM::kRegister:
            case FTM::kErrorList:
                // This is not very efficient because the space is reallocated
                // maybe we can check if the capacity of the std::vector
                // is ever decreased. If not, everythign is fine.
                fBuffer.resize(fHeader.fDataSize);
                AsyncRead(ba::buffer(fBuffer));
                AsyncWait(fInTimeout, 50, &Connection::HandleReadTimeout);
                return;

            default:
                ostringstream str;
                str << "Unknonw type " << fHeader.fType << " in received header." << endl;
                Error(str);
                PostClose(false);
                return;
            }

            return;
        }

        // Check the data integrity (check end delimiter)
        if (ntohs(fBuffer.back())!=FTM::kDelimiterEnd)
        {
            ostringstream str;
            str << "Invalid data received: end delimiter wrong, received ";
            str << hex << ntohs(fBuffer.back()) << ", expected " << FTM::kDelimiterEnd << ".";
            Error(str);
            PostClose(false);
            return;
        }

        // Remove end delimiter
        fBuffer.pop_back();

        try
        {
            // If we have already received a header this is the data now
            // This could be moved to a HandleReceivedData function

            fCounter[fHeader.fType]++;
            UpdateCounter();

            switch (fHeader.fType)
            {
            case FTM::kFtuList:
                fFtuList = fBuffer;
                UpdateFtuList();
                break;

            case FTM::kStaticData:
                fStaticData = fBuffer;

                if (fCounter[FTM::kStaticData]==1)
                    if (!CheckConsistency())
                    {
                        CmdSendStatDat();
                        break;
                    }

                UpdateStaticData();
                break;

            case FTM::kDynamicData:
                fDynamicData = fBuffer;
                UpdateDynamicData();
                break;

            case FTM::kRegister:
                if (fIsVerbose)
                {
                    Out() << endl << kBold << "Register received: " << endl;
                    Out() << "Addr:  " << ntohs(fBuffer[0]) << endl;
                    Out() << "Value: " << ntohs(fBuffer[1]) << endl;
                }
                break;

            case FTM::kErrorList:
                fError = fBuffer;
                UpdateError();
                break;

            default:
                ostringstream str;
                str << "Unknonw type " << fHeader.fType << " in header." << endl;
                Error(str);
                PostClose(false);
                return;
            }
        }
        catch (const logic_error &e)
        {
            ostringstream str;
            str << "Exception converting buffer into data structure: " << e.what();
            Error(str);
            PostClose(false);
            return;
        }

        fInTimeout.cancel();

        fHeader.clear();
        fHasHeader = false;
        fBuffer.resize(sizeof(FTM::Header)/2);
        AsyncRead(ba::buffer(fBuffer));
    }

    // This is called when a connection was established
    void ConnectionEstablished()
    {
        fState = FTM::kConnected;
        fCounter.clear();

        fHeader.clear();
        fHasHeader = false;
        fBuffer.resize(sizeof(FTM::Header)/2);
        AsyncRead(ba::buffer(fBuffer));

//        if (!fDefaultSetup.empty())
//            LoadStaticData(fDefaultSetup);

        // Get a header and configdata!
        CmdReqStatDat();

        // get the DNA of the FTUs
        CmdPing();
    }

    void HandleReadTimeout(const bs::error_code &error)
    {
        if (error==ba::error::basic_errors::operation_aborted)
            return;

        if (error)
        {
            ostringstream 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();
    }


    template<size_t N>
    void PostCmd(boost::array<uint16_t, N> dat, uint16_t u1=0, uint16_t u2=0, uint16_t u3=0, uint16_t u4=0)
    {
        boost::array<uint16_t, 5> cmd = {{ '@', u1, u2, u3, u4 }};

        ostringstream msg;
        msg << "Sending command:" << hex;
        msg << " 0x" << setw(4) << setfill('0') << cmd[0];
        msg << " 0x" << setw(4) << setfill('0') << u1;
        msg << " 0x" << setw(4) << setfill('0') << u2;
        msg << " 0x" << setw(4) << setfill('0') << u3;
        msg << " 0x" << setw(4) << setfill('0') << u4;
        msg << " (+" << dec << dat.size() << " words)";
        Message(msg);

        vector<uint16_t> out(cmd.size()+dat.size());

        transform(cmd.begin(), cmd.end(), out.begin(), htons);
        transform(dat.begin(), dat.end(), out.begin()+cmd.size(), htons);

        PostMessage(out);
    }

    void PostCmd(vector<uint16_t> dat, uint16_t u1=0, uint16_t u2=0, uint16_t u3=0, uint16_t u4=0)
    {
        boost::array<uint16_t, 5> cmd = {{ '@', u1, u2, u3, u4 }};

        ostringstream msg;
        msg << "Sending command:" << hex;
        msg << " 0x" << setw(4) << setfill('0') << cmd[0];
        msg << " 0x" << setw(4) << setfill('0') << u1;
        msg << " 0x" << setw(4) << setfill('0') << u2;
        msg << " 0x" << setw(4) << setfill('0') << u3;
        msg << " 0x" << setw(4) << setfill('0') << u4;
        msg << " (+" << dec << dat.size() << " words)";
        Message(msg);

        vector<uint16_t> out(cmd.size()+dat.size());

        transform(cmd.begin(), cmd.end(), out.begin(), htons);
        copy(dat.begin(), dat.end(), out.begin()+cmd.size());

        PostMessage(out);
    }

    void PostCmd(uint16_t u1=0, uint16_t u2=0, uint16_t u3=0, uint16_t u4=0)
    {
        PostCmd(boost::array<uint16_t, 0>(), u1, u2, u3, u4);
    }
public:

//    static const uint16_t kMaxAddr;

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

    void CmdToggleLed()
    {
        PostCmd(FTM::kCmdToggleLed);
    }

    void CmdPing()
    {
        PostCmd(FTM::kCmdPing);
    }

    void CmdReqDynDat()
    {
        PostCmd(FTM::kCmdRead, FTM::kCmdDynamicData);
    }

    void CmdReqStatDat()
    {
        PostCmd(FTM::kCmdRead, FTM::kCmdStaticData);
    }

    void CmdSendStatDat()
    {
        PostCmd(fStaticData.HtoN(), FTM::kCmdWrite, FTM::kCmdStaticData);

        // Request the changed configuration to ensure the
        // change is distributed in the network
        CmdReqStatDat();
    }

    void CmdStartRun()
    {
        PostCmd(FTM::kCmdStartRun, FTM::kStartRun);

        // Update state information by requesting a new header
        CmdGetRegister(0);
    }

    void CmdStopRun()
    {
        PostCmd(FTM::kCmdStopRun);

        // Update state information by requesting a new header
        CmdGetRegister(0);
    }

    void CmdTakeNevents(uint32_t n)
    {
        const boost::array<uint16_t, 2> data = {{ uint16_t(n>>16), uint16_t(n&0xffff) }};
        PostCmd(data, FTM::kCmdStartRun, FTM::kTakeNevents);

        // Update state information by requesting a new header
        CmdGetRegister(0);
    }

    bool CmdSetRegister(uint16_t addr, uint16_t val)
    {
        if (addr>FTM::StaticData::kMaxAddr)
            return false;

        const boost::array<uint16_t, 2> data = {{ addr, val }};
        PostCmd(data, FTM::kCmdWrite, FTM::kCmdRegister);

        // Request the changed configuration to ensure the
        // change is distributed in the network
        CmdReqStatDat();

        return true;
    }

    bool CmdGetRegister(uint16_t addr)
    {
        if (addr>FTM::StaticData::kMaxAddr)
            return false;

        const boost::array<uint16_t, 1> data = {{ addr }};
        PostCmd(data, FTM::kCmdRead, FTM::kCmdRegister);

        return true;
    }

    bool CmdResetCrate(uint16_t addr)
    {
        if (addr>3)
            return false;

        PostCmd(FTM::kCmdCrateReset, 1<<addr);

        return true;
    }

    bool CmdResetCamera()
    {
        PostCmd(FTM::kCmdCrateReset, FTM::kResetCrate0);
        PostCmd(FTM::kCmdCrateReset, FTM::kResetCrate1);
        PostCmd(FTM::kCmdCrateReset, FTM::kResetCrate2);
        PostCmd(FTM::kCmdCrateReset, FTM::kResetCrate3);

        return true;
    }

    bool CmdDisableReports(bool b)
    {
        PostCmd(FTM::kCmdDisableReports, b ? uint16_t(0) : uint16_t(1));
        return true;
    }

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

    void SetHexOutput(bool b)
    {
        fIsHexOutput = b;
    }

    void SetDynamicOut(bool b)
    {
        fIsDynamicOut = b;
    }
/*
    void SetDefaultSetup(const string &file)
    {
        fDefaultSetup = file;
    }
*/
    bool LoadStaticData(string name)
    {
        if (name.rfind(".bin")!=name.length()-4)
            name += ".bin";

        ifstream fin(name);
        if (!fin)
            return false;

        FTM::StaticData data;

        fin.read(reinterpret_cast<char*>(&data), sizeof(FTM::StaticData));

        if (fin.gcount()<streamsize(sizeof(FTM::StaticData)))
            return false;

        if (fin.fail() || fin.eof())
            return false;

        if (fin.peek()!=-1)
            return false;

        fStaticData = data;

        CmdSendStatDat();

        return true;
    }

    bool SaveStaticData(string name) const
    {
        if (name.rfind(".bin")!=name.length()-4)
            name += ".bin";

        ofstream fout(name);
        if (!fout)
            return false;

        fout.write(reinterpret_cast<const char*>(&fStaticData), sizeof(FTM::StaticData));

        return !fout.bad();
    }

    bool SetThreshold(int32_t patch, int32_t value)
    {
        if (patch>159)
            return false;

        if (value<0 || value>0xffff)
            return false;

        if (patch<0)
        {
            bool ident = true;
            for (int i=0; i<160; i++)
                if (fStaticData[i/4].fDAC[patch%4] != value)
                {
                    ident = false;
                    break;
                }

            if (ident)
                return true;

            for (int i=0; i<160; i++)
                fStaticData[i/4].fDAC[i%4] = value;
        }
        else
        {
            if (fStaticData[patch/4].fDAC[patch%4] == value)
                return true;

            fStaticData[patch/4].fDAC[patch%4] = value;
        }

        // Maybe move to a "COMMIT" command?
        CmdSendStatDat();

        return true;
    }

    bool SetPrescaling(uint32_t value)
    {
        if (value>0xffff)
            return false;

        bool ident = true;
        for (int i=0; i<40; i++)
            if (fStaticData[i].fPrescaling != value)
            {
                ident = false;
                break;
            }

        if (ident)
            return true;

        for (int i=0; i<40; i++)
            fStaticData[i].fPrescaling = value;

        // Maybe move to a "COMMIT" command?
        CmdSendStatDat();

        return true;
    }

    bool EnableFTU(int32_t board, bool enable)
    {
        if (board>39)
            return false;

        if (board<0)
        {
            if (enable)
                fStaticData.EnableAllFTU();
            else
                fStaticData.DisableAllFTU();
        }
        else
        {
            if (enable)
                fStaticData.EnableFTU(board);
            else
                fStaticData.DisableFTU(board);

        }

        // Maybe move to a "COMMIT" command?
        CmdSendStatDat();

        return true;
    }

    bool ToggleFTU(uint32_t board)
    {
        if (board>39)
            return false;

        fStaticData.ToggleFTU(board);

        // Maybe move to a "COMMIT" command?
        CmdSendStatDat();

        return true;
    }

    bool SetVal(uint16_t *dest, uint32_t val, uint32_t max)
    {
        if (val>max)
            return false;

        if (*dest==val)
            return true;

        *dest = val;

        CmdSendStatDat();

        return true;
    }

    bool SetTriggerInterval(uint32_t val)
    {
        return SetVal(&fStaticData.fTriggerInterval, val,
                      FTM::StaticData::kMaxTriggerInterval);
    }

    bool SetTriggerDelay(uint32_t val)
    {
        return SetVal(&fStaticData.fDelayTrigger, val,
                      FTM::StaticData::kMaxDelayTrigger);
    }

    bool SetTimeMarkerDelay(uint32_t val)
    {
        return SetVal(&fStaticData.fDelayTimeMarker, val,
                      FTM::StaticData::kMaxDelayTimeMarker);
    }

    bool SetDeadTime(uint32_t val)
    {
        return SetVal(&fStaticData.fDeadTime, val,
                      FTM::StaticData::kMaxDeadTime);
    }

    void Enable(FTM::StaticData::GeneralSettings type, bool enable)
    {
        if (fStaticData.IsEnabled(type)!=enable)
        {
            fStaticData.Enable(type, enable);
            CmdSendStatDat();
        }
    }

    bool SetTriggerSeq(const uint16_t d[3])
    {
        const uint16_t oldset = fStaticData.fGeneralSettings;
        const uint16_t oldseq = fStaticData.fTriggerSequence;

	if (d[0]>FTM::StaticData::kMaxSequence ||
            d[1]>FTM::StaticData::kMaxSequence ||
            d[2]>FTM::StaticData::kMaxSequence)
            return false;

        fStaticData.Enable(FTM::StaticData::kPedestal, d[0]>0);
        fStaticData.Enable(FTM::StaticData::kLPext,    d[1]>0);
        fStaticData.Enable(FTM::StaticData::kLPint,    d[2]>0);

        fStaticData.fTriggerSequence =
            (uint16_t(d[0])<<10) | (uint16_t(d[2])<<5) | uint16_t(d[1]);

        if (oldseq!=fStaticData.fTriggerSequence || oldset!=fStaticData.fGeneralSettings)
            CmdSendStatDat();

        return true;
    }

    bool SetTriggerMultiplicity(uint16_t n)
    {
        if (n==0 || n>FTM::StaticData::kMaxMultiplicity)
            return false;

        if (n==fStaticData.fMultiplicityPhysics)
            return true;

        fStaticData.fMultiplicityPhysics = n;

        CmdSendStatDat();

        return true;
    }

    bool SetTriggerWindow(uint16_t win)
    {
        if (win>FTM::StaticData::kMaxWindow)
            return false;

        if (win==fStaticData.fWindowPhysics)
            return true;

        fStaticData.fWindowPhysics = win;

        CmdSendStatDat();

        return true;
    }

    bool SetCalibMultiplicity(uint16_t n)
    {
        if (n==0 || n>FTM::StaticData::kMaxMultiplicity)
            return false;

        if (n==fStaticData.fMultiplicityCalib)
            return true;

        fStaticData.fMultiplicityCalib = n;

        CmdSendStatDat();

        return true;
    }

    bool SetCalibWindow(uint16_t win)
    {
        if (win>FTM::StaticData::kMaxWindow)
            return false;

        if (win==fStaticData.fWindowCalib)
            return true;

        fStaticData.fWindowCalib = win;

        CmdSendStatDat();

        return true;
    }

    bool EnablePixel(int16_t idx, bool enable)
    {
        if (idx<-1 || idx>FTM::StaticData::kMaxPixelIdx)
            return false;

        if (idx==-1)
            for (int i=0; i<=FTM::StaticData::kMaxPixelIdx; i++)
                fStaticData.EnablePixel(i, enable);
        else
            fStaticData.EnablePixel(idx, enable);

        CmdSendStatDat();

        return true;
    }

    bool DisableAllPixelsExcept(uint16_t idx)
    {
        if (idx>FTM::StaticData::kMaxPixelIdx)
            return false;

        for (int i=0; i<=FTM::StaticData::kMaxPixelIdx; i++)
            fStaticData.EnablePixel(i, i==idx);

        CmdSendStatDat();

        return true;
    }

    bool DisableAllPatchesExcept(uint16_t idx)
    {
        if (idx>FTM::StaticData::kMaxPatchIdx)
            return false;

        for (int i=0; i<=FTM::StaticData::kMaxPixelIdx; i++)
            fStaticData.EnablePixel(i, i/9==idx);

        CmdSendStatDat();

        return true;
    }

    bool TogglePixel(uint16_t idx)
    {
        if (idx>FTM::StaticData::kMaxPixelIdx)
            return false;

        fStaticData.EnablePixel(idx, !fStaticData.Enabled(idx));

        CmdSendStatDat();

        return true;
    }

    int GetState() const { return IsConnected() ? fState : (int)FTM::kDisconnected; }
};

//const uint16_t ConnectionFTM::kMaxAddr = 0xfff;

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

#include "DimDescriptionService.h"

class ConnectionDimFTM : public ConnectionFTM
{
private:

    DimDescribedService fDimPassport;
    DimDescribedService fDimTriggerCounter;
    DimDescribedService fDimError;
    DimDescribedService fDimFtuList;
    DimDescribedService fDimStaticData;
    DimDescribedService fDimDynamicData;
    DimDescribedService fDimCounter;

    template<class T>
        void Update(DimDescribedService &svc, const T &data) const
    {
        //cout << "Update: " << svc.getName() << " (" << sizeof(T) << ")" << endl;
        svc.setData(const_cast<T*>(&data), sizeof(T));
        svc.updateService();
    }

    void UpdateFirstHeader()
    {
        ConnectionFTM::UpdateFirstHeader();

        const FTM::DimPassport data(fHeader);
        Update(fDimPassport, data);
    }

    void UpdateHeader()
    {
        ConnectionFTM::UpdateHeader();

        if (fHeader.fType!=FTM::kDynamicData)
            return;

        const FTM::DimTriggerCounter data(fHeader);
        Update(fDimTriggerCounter, data);
    }

    void UpdateFtuList()
    {
        ConnectionFTM::UpdateFtuList();

        const FTM::DimFtuList data(fHeader, fFtuList);
        Update(fDimFtuList, data);
    }

    void UpdateStaticData()
    {
        ConnectionFTM::UpdateStaticData();

        const FTM::DimStaticData data(fHeader, fStaticData);
        Update(fDimStaticData, data);
    }

    void UpdateDynamicData()
    {
        ConnectionFTM::UpdateDynamicData();

        const FTM::DimDynamicData data(fHeader, fDynamicData);
        Update(fDimDynamicData, data);
    }

    void UpdateError()
    {
        ConnectionFTM::UpdateError();

        const FTM::DimError data(fHeader, fError);
        Update(fDimError, data);
    }

    void UpdateCounter()
    {
        ConnectionFTM::UpdateCounter();

        const uint32_t counter[6] =
        {
            fCounter[FTM::kHeader],
            fCounter[FTM::kStaticData],
            fCounter[FTM::kDynamicData],
            fCounter[FTM::kFtuList],
            fCounter[FTM::kErrorList],
            fCounter[FTM::kRegister],
        };

        Update(fDimCounter, counter);
    }

public:
    ConnectionDimFTM(ba::io_service& ioservice, MessageImp &imp) :
        ConnectionFTM(ioservice, imp),
        fDimPassport      ("FTM_CONTROL/PASSPORT",        "X:1;S:1", ""),
        fDimTriggerCounter("FTM_CONTROL/TRIGGER_COUNTER", "X:1;I:1", ""),
        fDimError         ("FTM_CONTROL/ERROR",           "X:1;S:1;S:28", ""),
        fDimFtuList       ("FTM_CONTROL/FTU_LIST",        "X:1;X:1;S:1;C:4;X:40;C:40;C:40",  ""),
        fDimStaticData    ("FTM_CONTROL/STATIC_DATA",     "X:1;S:1;S:1;X:1;S:1;S:3;S:1;S:1;S:1;S:1;S:1;S:1;I:1;S:8;S:90;S:160;S:40;S:40", ""),
        fDimDynamicData   ("FTM_CONTROL/DYNAMIC_DATA",    "X:1;X:1;F:4;I:160;I:40;S:40;S:40", ""),
        fDimCounter       ("FTM_CONTROL/COUNTER",         "I:6", "")
    {
    }

    // 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 StateMachineFTM : 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(&StateMachineFTM::Wrap, this, func);
    }

private:
    S fFTM;

    enum states_t
    {
        kStateDisconnected = FTM::kDisconnected,
        kStateConnected    = FTM::kConnected,
        kStateIdle         = FTM::kIdle,
        kStateTakingData   = FTM::kTakingData,

        kCmdTest
    };

    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 << ".";
        T::Fatal(msg);
        return false;
    }

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

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

        if (dat[1]>uint16_t(-1))
        {
            ostringstream msg;
            msg << hex << "Value " << dat[1] << " out of range.";
            T::Error(msg);
            return T::GetCurrentState();
        }


        if (dat[0]>uint16_t(-1) || !fFTM.CmdSetRegister(dat[0], dat[1]))
        {
            ostringstream msg;
            msg << hex << "Address " << dat[0] << " out of range.";
            T::Error(msg);
        }

        return T::GetCurrentState();
    }

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

        const unsigned int addr = evt.GetInt();
        if (addr>uint16_t(-1) || !fFTM.CmdGetRegister(addr))
        {
            ostringstream msg;
            msg << hex << "Address " << addr << " out of range.";
            T::Error(msg);
        }

        return T::GetCurrentState();
    }

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

        const unsigned int dat = evt.GetUInt();

        /*
        if (dat[1]>uint32_t(-1))
        {
            ostringstream msg;
            msg << hex << "Value " << dat[1] << " out of range.";
            T::Error(msg);
            return T::GetCurrentState();
        }*/

        fFTM.CmdTakeNevents(dat);

        return T::GetCurrentState();
    }

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

        fFTM.CmdDisableReports(evt.GetBool());

        return T::GetCurrentState();
    }

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

        fFTM.SetVerbose(evt.GetBool());

        return T::GetCurrentState();
    }

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

        fFTM.SetHexOutput(evt.GetBool());

        return T::GetCurrentState();
    }

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

        fFTM.SetDynamicOut(evt.GetBool());

        return T::GetCurrentState();
    }

    int LoadStaticData(const EventImp &evt)
    {
        if (fFTM.LoadStaticData(evt.GetString()))
            return T::GetCurrentState();

        ostringstream msg;
        msg << "Loading static data from file '" << evt.GetString() << "' failed ";

        if (errno)
            msg << "(" << strerror(errno) << ")";
        else
            msg << "(wrong size, expected " << sizeof(FTM::StaticData) << " bytes)";

        T::Warn(msg);

        return T::GetCurrentState();
    }

    int SaveStaticData(const EventImp &evt)
    {
        if (fFTM.SaveStaticData(evt.GetString()))
            return T::GetCurrentState();

        ostringstream msg;
        msg << "Writing static data to file '" << evt.GetString() << "' failed ";
        msg << "(" << strerror(errno) << ")";

        T::Warn(msg);

        return T::GetCurrentState();
    }

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

        const int32_t *data = evt.Ptr<int32_t>();

        if (!fFTM.SetThreshold(data[0], data[1]))
            T::Warn("SetThreshold - Maximum allowed patch number 159, valid value range 0-0xffff");

        return T::GetCurrentState();
    }

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

        const int32_t &board  = evt.Get<int32_t>();
        const int8_t  &enable = evt.Get<int8_t>(4);

        if (!fFTM.EnableFTU(board, enable))
            T::Warn("EnableFTU - Board number must be <40.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.ToggleFTU(evt.GetInt()))
            T::Warn("ToggleFTU - Allowed range of boards 0-39.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetTriggerInterval(evt.GetInt()))
            T::Warn("SetTriggerInterval - Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetTriggerDelay(evt.GetInt()))
            T::Warn("SetTriggerDealy - Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetTimeMarkerDelay(evt.GetInt()))
            T::Warn("SetTimeMarkerDelay - Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetPrescaling(evt.GetInt()-1))
            T::Warn("SetPrescaling - Value out of range.");

        return T::GetCurrentState();
    }

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

        const uint16_t *data = evt.Ptr<uint16_t>();

        if (!fFTM.SetTriggerSeq(data))
            T::Warn("SetTriggerSeq - Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetDeadTime(evt.GetInt()))
            T::Warn("SetDeadTime - Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetTriggerMultiplicity(evt.GetUShort()))
            T::Warn("SetTriggerMultiplicity -  Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetCalibMultiplicity(evt.GetUShort()))
            T::Warn("SetCalibMultiplicity -  Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetTriggerWindow(evt.GetUShort()))
            T::Warn("SetTriggerWindow -  Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.SetCalibWindow(evt.GetUShort()))
            T::Warn("SetCalibWindow -  Value out of range.");

        return T::GetCurrentState();
    }


    int Enable(const EventImp &evt, FTM::StaticData::GeneralSettings type)
    {
        if (!CheckEventSize(evt.GetSize(), "Enable", 1))
            return T::kSM_FatalError;

        fFTM.Enable(type, evt.GetBool());

        return T::GetCurrentState();
    }

    int EnablePixel(const EventImp &evt, bool b)
    {
        if (!CheckEventSize(evt.GetSize(), "EnablePixel", 2))
            return T::kSM_FatalError;

        if (!fFTM.EnablePixel(evt.GetUShort(), b))
            T::Warn("EnablePixel -  Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.DisableAllPixelsExcept(evt.GetUShort()))
            T::Warn("DisableAllPixelsExcept -  Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.DisableAllPatchesExcept(evt.GetUShort()))
            T::Warn("DisableAllPatchesExcept -  Value out of range.");

        return T::GetCurrentState();
    }

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

        if (!fFTM.TogglePixel(evt.GetUShort()))
            T::Warn("TogglePixel -  Value out of range.");

        return T::GetCurrentState();
    }

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

        fFTM.CmdResetCrate(evt.GetUShort());

        return T::GetCurrentState();
    }

    int Disconnect()
    {
        // Close all connections
        fFTM.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
        fFTM.PostClose(false);

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

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

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

        return T::GetCurrentState();
    }

    /*
    int Transition(const Event &evt)
    {
        switch (evt.GetTargetState())
        {
        case kStateDisconnected:
        case kStateConnected:
        }

        return T::kSM_FatalError;
    }*/

    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 fFTM.GetState();
    }

public:
    StateMachineFTM(ostream &out=cout) :
        T(out, "FTM_CONTROL"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
        fFTM(*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",
                     "FTM board not connected via ethernet.");

        AddStateName(kStateConnected, "Connected",
                     "Ethernet connection to FTM established (no state received yet).");

        AddStateName(kStateIdle, "Idle",
                     "Ethernet connection to FTM established, FTM in idle state.");

        AddStateName(kStateTakingData, "TakingData",
                     "Ethernet connection to FTM established, FTM is in taking data state.");

        // FTM Commands
        AddEvent("TOGGLE_LED", kStateIdle)
            (Wrapper(boost::bind(&ConnectionFTM::CmdToggleLed, &fFTM)))
            ("toggle led");

        AddEvent("PING", kStateIdle)
            (Wrapper(boost::bind(&ConnectionFTM::CmdPing, &fFTM)))
            ("send ping");

        AddEvent("REQUEST_DYNAMIC_DATA", kStateIdle)
            (Wrapper(boost::bind(&ConnectionFTM::CmdReqDynDat, &fFTM)))
            ("request transmission of dynamic data block");

        AddEvent("REQUEST_STATIC_DATA", kStateIdle)
            (Wrapper(boost::bind(&ConnectionFTM::CmdReqStatDat, &fFTM)))
            ("request transmission of static data from FTM to memory");

        AddEvent("GET_REGISTER", "I", kStateIdle)
            (boost::bind(&StateMachineFTM::GetRegister, this, _1))
            ("read register from address addr"
            "|addr[short]:Address of register");

        AddEvent("SET_REGISTER", "I:2", kStateIdle)
            (boost::bind(&StateMachineFTM::SetRegister, this, _1))
            ("set register to value"
            "|addr[short]:Address of register"
            "|val[short]:Value to be set");

        AddEvent("START_RUN", kStateIdle)
            (Wrapper(boost::bind(&ConnectionFTM::CmdStartRun, &fFTM)))
            ("start a run (start distributing triggers)");

        AddEvent("STOP_RUN", kStateTakingData)
            (Wrapper(boost::bind(&ConnectionFTM::CmdStopRun, &fFTM)))
            ("stop a run (stop distributing triggers)");

        AddEvent("TAKE_N_EVENTS", "I", kStateIdle)
            (boost::bind(&StateMachineFTM::TakeNevents, this, _1))
            ("take n events (distribute n triggers)|number[int]:Number of events to be taken");

        AddEvent("DISABLE_REPORTS", "B", kStateIdle)
            (boost::bind(&StateMachineFTM::DisableReports, this, _1))
            ("disable sending rate reports"
             "|status[bool]:disable or enable that the FTM sends rate reports (yes/no)");

        AddEvent("SET_THRESHOLD", "I:2", kStateIdle)
            (boost::bind(&StateMachineFTM::SetThreshold, this, _1))
            ("Set the comparator threshold"
             "|Patch[idx]:Index of the patch (0-159), -1 for all"
             "|Threshold[counts]:Threshold to be set in binary counts");

        AddEvent("SET_PRESCALING", "I:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetPrescaling, this, _1))
            (""
             "|[]:");

        AddEvent("ENABLE_FTU", "I:1;B:1", kStateIdle)
            (boost::bind(&StateMachineFTM::EnableFTU, this, _1))
            ("Enable or disable FTU"
             "|Board[idx]:Index of the board (0-39), -1 for all"
             "|Enable[bool]:Whether FTU should be enabled or disabled (yes/no)");

        AddEvent("DISABLE_PIXEL", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::EnablePixel, this, _1, false))
            ("(-1 or all)");

        AddEvent("ENABLE_PIXEL", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::EnablePixel, this, _1, true))
            ("(-1 or all)");

        AddEvent("DISABLE_ALL_PIXELS_EXCEPT", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::DisableAllPixelsExcept, this, _1))
            ("");

        AddEvent("DISABLE_ALL_PATCHES_EXCEPT", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::DisableAllPatchesExcept, this, _1))
            ("");

        AddEvent("TOGGLE_PIXEL", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::TogglePixel, this, _1))
            ("");

        AddEvent("TOGGLE_FTU", "I:1", kStateIdle)
            (boost::bind(&StateMachineFTM::ToggleFTU, this, _1))
            ("Toggle status of FTU (this is mainly meant to be used in the GUI)"
             "|Board[idx]:Index of the board (0-39)");

        AddEvent("SET_TRIGGER_INTERVAL", "I:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetTriggerInterval, this, _1))
            ("Sets the trigger interval which is the distance between two consecutive artificial triggers."
             "|interval[int]:The applied trigger interval is: interval*4ns+8ns");

        AddEvent("SET_TRIGGER_DELAY", "I:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetTriggerDelay, this, _1))
            (""
             "|delay[int]:The applied trigger delay is: delay*4ns+8ns");

        AddEvent("SET_TIME_MARKER_DELAY", "I:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetTimeMarkerDelay, this, _1))
            (""
            "|delay[int]:The applied time marker delay is: delay*4ns+8ns");

        AddEvent("SET_DEAD_TIME", "I:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetDeadTime, this, _1))
            (""
            "|dead_time[int]:The applied dead time is: dead_time*4ns+8ns");

        AddEvent("ENABLE_TRIGGER", "B:1", kStateIdle)
            (boost::bind(&StateMachineFTM::Enable, this, _1, FTM::StaticData::kTrigger))
            ("Switch on the physics trigger"
             "|Enable[bool]:Enable physics trigger (yes/no)");

        // FIXME: Switch on/off depending on sequence
        AddEvent("ENABLE_EXT1", "B:1", kStateIdle)
            (boost::bind(&StateMachineFTM::Enable, this, _1, FTM::StaticData::kExt1))
            ("Switch on the triggers through the first external line"
             "|Enable[bool]:Enable ext1 trigger (yes/no)");

        // FIXME: Switch on/off depending on sequence
        AddEvent("ENABLE_EXT2", "B:1", kStateIdle)
            (boost::bind(&StateMachineFTM::Enable, this, _1, FTM::StaticData::kExt2))
            ("Switch on the triggers through the second external line"
             "|Enable[bool]:Enable ext2 trigger (yes/no)");

        AddEvent("ENABLE_VETO", "B:1", kStateIdle)
            (boost::bind(&StateMachineFTM::Enable, this, _1, FTM::StaticData::kVeto))
            ("Enable veto line"
             "|Enable[bool]:Enable veto (yes/no)");

        AddEvent("ENABLE_CLOCK_CONDITIONER", "B:1", kStateIdle)
            (boost::bind(&StateMachineFTM::Enable, this, _1, FTM::StaticData::kClockConditioner))
            ("Enable clock conidtioner output in favor of time marker output"
             "|Enable[bool]:Enable clock conditioner (yes/no)");

        AddEvent("SET_TRIGGER_SEQUENCE", "S:3", kStateIdle)
            (boost::bind(&StateMachineFTM::SetTriggerSeq, this, _1))
            ("Setup the sequence of artificial triggers produced by the FTM"
             "|Ped[short]:number of pedestal triggers in a row"
             "|LPext[short]:number of triggers of the external light pulser"
             "|LPint[short]:number of triggers of the internal light pulser");

        AddEvent("SET_TRIGGER_MULTIPLICITY", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetTriggerMultiplicity, this, _1))
            ("Setup the Multiplicity condition for physcis triggers"
             "|N[int]:Number of requirered coincident triggers from sum-patches (1-40)");

        AddEvent("SET_TRIGGER_WINDOW", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetTriggerWindow, this, _1))
            ("");

        AddEvent("SET_CALIBRATION_MULTIPLICITY", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetCalibMultiplicity, this, _1))
            ("Setup the Multiplicity condition for artificial (calibration) triggers"
             "|N[int]:Number of requirered coincident triggers from sum-patches (1-40)");

        AddEvent("SET_CALIBRATION_WINDOW", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::SetCalibWindow, this, _1))
            ("");

        AddEvent("RESET_CRATE", "S:1", kStateIdle)
            (boost::bind(&StateMachineFTM::ResetCrate, this, _1))
            ("Reset one of the crates 0-3"
             "|crate[short]:Crate number to be reseted (0-3)");

        AddEvent("RESET_CAMERA", kStateIdle)
            (Wrapper(boost::bind(&ConnectionFTM::CmdResetCamera, &fFTM)))
            ("Reset all crates. The commands are sent in the order 0,1,2,3");


        // Load/save static data block
        T::AddEvent("SAVE", "C", kStateIdle)
            (boost::bind(&StateMachineFTM::SaveStaticData, this, _1))
            ("Saves the static data (FTM configuration) from memory to a file"
             "|filename[string]:Filename (can include a path), .bin is automatically added");

        T::AddEvent("LOAD", "C", kStateIdle)
            (boost::bind(&StateMachineFTM::LoadStaticData, this, _1))
            ("Loads the static data (FTM configuration) from a file into memory and sends it to the FTM"
             "|filename[string]:Filename (can include a path), .bin is automatically added");



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

        T::AddEvent("SET_HEX_OUTPUT", "B")
            (boost::bind(&StateMachineFTM::SetHexOutput, this, _1))
            ("enable or disable hex output for received data"
             "|hexout[bool]:disable or enable hex output for received data (yes/no)");

        T::AddEvent("SET_DYNAMIC_OUTPUT", "B")
            (boost::bind(&StateMachineFTM::SetDynamicOut, this, _1))
            ("enable or disable output for received dynamic data (data is still broadcasted via Dim)"
             "|dynout[bool]:disable or enable output for dynamic data (yes/no)");


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

        AddEvent("RECONNECT", "O", kStateDisconnected, kStateConnected, kStateIdle)
            (boost::bind(&StateMachineFTM::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>");

        fFTM.StartConnect();
    }

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

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

        fFTM.SetVerbose(!conf.Get<bool>("quiet"));
        fFTM.SetHexOutput(conf.Get<bool>("hex-out"));
        fFTM.SetDynamicOut(conf.Get<bool>("dynamic-out"));

//        fFTM.SetDefaultSetup(conf.Get<string>("default-setup"));

        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
    StateMachineFTM<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;

    StateMachineFTM<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(&StateMachineFTM<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_bool(),      "Disable dim services")
        ("console,c", var<int>(),     "Use console (0=shell, 1=simple buffered, X=simple unbuffered)")
        ("exec,e",    vars<string>(), "Execute one or more scrips at startup")
        ;

    po::options_description control("FTM control options");
    control.add_options()
        ("addr,a",        var<string>("localhost:5000"),  "Network address of FTM")
        ("quiet,q",       po_bool(),  "Disable printing contents of all received messages (except dynamic data) in clear text.")
        ("hex-out",       po_bool(),  "Enable printing contents of all printed messages also as hex data.")
        ("dynamic-out",   po_bool(),  "Enable printing received dynamic data.")
//        ("default-setup", var<string>(), "Binary file with static data loaded whenever a connection to the FTM was established.")
        ;

    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 ftmctrl controls the FTM (FACT Trigger Master) board.\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: ftmctrl [-c type] [OPTIONS]\n"
        "  or:  ftmctrl [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, ConnectionFTM>(conf);
            else
                return RunShell<LocalStream, StateMachineDim, ConnectionDimFTM>(conf);
        }
        // Cosole access w/ and w/o Dim
        if (conf.Get<bool>("no-dim"))
        {
            if (conf.Get<int>("console")==0)
                return RunShell<LocalShell, StateMachine, ConnectionFTM>(conf);
            else
                return RunShell<LocalConsole, StateMachine, ConnectionFTM>(conf);
        }
        else
        {
            if (conf.Get<int>("console")==0)
                return RunShell<LocalShell, StateMachineDim, ConnectionDimFTM>(conf);
            else
                return RunShell<LocalConsole, StateMachineDim, ConnectionDimFTM>(conf);
        }
    }
    /*catch (std::exception& e)
    {
        cerr << "Exception: " << e.what() << endl;
        return -1;
    }*/

    return 0;
}
