#include <functional>

#include "Dim.h"
#include "Event.h"
#include "Shell.h"
#include "StateMachineDim.h"
#include "Connection.h"
#include "LocalControl.h"
#include "Configuration.h"
#include "Console.h"
#include "Converter.h"
#include "HeadersFAD.h"

#include "tools.h"

#include "DimDescriptionService.h"
#include "EventBuilderWrapper.h"

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

using ba::ip::tcp;

using namespace std;

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

class ConnectionFAD : public Connection
{
    uint16_t fSlot;
//    tcp::endpoint fEndpoint;

    vector<uint16_t> fBuffer;

protected:
    FAD::EventHeader   fEventHeader;
    FAD::ChannelHeader fChannelHeader[FAD::kNumChannels];

private:
    bool fIsVerbose;
    bool fIsHexOutput;
    bool fIsDataOutput;
    bool fBlockTransmission;

    uint64_t fCounter;

    FAD::EventHeader fBufEventHeader;
    vector<uint16_t> fTargetRoi;

protected:
    void PrintEventHeader()
    {
        Out() << endl << kBold << "Header received (N=" << dec << fCounter << "):" << endl;
        Out() << fEventHeader;
        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fEventHeader, 16) << endl;
    }

    void PrintChannelHeaders()
    {
        Out() << dec << endl;

        for (unsigned int c=0; c<FAD::kNumChips; c++)
        {
            Out() << "ROI|" << fEventHeader.Crate() << ":" << fEventHeader.Board() << ":" << c << ":";
            for (unsigned int ch=0; ch<FAD::kNumChannelsPerChip; ch++)
		Out() << " " << setw(4) << fChannelHeader[c+ch*FAD::kNumChips].fRegionOfInterest;
            Out() << endl;
        }

        Out() << "CEL|" << fEventHeader.Crate() << ":" <<fEventHeader.Board() << ": ";
        for (unsigned int c=0; c<FAD::kNumChips; c++)
        {
            if (0)//fIsFullChannelHeader)
            {
                for (unsigned int ch=0; ch<FAD::kNumChannelsPerChip; ch++)
                    Out() << " " << setw(4) << fChannelHeader[c+ch*FAD::kNumChips].fStartCell;
                Out() << endl;
            }
            else
            {
                Out() << " ";
                const uint16_t cel = fChannelHeader[c*FAD::kNumChannelsPerChip].fStartCell;
                for (unsigned int ch=1; ch<FAD::kNumChannelsPerChip; ch++)
                    if (cel!=fChannelHeader[c+ch*FAD::kNumChips].fStartCell)
                    {
                        Out() << "!";
                        break;
                    }
                Out() << cel;
            }
        }
        Out() << endl;

        if (fIsHexOutput)
            Out() << Converter::GetHex<uint16_t>(fChannelHeader, 16) << endl;

    }

    virtual void UpdateFirstHeader()
    {
    }

    virtual void UpdateEventHeader()
    {
        // emit service with trigger counter from header
        if (fIsVerbose)
            PrintEventHeader();
    }

    virtual void UpdateChannelHeaders()
    {
        // emit service with trigger counter from header
        if (fIsVerbose)
            PrintChannelHeaders();

    }

    virtual void UpdateData(const uint16_t *data, size_t sz)
    {
        // emit service with trigger counter from header
        if (fIsVerbose && fIsDataOutput)
            Out() << Converter::GetHex<uint16_t>(data, sz, 16, true) << endl;
    }

private:
    enum
    {
        kReadHeader = 1,
        kReadData   = 2,
    };

    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 to "+URL()+" closed by remote host (FAD).");

            // 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;
        }

        EventBuilderWrapper::This->debugStream(fSlot*7, fBuffer.data(), bytes_received);

        if (type==kReadHeader)
        {
            if (bytes_received!=sizeof(FAD::EventHeader))
            {
                ostringstream str;
                str << "Bytes received (" << bytes_received << " don't match header size " << sizeof(FAD::EventHeader);
                Error(str);
                PostClose(false);
                return;
            }

            fEventHeader = fBuffer;

            if (fEventHeader.fStartDelimiter!=FAD::kDelimiterStart)
            {
                ostringstream str;
                str << "Invalid header received: start delimiter wrong, received ";
                str << hex << fEventHeader.fStartDelimiter << ", expected " << FAD::kDelimiterStart << ".";
                Error(str);
                PostClose(false);
                return;
            }

            if (fCounter==0)
                UpdateFirstHeader();

            UpdateEventHeader();

            EventBuilderWrapper::This->debugHead(fSlot*7, fEventHeader);

            fBuffer.resize(fEventHeader.fPackageLength-sizeof(FAD::EventHeader)/2);
            AsyncRead(ba::buffer(fBuffer), kReadData);
            AsyncWait(fInTimeout, 2000, &Connection::HandleReadTimeout);

            return;
        }

        fInTimeout.cancel();

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

        uint8_t *ptr = reinterpret_cast<uint8_t*>(fBuffer.data());
        uint8_t *end = ptr + fBuffer.size()*2;
        for (unsigned int i=0; i<FAD::kNumChannels; i++)
        {
            if (ptr+sizeof(FAD::ChannelHeader) > end)
            {
                Error("Channel header exceeds buffer size.");
                PostClose(false);
                return;
            }

            fChannelHeader[i] = vector<uint16_t>((uint16_t*)ptr, (uint16_t*)ptr+sizeof(FAD::ChannelHeader)/2);
            ptr += sizeof(FAD::ChannelHeader);

            //UpdateChannelHeader(i);

            if (ptr+fChannelHeader[i].fRegionOfInterest*2 > end)
            {
                Error("Data block exceeds buffer size.");
                PostClose(false);
                return;
            }

            const uint16_t *data = reinterpret_cast<uint16_t*>(ptr);
            UpdateData(data, fChannelHeader[i].fRegionOfInterest*2);
            ptr += fChannelHeader[i].fRegionOfInterest*2;
        }

        if (fIsVerbose)
            UpdateChannelHeaders();

        fCounter++;

        fBuffer.resize(sizeof(FAD::EventHeader)/2);
        AsyncRead(ba::buffer(fBuffer), kReadHeader);
    }

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

    // This is called when a connection was established
    void ConnectionEstablished()
    {
        fBufEventHeader.clear();
        fBufEventHeader.fEventCounter = 1;
        fBufEventHeader.fStatus = 0xf000|
            FAD::EventHeader::kDenable|
            FAD::EventHeader::kDwrite|
            FAD::EventHeader::kDcmLocked|
            FAD::EventHeader::kDcmReady|
            FAD::EventHeader::kSpiSclk;

        fEventHeader.clear();
        for (unsigned int i=0; i<FAD::kNumChannels; i++)
            fChannelHeader[i].clear();

        fCounter = 0;

        fBuffer.resize(sizeof(FAD::EventHeader)/2);
        AsyncRead(ba::buffer(fBuffer), kReadHeader);

//        for (int i=0; i<36; i++)
//            CmdSetRoi(i, 100);

//        Cmd(FAD::kCmdTriggerLine, true);
//        Cmd(FAD::kCmdSingleTrigger);
    }

public:
    void PostCmd(std::vector<uint16_t> cmd)
    {
        if (fBlockTransmission || !IsConnected())
            return;

#ifdef DEBUG_TX
        ostringstream msg;
        msg << "Sending command:" << hex;
        msg << " 0x" << setw(4) << setfill('0') << cmd[0];
        msg << " (+ " << cmd.size()-1 << " bytes data)";
        Message(msg);
#endif
        transform(cmd.begin(), cmd.end(), cmd.begin(), htons);

        PostMessage(cmd);
    }

    void PostCmd(uint16_t cmd)
    {
        if (fBlockTransmission || !IsConnected())
            return;

#ifdef DEBUG_TX
        ostringstream msg;
        msg << "Sending command:" << hex;
        msg << " 0x" << setw(4) << setfill('0') << cmd;
        Message(msg);
#endif
        cmd = htons(cmd);
        PostMessage(&cmd, sizeof(uint16_t));
    }

    void PostCmd(uint16_t cmd, uint16_t data)
    {
        if (fBlockTransmission || !IsConnected())
            return;

#ifdef DEBUG_TX
        ostringstream msg;
        msg << "Sending command:" << hex;
        msg << " 0x" << setw(4) << setfill('0') << cmd;
        msg << " 0x" << setw(4) << setfill('0') << data;
        Message(msg);
#endif
        const uint16_t d[2] = { htons(cmd), htons(data) };
        PostMessage(d, sizeof(d));
    }

public:
    ConnectionFAD(ba::io_service& ioservice, MessageImp &imp, uint16_t slot) :
        Connection(ioservice, imp()), fSlot(slot),
        fIsVerbose(false), fIsHexOutput(false), fIsDataOutput(false),
        fBlockTransmission(false), fCounter(0),
        fTargetRoi(FAD::kNumChannels)
    {
        // Maximum possible needed space:
        // The full header, all channels with all DRS bins
        // Two trailing shorts
        fBuffer.reserve(sizeof(FAD::EventHeader) + FAD::kNumChannels*(sizeof(FAD::ChannelHeader) + FAD::kMaxBins*sizeof(uint16_t)) + 2*sizeof(uint16_t));

        SetLogStream(&imp);
    }

    void Cmd(FAD::Enable cmd, bool on=true)
    {
        switch (cmd)
        {
        case FAD::kCmdDrsEnable:   fBufEventHeader.Enable(FAD::EventHeader::kDenable,     on);  break;
        case FAD::kCmdDwrite:      fBufEventHeader.Enable(FAD::EventHeader::kDwrite,      on);  break;
        case FAD::kCmdTriggerLine: fBufEventHeader.Enable(FAD::EventHeader::kTriggerLine, on);  break;
        case FAD::kCmdBusyOn:      fBufEventHeader.Enable(FAD::EventHeader::kBusyOn,      on);  break;
        case FAD::kCmdBusyOff:     fBufEventHeader.Enable(FAD::EventHeader::kBusyOff,     on);  break;
        case FAD::kCmdContTrigger: fBufEventHeader.Enable(FAD::EventHeader::kContTrigger, on);  break;
        case FAD::kCmdSocket:      fBufEventHeader.Enable(FAD::EventHeader::kSock17,      !on); break;
        default:
            break;
        }

        PostCmd(cmd + (on ? 0 : 0x100));
    }

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

    // IMPLEMENT: Abs/Rel
    void CmdPhaseShift(int16_t val)
    {
        vector<uint16_t> cmd(abs(val)+2, FAD::kCmdPhaseApply);
        cmd[0] = FAD::kCmdPhaseReset;
        cmd[1] = val<0 ? FAD::kCmdPhaseDecrease : FAD::kCmdPhaseIncrease;
        PostCmd(cmd);
    }

    bool CmdSetTriggerRate(int32_t val)
    {
        if (val<0 || val>0xffff)
            return false;

        fBufEventHeader.fTriggerGeneratorPrescaler = val;
        PostCmd(FAD::kCmdWriteRate, val);//uint8_t(1000./val/12.5));
        //PostCmd(FAD::kCmdWriteExecute);

        return true;
    }

    void CmdSetRunNumber(uint32_t num)
    {
        fBufEventHeader.fRunNumber = num;

        PostCmd(FAD::kCmdWriteRunNumberLSW, num&0xffff);
        PostCmd(FAD::kCmdWriteRunNumberMSW, num>>16);
        PostCmd(FAD::kCmdWriteExecute);
    }

    void CmdSetRegister(uint8_t addr, uint16_t val)
    {
        // Allowed addr:  [0, MAX_ADDR]
        // Allowed value: [0, MAX_VAL]
        PostCmd(FAD::kCmdWrite + addr, val);
        PostCmd(FAD::kCmdWriteExecute);
    }

    bool CmdSetDacValue(int8_t addr, uint16_t val)
    {
        if (addr<0)
        {
            for (unsigned int i=0; i<=FAD::kMaxDacAddr; i++)
            {
                fBufEventHeader.fDac[i] = val;
                PostCmd(FAD::kCmdWriteDac + i, val);
            }
            PostCmd(FAD::kCmdWriteExecute);
            return true;
        }

        if (uint8_t(addr)>FAD::kMaxDacAddr) // NDAC
            return false;

        fBufEventHeader.fDac[addr] = val;

        PostCmd(FAD::kCmdWriteDac + addr, val);
        PostCmd(FAD::kCmdWriteExecute);
        return true;
    }

    bool CmdSetRoi(int8_t addr, uint16_t val)
    {
        if (val>FAD::kMaxRoiValue)
            return false;

        if (addr<0)
        {
            for (unsigned int i=0; i<=FAD::kMaxRoiAddr; i++)
            {
                fTargetRoi[i] = val;
                PostCmd(FAD::kCmdWriteRoi + i, val);
            }
            PostCmd(FAD::kCmdWriteExecute);
            return true;
        }

        if (uint8_t(addr)>FAD::kMaxRoiAddr)
            return false;

        fTargetRoi[addr] = val;

        PostCmd(FAD::kCmdWriteRoi + addr, val);
        PostCmd(FAD::kCmdWriteExecute);
        return true;
    }

    bool CmdSetRoi(uint16_t val) { return CmdSetRoi(-1, val); }

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

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

    void SetDataOutput(bool b)
    {
        fIsDataOutput = b;
    }

    void SetBlockTransmission(bool b)
    {
        fBlockTransmission = b;
    }

    bool IsTransmissionBlocked() const
    {
        return fBlockTransmission;
    }

    void PrintEvent()
    {
        if (fCounter>0)
        {
            PrintEventHeader();
            PrintChannelHeaders();
        }
        else
            Out() << "No event received yet." << endl;
    }

    bool HasCorrectRoi() const
    {
        for (int i=0; i<FAD::kNumChannels; i++)
            if (fTargetRoi[i]!=fChannelHeader[i].fRegionOfInterest)
                return false;

        return true;
    }

    bool HasCorrectHeader() const
    {
        return fEventHeader==fBufEventHeader;
    }

    bool IsConfigured() const
    {
        return HasCorrectRoi() && HasCorrectHeader();
    }

    void PrintCheckHeader()
    {
        Out() << "================================================================================" << endl;
        fEventHeader.print(Out());
        Out() << "--------------------------------------------------------------------------------" << endl;
        fBufEventHeader.print(Out());
        Out() << "================================================================================" << endl;
    }

    const FAD::EventHeader &GetConfiguration() const { return fBufEventHeader; }
};

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

template <class T>
class StateMachineFAD : public T, public EventBuilderWrapper, public ba::io_service, public ba::io_service::work
{
private:
    typedef map<uint8_t, ConnectionFAD*> BoardList;

    BoardList fBoards;

    bool fIsVerbose;
    bool fIsHexOutput;
    bool fIsDataOutput;
    bool fDebugTx;

    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 Cmd(FAD::Enable command)
    {
        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->Cmd(command);

        return T::GetCurrentState();
    }

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

        if (evt.GetUInt()>0xffff)
        {
            T::Warn("Command value out of range (0-65535).");
            return T::GetCurrentState();
        }

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->PostCmd(evt.GetUInt());

        return T::GetCurrentState();
    }

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

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

        if (ptr[0]>0xffff)
        {
            T::Warn("Command value out of range (0-65535).");
            return T::GetCurrentState();
        }

        if (ptr[1]>0xffff)
        {
            T::Warn("Data value out of range (0-65535).");
            return T::GetCurrentState();
        }

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->PostCmd(ptr[0], ptr[1]);

        return T::GetCurrentState();
    }

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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->Cmd(command, evt.GetBool());

        return T::GetCurrentState();
    }

    bool Check(const uint32_t *dat, uint32_t maxaddr, uint32_t maxval)
    {
        if (dat[0]>maxaddr)
        {
            ostringstream msg;
            msg << hex << "Address " << dat[0] << " out of range, max=" << maxaddr << ".";
            T::Error(msg);
            return false;
        }

        if (dat[1]>maxval)
        {
            ostringstream msg;
            msg << hex << "Value " << dat[1] << " out of range, max=" << maxval << ".";
            T::Error(msg);
            return false;
        }

        return true;
    }

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

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

        if (!Check(dat, FAD::kMaxRegAddr, FAD::kMaxRegValue))
            return T::GetCurrentState();

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->CmdSetRegister(dat[0], dat[1]);

        return T::GetCurrentState();
    }

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

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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            if (!i->second->CmdSetRoi(dat[0], dat[1]))
            {
                ostringstream msg;
                msg << hex << "Channel " << dat[0] << " or Value " << dat[1] << " out of range.";
                T::Error(msg);
                return false;
            }


        return T::GetCurrentState();
    }

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

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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            if (!i->second->CmdSetDacValue(dat[0], dat[1]))
            {
                ostringstream msg;
                msg << hex << "Channel " << dat[0] << " or Value " << dat[1] << " out of range.";
                T::Error(msg);
                return false;
            }

        return T::GetCurrentState();
    }

    int Trigger(int n)
    {
        for (int nn=0; nn<n; nn++)
            for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
                i->second->Cmd(FAD::kCmdSingleTrigger);

        return T::GetCurrentState();
    }

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

        Trigger(evt.GetUInt());

        return T::GetCurrentState();
    }

    int StartRun(const EventImp &evt, bool start)
    {
        if (!CheckEventSize(evt.GetSize(), "StartRun", 0))
            return T::kSM_FatalError;

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->Cmd(FAD::kCmdRun, start);

        return T::GetCurrentState();
    }

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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->CmdPhaseShift(evt.GetShort());

        return T::GetCurrentState();
    }

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

        if (evt.GetUInt()>0xffff)
        {
            ostringstream msg;
            msg << hex << "Value " << evt.GetUShort() << " out of range, max=" << 0xffff << "(?)";
            T::Error(msg);
            return false;
        }

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->CmdSetTriggerRate(evt.GetUInt());

        return T::GetCurrentState();
    }

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

        const uint64_t num = evt.GetUXtra();

        if (num>FAD::kMaxRunNumber)
        {
            ostringstream msg;
            msg << "Run number " << num << " out of range (max=" << FAD::kMaxRunNumber << ")";
            T::Error(msg);
            return false;
        }

        if (num>0 && num<GetRunNumber())
        {
            ostringstream msg;
            msg << "Given run number (" << num << ") smaller than next run number (" << GetRunNumber() << ") which will be opened by the event builder";
            T::Error(msg);
            return false;
        }

        IncreaseRunNumber(num);
 
        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->CmdSetRunNumber(num);

        return T::GetCurrentState();
    }

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

        const int16_t mem = evt.GetShort();

        if (mem<=0)
        {
            ostringstream msg;
            msg << hex << "Value " << mem << " out of range.";
            T::Error(msg);
            return false;
        }

        SetMaxMemory(mem);

        return T::GetCurrentState();
    }

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

        const uint16_t fmt = evt.GetUShort();

        // A simple way to make sure that no invalid file format
        // is passed to the event builder
	switch (fmt)
	{
        case FAD::kNone:
        case FAD::kDebug:
        case FAD::kFits:
        case FAD::kCfitsio:
        case FAD::kRaw:
        case FAD::kCalib:
            SetOutputFormat(FAD::FileFormat_t(fmt));
            break;
	default:
            T::Error("File format unknonw.");
            return false;
        }

        return T::GetCurrentState();
    }

    int StartDrsCalibration()
    {
        SetOutputFormat(FAD::kCalib);
        return T::GetCurrentState();
    }

    int ResetSecondaryDrsBaseline()
    {
        EventBuilderWrapper::ResetSecondaryDrsBaseline();
        return T::GetCurrentState();
    }

    int LoadDrsCalibration(const EventImp &evt)
    {
        EventBuilderWrapper::LoadDrsCalibration(evt.GetText());
        return T::GetCurrentState();
    }

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


        SetMode(evt.GetShort());

        return T::GetCurrentState();
    }


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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->SetVerbose(evt.GetBool());

        return T::GetCurrentState();
    }

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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->SetHexOutput(evt.GetBool());

        return T::GetCurrentState();
    }

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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->SetDataOutput(evt.GetBool());

        return T::GetCurrentState();
    }

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

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            i->second->SetDebugTx(evt.GetBool());

        return T::GetCurrentState();
    }

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

        SetDebugLog(evt.GetBool());

        return T::GetCurrentState();
    }

    const BoardList::iterator GetSlot(uint16_t slot)
    {
        const BoardList::iterator it=fBoards.find(slot);
        if (it==fBoards.end())
        {
            ostringstream str;
            str << "Slot " << slot << " not found.";
            T::Warn(str);
        }

        return it;
    }

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

        const int16_t slot = evt.Get<int16_t>();

        if (slot<0)
        {
            for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
                i->second->PrintEvent();
        }
        else
        {
            const BoardList::iterator it=GetSlot(slot);
            if (it!=fBoards.end())
                it->second->PrintEvent();
        }

        return T::GetCurrentState();
    }

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

        const int16_t slot = evt.Get<int32_t>();

        const BoardList::iterator it=GetSlot(slot);
        if (it!=fBoards.end())
            it->second->SetBlockTransmission(evt.Get<uint8_t>(2));

        return T::GetCurrentState();
    }

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

        const int16_t *slot  = evt.Ptr<int16_t>();
        const bool     block = evt.Get<uint8_t>(4);

        for (int i=slot[0]; i<=slot[1]; i++)
        {
            const BoardList::iterator it=GetSlot(i);
            if (it!=fBoards.end())
                it->second->SetBlockTransmission(block);
        }

        return T::GetCurrentState();
    }

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

        const uint16_t slot = evt.Get<uint16_t>();

        if (slot>39)
        {
            T::Warn("Slot out of range (0-39).");
            return T::GetCurrentState();
        }

        SetIgnore(slot, evt.Get<uint8_t>(2));

        return T::GetCurrentState();
    }

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

        const int16_t *slot  = evt.Ptr<int16_t>();
        const bool     block = evt.Get<uint8_t>(4);

        if (slot[0]<0 || slot[1]>39 || slot[0]>slot[1])
        {
            T::Warn("Slot out of range.");
            return T::GetCurrentState();
        }

        for (int i=slot[0]; i<=slot[1]; i++)
            SetIgnore(i, block);

        return T::GetCurrentState();
    }

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

        SetDebugStream(evt.Get<uint8_t>());

        return T::GetCurrentState();
    }

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

        SetDebugRead(evt.Get<uint8_t>());

        return T::GetCurrentState();
    }

    int StartConfigure(const EventImp &evt)
    {
        const string name = evt.Ptr<char>(16);

        fTargetConfig = fConfigs.find(name);
        if (fTargetConfig==fConfigs.end())
        {
            T::Error("StartConfigure - Run-type '"+name+"' not found.");
            return T::GetCurrentState();
        }

        if (fNightAsInt!=Time().NightAsInt())
        {
            ostringstream out;
            out << "Night changed from " << fNightAsInt << " to " << Time().NightAsInt() << "... determining new run-number.";
            T::Info(out);

            // FIXME: What about an error state?
            fNightAsInt = InitRunNumber();
            if (fNightAsInt<0)
                return FAD::kConnected;
        }

        const uint32_t runno = StartNewRun(evt.Get<uint64_t>(), evt.Get<uint64_t>(8), *fTargetConfig);

        ostringstream str;
        str << "Starting configuration for run " << runno << " (" << name << ")";
        T::Message(str.str());

        if (runno>=1000)
            T::Warn("Run number exceeds logical maximum of 999 - this is no problem for writing but might give raise to problems in the analysis.");

        const FAD::Configuration &conf = fTargetConfig->second;

        for (BoardList::iterator it=fBoards.begin(); it!=fBoards.end(); it++)
        {
            ConnectionFAD &fad  = *it->second;

            fad.Cmd(FAD::kCmdBusyOn,      true);  // continously on
            fad.Cmd(FAD::kCmdTriggerLine, false);
            fad.Cmd(FAD::kCmdContTrigger, false);
            fad.Cmd(FAD::kCmdSocket,      true);
            fad.Cmd(FAD::kCmdBusyOff,     false);  // normal when BusyOn==0

            fad.Cmd(FAD::kCmdDwrite,      conf.fDwrite);
            fad.Cmd(FAD::kCmdDrsEnable,   conf.fDenable);

            for (int i=0; i<FAD::kNumDac; i++)
                fad.CmdSetDacValue(i, conf.fDac[i]);

            for (int i=0; i<FAD::kNumChips; i++)
                for (int j=0; j<FAD::kNumChannelsPerChip; j++)
                    fad.CmdSetRoi(i*FAD::kNumChannelsPerChip+j, conf.fRoi[j]);

            fad.CmdSetTriggerRate(conf.fTriggerRate);
            fad.CmdSetRunNumber(runno);
            fad.Cmd(FAD::kCmdResetEventCounter);
            fad.Cmd(FAD::kCmdTriggerLine, true);
            //fad.Cmd(FAD::kCmdSingleTrigger);
            //fad.Cmd(FAD::kCmdTriggerLine, true);
        }

        // Now the old run is stopped already. So all other servers can start a new run
        // (Note that we might need another step which only checks if the continous trigger
        //  is wwitched off, too)
        const int64_t runs[2] = { runno, runno+1 };
        fDimStartRun.Update(runs);

        T::Info(" ==> TODO: Insert/update run configuration in database!");

        fConfigTimer = Time();

        return FAD::kConfiguring1;
    }

    int ResetConfig()
    {
        const int64_t runs[2] = { -1, GetRunNumber() };
        fDimStartRun.Update(runs);

        return FAD::kConnected;
    }

    void CloseRun(uint32_t runid)
    {
        if (runid==GetRunNumber()-1)
            ResetConfig();
    }

    int AddAddress(const EventImp &evt)
    {
        const string addr = Tools::Trim(evt.GetText());

        const tcp::endpoint endpoint = GetEndpoint(addr);
        if (endpoint==tcp::endpoint())
            return T::GetCurrentState();

        for (BoardList::const_iterator i=fBoards.begin(); i!=fBoards.end(); i++)
        {
            if (i->second->GetEndpoint()==endpoint)
            {
               T::Warn("Address "+addr+" already known.... ignored.");
               return T::GetCurrentState();
            }
        }

        AddEndpoint(endpoint);

        return T::GetCurrentState();
    }

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

        const int16_t slot = evt.GetShort();

        const BoardList::iterator it = GetSlot(slot);

        if (it==fBoards.end())
            return T::GetCurrentState();

        ConnectSlot(slot, tcp::endpoint());

        delete it->second;
        fBoards.erase(it);

        return T::GetCurrentState();
    }

    int ListSlots()
    {
        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
        {
            const int           &idx = i->first;
            const ConnectionFAD *fad = i->second;

            ostringstream str;
            str << "Slot " << setw(2) << idx << ": " << fad->GetEndpoint();

            if (fad->IsConnecting())
                str << " (0:connecting, ";
            else
            {
                if (fad->IsClosed())
                    str << " (0:disconnected, ";
                if (fad->IsConnected())
                    str << " (0:connected, ";
            }

            switch (fStatus2[idx])
            {
            case 0:  str << "1-7:not connected)"; break;
            case 8:  str << "1-7:connected)";     break;
            default: str << "1-7:connecting [" << (int)(fStatus2[idx]-1) << "])"; break;
            }

            if (fad->IsTransmissionBlocked())
                str << " [cmd_blocked]";

            if (fStatus2[idx]==8 && IsIgnored(idx))
                str << " [data_ignored]";

            if (fStatusC[idx])
                str << " [configured]";

            T::Out() << str.str() << endl;
        }

        T::Out() << "Event builder thread:";
        if (!IsThreadRunning())
            T::Out() << " not";
        T::Out() << " running" << endl;

        // FIXME: Output state

        return T::GetCurrentState();
    }

    void EnableConnection(ConnectionFAD *ptr, bool enable=true)
    {
        if (!enable)
        {
            ptr->PostClose(false);
            return;
        }

        if (!ptr->IsDisconnected())
        {
            ostringstream str;
            str << ptr->GetEndpoint();

            T::Warn("Connection to "+str.str()+" already in progress.");
            return;
        }

	ptr->StartConnect();
    }

    void EnableAll(bool enable=true)
    {
        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            EnableConnection(i->second, enable);
    }

    int CloseOpenFiles()
    {
        EventBuilderWrapper::CloseOpenFiles();
        return T::GetCurrentState();
    }

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

        const int16_t slot = evt.GetShort();

        const BoardList::iterator it = GetSlot(slot);
        if (it==fBoards.end())
            return T::GetCurrentState();

        EnableConnection(it->second, enable);
        ConnectSlot(it->first, enable ? it->second->GetEndpoint() : tcp::endpoint());

        return T::GetCurrentState();
    }

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

        const int16_t slot = evt.GetShort();

        const BoardList::iterator it = GetSlot(slot);
        if (it==fBoards.end())
            return T::GetCurrentState();

        const bool enable = it->second->IsDisconnected();

        EnableConnection(it->second, enable);
        ConnectSlot(it->first, enable ? it->second->GetEndpoint() : tcp::endpoint());

        return T::GetCurrentState();
    }

    int StartConnection()
    {
        vector<tcp::endpoint> addr(40);

        for (BoardList::iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            addr[i->first] = i->second->GetEndpoint();

        StartThread(addr);
        EnableAll(true);

        return T::GetCurrentState();
    }

    int StopConnection()
    {
        Exit();
        EnableAll(false);
        return T::GetCurrentState();
    }

    int AbortConnection()
    {
        Abort();
        EnableAll(false);
        return T::GetCurrentState();
    }

    int Reset(bool soft)
    {
        ResetThread(soft);
        return T::GetCurrentState();
    }

    vector<uint8_t> fStatus1;
    vector<uint8_t> fStatus2;
    vector<uint8_t> fStatusC;
    bool            fStatusT;

    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();

        // ===== Evaluate connection status =====

        uint16_t nclosed1     = 0;
        uint16_t nconnecting1 = 0;
        uint16_t nconnecting2 = 0;
        uint16_t nconnected1  = 0;
        uint16_t nconnected2  = 0;
        uint16_t nconfigured  = 0;

        vector<uint8_t> stat1(40);
        vector<uint8_t> stat2(40);

        int cnt = 0; // counter for enabled board

        const bool runs = IsThreadRunning();

        for (int idx=0; idx<40; idx++)
        {
            // ----- Command socket -----
            const BoardList::const_iterator &slot = fBoards.find(idx);
            if (slot!=fBoards.end())
            {
                const ConnectionFAD *c = slot->second;
                if (c->IsDisconnected())
                {
                    stat1[idx] = 0;
                    nclosed1++;

                    //DisconnectSlot(idx);
                }
                if (c->IsConnecting())
                {
                    stat1[idx] = 1;
                    nconnecting1++;
                }
                if (c->IsConnected())
                {
                    stat1[idx] = 2;
                    nconnected1++;

                    if (c->IsConfigured())
                    {
                        stat1[idx] = 3;
                        nconfigured++;
                    }
                }

                cnt++;
            }

            // ----- Event builder -----

            if (!runs)
                continue;

            stat2[idx] = GetNumConnected(idx);

            if (runs && IsConnecting(idx))
            {
                nconnecting2++;
                stat2[idx]++;
            }

            if (IsConnected(idx))
            {
                stat2[idx]++;
                nconnected2++;
            }
        }

        // ===== Send connection status via dim =====

        if (fStatus1!=stat1 || fStatus2!=stat2 || fStatusT!=runs)
        {
            fStatus1 = stat1;
            fStatus2 = stat2;
            fStatusT = runs;
            UpdateConnectionStatus(stat1, stat2, runs);
        }

        // ===== Return connection status =====

        // fadctrl:       Always connecting if not disabled
        // event builder:
        if (nconnecting1==0 && nconnected1>0 && nconnected2==nconnected1)
        {
            if (T::GetCurrentState()==FAD::kConfiguring1)
            {
                // We need some delay so that the FAD is not busy anymore
                // and really sees the software trigger
                // FIXME: Do we need this to be configurable?
                if (Time()-fConfigTimer<boost::posix_time::milliseconds(3000))
                    return FAD::kConfiguring1;

                for (BoardList::iterator it=fBoards.begin(); it!=fBoards.end(); it++)
                    it->second->Cmd(FAD::kCmdSingleTrigger);

                return FAD::kConfiguring2;
            }

            // If all boards are configured and we are configuring
            // go on and start the FADs
            if (T::GetCurrentState()==FAD::kConfiguring2)
            {
                // If not all boards have yet received the proper
                // configuration
                if (nconfigured!=nconnected1)
                    return FAD::kConfiguring2;

                // FIXME: Distinguish between not all boards have received
                // the configuration and the configuration is not consistent

                for (BoardList::iterator it=fBoards.begin(); it!=fBoards.end(); it++)
                {
                    ConnectionFAD &fad  = *it->second;

                    // Make sure that after switching on the trigger line
                    // there needs to be some waiting before all boards
                    // can be assumed to be listening
                    fad.Cmd(FAD::kCmdResetEventCounter);
                    fad.Cmd(FAD::kCmdSocket,      false);
                    //fad.Cmd(FAD::kCmdTriggerLine, true);
                    if (fTargetConfig->second.fContinousTrigger)
                        fad.Cmd(FAD::kCmdContTrigger, true);
                    fad.Cmd(FAD::kCmdBusyOn,      false);  // continously on

                    // FIXME: How do we find out when the FADs
                    //        successfully enabled the trigger lines?
                }
                return FAD::kConfigured;
            }

            if (T::GetCurrentState()==FAD::kConfigured)
            {
                // Stay in Configured as long as we have a valid
                // configuration and the run has not yet been started
                // (means the the event builder has received its
                // first event)
                if (!IsRunStarted() && nconfigured==nconnected1)
                    return FAD::kConfigured;

                if (IsRunStarted())
                    T::Message("Run successfully started... (gotNewRun called)");
                if (nconfigured!=nconnected1)
                    T::Message("Configuration of some boards changed.");
            }

            return GetNumFilesOpen()>0 ? FAD::kWritingData : FAD::kConnected;
        }

        if (nconnecting1>0 || nconnecting2>0 || nconnected1!=nconnected2)
            return FAD::kConnecting;

        // nconnected1 == nconnected2 == 0
        return runs ? FAD::kDisconnected : FAD::kOffline;
    }

    void AddEndpoint(const tcp::endpoint &addr)
    {
        int i=0;
        while (i<40)
        {
            if (fBoards.find(i)==fBoards.end())
                break;
            i++;
        }

        if (i==40)
        {
            T::Warn("Not more than 40 slots allowed.");
            return;
        }

        ConnectionFAD *fad = new ConnectionFAD(*this, *this, i);

        fad->SetEndpoint(addr);
        fad->SetVerbose(fIsVerbose);
        fad->SetHexOutput(fIsHexOutput);
        fad->SetDataOutput(fIsDataOutput);
        fad->SetDebugTx(fDebugTx);

        fBoards[i] = fad;
    }


    DimDescribedService fDimStartRun;
    DimDescribedService fDimConnection;

    void UpdateConnectionStatus(const vector<uint8_t> &stat1, const vector<uint8_t> &stat2, bool thread)
    {
        vector<uint8_t> stat(41);

        for (int i=0; i<40; i++)
            stat[i] = stat1[i]|(stat2[i]<<3);

        stat[40] = thread;

        fDimConnection.Update(stat);
    }

public:
    StateMachineFAD(ostream &out=cout) :
        T(out, "FAD_CONTROL"), EventBuilderWrapper(*static_cast<MessageImp*>(this)), ba::io_service::work(static_cast<ba::io_service&>(*this)),
        fStatus1(40), fStatus2(40), fStatusC(40), fStatusT(false),
        fDimStartRun("FAD_CONTROL/START_RUN", "X:1;X:1",
                                              "Run numbers"
                                              "|run[idx]:Run number of last configured run (-1 if configuration was reset or no run was yet configured)"
                                              "|next[idx]:Run number which will be assigned to next configuration"),
        fDimConnection("FAD_CONTROL/CONNECTIONS", "C:40;C:1",
                                                  "Connection status of FAD boards"
                                                  "|status[bitpattern]:lower bits stat1, upper bits stat2, for every board. 40=thread"
                                                  "|char[unknown]:to be completed")
    {
        // 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.
        ResetConfig();
        SetOutputFormat(FAD::kNone);

        // State names
        T::AddStateName(FAD::kOffline, "Disengaged",
                        "All enabled FAD boards are disconnected and the event-builer thread is not running.");

        T::AddStateName(FAD::kDisconnected, "Disconnected",
                        "All enabled FAD boards are disconnected, but the event-builder thread is running.");

        T::AddStateName(FAD::kConnecting, "Connecting",
                        "Only some enabled FAD boards are connected.");

        T::AddStateName(FAD::kConnected, "Connected",
                        "All enabled FAD boards are connected..");

        T::AddStateName(FAD::kConfiguring1, "Configuring1",
                        "Waiting 3 seconds for all FADs to be configured before requesting configuration.");

        T::AddStateName(FAD::kConfiguring2, "Configuring2",
                        "Waiting until all boards returned their configuration and they are valid.");

        T::AddStateName(FAD::kConfigured, "Configured",
                        "The configuration of all boards was successfully cross checked. Waiting for events with a new run number to receive.");

        T::AddStateName(FAD::kWritingData, "WritingData",
                        "The event builder has an open file (that does not mean that new events are currently received)");

        // FAD Commands
        T::AddEvent("SEND_CMD", "I:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SendCmd, this, placeholders::_1))
            ("Send a command to the FADs. Values between 0 and 0xffff are allowed."
             "|command[uint16]:Command to be transmittted.");
        T::AddEvent("SEND_DATA", "I:2", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SendCmdData, this, placeholders::_1))
            ("Send a command with data to the FADs. Values between 0 and 0xffff are allowed."
             "|command[uint16]:Command to be transmittted."
             "|data[uint16]:Data to be sent with the command.");

        T::AddEvent("ENABLE_SRCLK", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdSrclk))
            ("Set SRCLK");
        T::AddEvent("ENABLE_BUSY_OFF", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdBusyOff))
            ("Set BUSY continously low");
        T::AddEvent("ENABLE_BUSY_ON", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdBusyOn))
            ("Set BUSY constantly high (has priority over BUSY_OFF)");
        T::AddEvent("ENABLE_SCLK", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdSclk))
            ("Set SCLK");
        T::AddEvent("ENABLE_DRS", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdDrsEnable))
            ("Switch Domino wave");
        T::AddEvent("ENABLE_DWRITE", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdDwrite))
            ("Set Dwrite (possibly high / always low)");
        T::AddEvent("ENABLE_CONTINOUS_TRIGGER", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdContTrigger))
            ("Enable continous (internal) trigger.");
        T::AddEvent("ENABLE_TRIGGER_LINE", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdTriggerLine))
            ("Incoming triggers can be accepted/will not be accepted");
        T::AddEvent("ENABLE_COMMAND_SOCKET_MODE", "B:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CmdEnable, this, placeholders::_1, FAD::kCmdSocket))
            ("Set debug mode (yes: dump events through command socket, no=dump events through other sockets)");

        T::AddEvent("SET_TRIGGER_RATE", "I:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SetTriggerRate, this, placeholders::_1))
            ("Enable continous trigger");
        T::AddEvent("SEND_SINGLE_TRIGGER")
            (bind(&StateMachineFAD::Trigger, this, 1))
            ("Issue software triggers");
        T::AddEvent("SEND_N_TRIGGERS", "I", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SendTriggers, this, placeholders::_1))
            ("Issue software triggers");
        T::AddEvent("START_RUN", "", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::StartRun, this, placeholders::_1, true))
            ("Set FAD DAQ mode. when started, no configurations must be send.");
        T::AddEvent("STOP_RUN", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::StartRun, this, placeholders::_1, false))
            ("");
        T::AddEvent("PHASE_SHIFT", "S:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::PhaseShift, this, placeholders::_1))
            ("Adjust ADC phase (in 'steps')");

        T::AddEvent("RESET_EVENT_COUNTER", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::Cmd, this, FAD::kCmdResetEventCounter))
            ("");

        T::AddEvent("SET_RUN_NUMBER", "X:1", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SetRunNumber, this, placeholders::_1))
            ("");

        T::AddEvent("SET_MAX_MEMORY", "S:1")
            (bind(&StateMachineFAD::SetMaxMemoryBuffer, this, placeholders::_1))
            ("Set maximum memory buffer size allowed to be consumed by the EventBuilder to buffer events."
             "|memory[short]:Buffer size in Mega-bytes.");

        T::AddEvent("SET_REGISTER", "I:2", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SetRegister, this, placeholders::_1))
            ("set register to value"
            "|addr[short]:Address of register"
            "|val[short]:Value to be set");

        // FIXME:  Maybe add a mask which channels should be set?
        T::AddEvent("SET_REGION_OF_INTEREST", "I:2", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SetRoi, this, placeholders::_1))
            ("Set region-of-interest to value"
            "|channel[short]:Channel on each chip for which the ROI is set (0-8), -1 for all"
            "|val[short]:Value to be set");

        // FIXME:  Maybe add a mask which channels should be set?
        T::AddEvent("SET_DAC_VALUE", "I:2", FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::SetDac, this, placeholders::_1))
            ("Set DAC numbers in range to value"
            "|addr[short]:Address of register (-1 for all)"
            "|val[short]:Value to be set");

        T::AddEvent("CONFIGURE", "X:2;C", FAD::kConnected, FAD::kConfigured, FAD::kWritingData)
            (bind(&StateMachineFAD::StartConfigure, this, placeholders::_1))
            ("");

        T::AddEvent("RESET_CONFIGURE", FAD::kConfiguring1, FAD::kConfiguring2, FAD::kConfigured)
            (bind(&StateMachineFAD::ResetConfig, this))
            ("");

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

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

        T::AddEvent("SET_DATA_OUTPUT", "B:1")
            (bind(&StateMachineFAD::SetDataOutput, this, placeholders::_1))
            ("");

        T::AddEvent("SET_DEBUG_TX", "B:1")
            (bind(&StateMachineFAD::SetDebugTx, this, placeholders::_1))
	    ("Enable or disable the output of messages in case of successfull data transmission to the boards."
	     "|debug[bool]:disable or enable debug output for transmitted data (yes/no)");

        T::AddEvent("SET_DEBUG_EVENT_BUILDER_OUT", "B:1")
            (bind(&StateMachineFAD::SetDebugEb, this, placeholders::_1))
	    ("");

        T::AddEvent("PRINT_EVENT", "S:1")
            (bind(&StateMachineFAD::PrintEvent, this, placeholders::_1))
            ("Print (last) event"
             "|board[short]:slot from which the event should be printed (-1 for all)");

        T::AddEvent("DUMP_STREAM", "B:1")
            (bind(&StateMachineFAD::SetDumpStream, this, placeholders::_1))
            ("For debugging purpose: the binary data stream read from the sockets 0-7 can be dumped to files."
             "|switch[bool]:Enable (yes) or disable (no)");

        T::AddEvent("DUMP_RECV", "B:1")
            (bind(&StateMachineFAD::SetDumpRecv, this, placeholders::_1))
            ("For debugging purpose: the times when data has been receives are dumped to a file."
             "|switch[bool]:Enable (yes) or disable (no)");

        T::AddEvent("BLOCK_TRANSMISSION", "S:1;B:1")
            (bind(&StateMachineFAD::SetBlockTransmission, this, placeholders::_1))
            ("Blocks the transmission of commands to the given slot. Use with care! For debugging pupose only!"
             "|slot[short]:Slot to which the command transmission should be blocked (0-39)"
             "|enable[bool]:Whether the command transmission should be blockes (yes) or allowed (no)");

        T::AddEvent("BLOCK_TRANSMISSION_RANGE", "S:2;B:1")
            (bind(&StateMachineFAD::SetBlockTransmissionRange, this, placeholders::_1))
            ("Blocks the transmission of commands to the given range of slots. Use with care! For debugging pupose only!"
             "|first[short]:First slot to which the command transmission should be blocked (0-39)"
             "|last[short]:Last slot to which the command transmission should be blocked (0-39)"
             "|enable[bool]:Whether the command transmission should be blockes (yes) or allowed (no)");

        T::AddEvent("IGNORE_EVENTS", "S:1;B:1")
            (bind(&StateMachineFAD::SetIgnoreSlot, this, placeholders::_1))
            ("Instructs the event-builder to ignore events from the given slot but still read the data from the socket."
             "|slot[short]:Slot from which the data should be ignored when building events"
             "|enable[bool]:Whether the event builder should ignore data from this slot (yes) or allowed (no)");

        T::AddEvent("IGNORE_EVENTS_RANGE", "S:2;B:1")
            (bind(&StateMachineFAD::SetIgnoreSlots, this, placeholders::_1))
            ("Instructs the event-builder to ignore events from the given slot but still read the data from the socket."
             "|first[short]:First slot from which the data should be ignored when building events"
             "|last[short]:Last slot from which the data should be ignored when building events"
             "|enable[bool]:Whether the event builder should ignore data from this slot (yes) or allowed (no)");

        T::AddEvent("CLOSE_OPEN_FILES", FAD::kDisconnected, FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::CloseOpenFiles, this))
            ("Close all run files opened by the EventBuilder.");

        T::AddEvent("TEST", "S:1")
           (bind(&StateMachineFAD::Test, this, placeholders::_1))
            ("");



        // Conenction commands
        T::AddEvent("START", FAD::kOffline)
            (bind(&StateMachineFAD::StartConnection, this))
            ("Start EventBuilder thread and connect all valid slots.");

        T::AddEvent("STOP",  FAD::kDisconnected, FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::StopConnection, this))
            ("Stop EventBuilder thread (still write buffered events) and disconnect all slots.");

        T::AddEvent("ABORT", FAD::kDisconnected, FAD::kConnecting, FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::AbortConnection, this))
            ("Immediately abort EventBuilder thread and disconnect all slots.");

        T::AddEvent("SOFT_RESET", FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::Reset, this, true))
            ("Wait for buffers to drain, close all files and reinitialize event builder thread.");

        T::AddEvent("HARD_RESET", FAD::kConnected, FAD::kWritingData)
            (bind(&StateMachineFAD::Reset, this, false))
            ("Free all buffers, close all files and reinitialize event builder thread.");

        T::AddEvent("CONNECT", "S:1", FAD::kDisconnected, FAD::kConnecting, FAD::kConnected)
            (bind(&StateMachineFAD::EnableSlot, this, placeholders::_1, true))
            ("Connect a disconnected slot.");

        T::AddEvent("DISCONNECT", "S:1", FAD::kConnecting, FAD::kConnected)
            (bind(&StateMachineFAD::EnableSlot, this, placeholders::_1, false))
            ("Disconnect a connected slot.");

        T::AddEvent("TOGGLE", "S:1", FAD::kDisconnected, FAD::kConnecting, FAD::kConnected)
            (bind(&StateMachineFAD::ToggleSlot, this, placeholders::_1))
            ("");

        T::AddEvent("SET_FILE_FORMAT", "S:1")
            (bind(&StateMachineFAD::SetFileFormat, this, placeholders::_1))
            ("");

        T::AddEvent("START_DRS_CALIBRATION")
            (bind(&StateMachineFAD::StartDrsCalibration, this))
            ("");

        T::AddEvent("RESET_SECONDARY_DRS_BASELINE")
            (bind(&StateMachineFAD::ResetSecondaryDrsBaseline, this))
            ("");

        T::AddEvent("LOAD_DRS_CALIBRATION", "C")
            (bind(&StateMachineFAD::LoadDrsCalibration, this, placeholders::_1))
            ("Load a DRS calibration file"
             "|absolute path");


        T::AddEvent("ADD_ADDRESS", "C", FAD::kOffline)
            (bind(&StateMachineFAD::AddAddress, this, placeholders::_1))
            ("Add the address of a DRS4 board to the first free slot"
             "|IP[string]:address in the format <address:port>");
        T::AddEvent("REMOVE_SLOT", "S:1", FAD::kOffline)
            (bind(&StateMachineFAD::RemoveSlot, this, placeholders::_1))
            ("Remove the Iaddress in slot n. For a list see LIST"
             "|slot[short]:Remove the address in slot n from the list");
        T::AddEvent("LIST_SLOTS")
            (bind(&StateMachineFAD::ListSlots, this))
            ("Print a list of all available board addressesa and whether they are enabled");
    }

    ~StateMachineFAD()
    {
        for (BoardList::const_iterator i=fBoards.begin(); i!=fBoards.end(); i++)
            delete i->second;
        fBoards.clear();
    }

    tcp::endpoint GetEndpoint(const string &base)
    {
        const size_t p0 = base.find_first_of(':');
        const size_t p1 = base.find_last_of(':');

        if (p0==string::npos || p0!=p1)
        {
           T::Out() << kRed << "GetEndpoint - Wrong format ('host:port' expected)" << endl;
           return tcp::endpoint();
        }

        tcp::resolver resolver(get_io_service());

        boost::system::error_code ec;

        const tcp::resolver::query query(base.substr(0, p0), base.substr(p0+1));
        const tcp::resolver::iterator iterator = resolver.resolve(query, ec);

        if (ec)
        {
           T::Out() << kRed << "GetEndpoint - Couldn't resolve endpoint '" << base << "': " << ec.message();
           return tcp::endpoint();
        }

        return *iterator;
    }

    typedef map<string, FAD::Configuration> Configs;
    Configs fConfigs;
    Configs::const_iterator fTargetConfig;

    Time fConfigTimer;


    template<class V>
    bool CheckConfigVal(Configuration &conf, V max, const string &name, const string &sub)
    {
        if (!conf.HasDef(name, sub))
        {
            T::Error("Neither "+name+"default nor "+name+sub+" found.");
            return false;
        }

        const V val = conf.GetDef<V>(name, sub);

        if (val<=max)
            return true;

        ostringstream str;
        str << name << sub << "=" << val << " exceeds allowed maximum of " << max << "!";
        T::Error(str);

        return false;
    }

    int64_t fNightAsInt;

    int EvalOptions(Configuration &conf)
    {
        // ---------- General setup ---------
        fIsVerbose = !conf.Get<bool>("quiet");
        fIsHexOutput = conf.Get<bool>("hex-out");
        fIsDataOutput = conf.Get<bool>("data-out");
        fDebugTx = conf.Get<bool>("debug-tx");

        // ---------- Setup event builder ---------
        SetMaxMemory(conf.Get<unsigned int>("max-mem"));

        fNightAsInt = InitRunNumber(conf.Get<string>("destination-folder"));
        if (fNightAsInt<0)
            return 1;

        // ---------- Setup run types ---------
        const vector<string> types = conf.Vec<string>("run-type");
        if (types.size()==0)
            T::Warn("No run-types defined.");
        else
            T::Message("Defining run-types");
        for (vector<string>::const_iterator it=types.begin();
             it!=types.end(); it++)
        {
            T::Message(" -> "+ *it);

            if (fConfigs.count(*it)>0)
            {
                T::Error("Run-type "+*it+" defined twice.");
                return 2;
            }

            FAD::Configuration target;

            if (!CheckConfigVal<bool>(conf, true, "enable-drs.",               *it) ||
                !CheckConfigVal<bool>(conf, true, "enable-dwrite.",            *it) ||
                !CheckConfigVal<bool>(conf, true, "enable-continous-trigger.", *it))
                return 3;

            target.fDenable          = conf.GetDef<bool>("enable-drs.", *it);
            target.fDwrite           = conf.GetDef<bool>("enable-dwrite.", *it);
            target.fContinousTrigger = conf.GetDef<bool>("enable-continous-trigger.", *it);

            target.fTriggerRate = 0;
            //if (target.fContinousTrigger)
            {
                if (!CheckConfigVal<uint16_t>(conf, 0xffff, "trigger-rate.", *it))
                    return 4;

                target.fTriggerRate = conf.GetDef<uint16_t>("trigger-rate.", *it);
            }

            for (int i=0; i<FAD::kNumChannelsPerChip; i++)
            {
                ostringstream str;
                str << "roi-ch" << i << '.';

                if (!CheckConfigVal<uint16_t>(conf, FAD::kMaxRoiValue, "roi.",    *it) &&
                    !CheckConfigVal<uint16_t>(conf, FAD::kMaxRoiValue, str.str(), *it))
                    return 5;

                target.fRoi[i] = conf.HasDef(str.str(), *it) ?
                    conf.GetDef<uint16_t>(str.str(), *it) :
                    conf.GetDef<uint16_t>("roi.",    *it);
            }

            for (int i=0; i<FAD::kNumDac; i++)
            {
                ostringstream str;
                str << "dac-" << i << '.';

                if (!CheckConfigVal<uint16_t>(conf, FAD::kMaxDacValue, "dac.",    *it) &&
                    !CheckConfigVal<uint16_t>(conf, FAD::kMaxDacValue, str.str(), *it))
                    return 6;

                target.fDac[i] = conf.HasDef(str.str(), *it) ?
                    conf.GetDef<uint16_t>(str.str(), *it) :
                    conf.GetDef<uint16_t>("dac.",    *it);
            }

            fConfigs[*it] = target;
        }

        // FIXME: Add a check about unsused configurations

        // ---------- Setup board addresses for fake-fad ---------

        if (conf.Has("debug-addr"))
        {
            const string addr = conf.Get<string>("debug-addr");
            const int    num  = conf.Get<unsigned int>("debug-num");

            const tcp::endpoint endpoint = GetEndpoint(addr);
            if (endpoint==tcp::endpoint())
                return 7;

            for (int i=0; i<num; i++)
                AddEndpoint(tcp::endpoint(endpoint.address(), endpoint.port()+8*i));

            StartConnection();
            return -1;
        }

        // ---------- Setup board addresses for the real camera ---------

        if (conf.Has("base-addr"))
        {
            string base = conf.Get<string>("base-addr");

            if (base=="def" || base =="default")
                base = "10.0.128.128:31919";

            const tcp::endpoint endpoint = GetEndpoint(base);
            if (endpoint==tcp::endpoint())
                return 8;

            const ba::ip::address_v4::bytes_type ip = endpoint.address().to_v4().to_bytes();

            if (ip[2]>250 || ip[3]>244)
            {
                T::Out() << kRed << "EvalConfiguration - IP address given by --base-addr out-of-range." << endl;
                return 9;
            }

            for (int crate=0; crate<4; crate++)
                for (int board=0; board<10; board++)
                {
                    ba::ip::address_v4::bytes_type target = endpoint.address().to_v4().to_bytes();
                    target[2] += crate;
                    target[3] += board;

                    AddEndpoint(tcp::endpoint(ba::ip::address_v4(target), endpoint.port()));
                }

            StartConnection();
            return -1;

        }

        // ---------- Setup board addresses one by one ---------

        if (conf.Has("addr"))
        {
            const vector<string> addrs = conf.Vec<string>("addr");
            for (vector<string>::const_iterator i=addrs.begin(); i<addrs.end(); i++)
            {
                const tcp::endpoint endpoint = GetEndpoint(*i);
                if (endpoint==tcp::endpoint())
                    return 10;

                AddEndpoint(endpoint);
            }

            StartConnection();
            return -1;
        }
        return -1;
    }

};

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

#include "Main.h"

template<class T, class S>
int RunShell(Configuration &conf)
{
    return Main::execute<T, StateMachineFAD<S>>(conf);
}

void SetupConfiguration(Configuration &conf)
{
    po::options_description control("FAD control options");
    control.add_options()
        ("quiet,q",  po_bool(), "Disable printing contents of all received messages in clear text.")
        ("hex-out",  po_bool(), "Enable printing contents of all printed messages also as hex data.")
        ("data-out", po_bool(), "Enable printing received event data.")
        ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
        ;

    po::options_description connect("FAD connection options");
    connect.add_options()
        ("addr",        vars<string>(),     "Network address of FAD")
        ("base-addr",   var<string>(),      "Base address of all FAD")
        ("debug-num,n", var<unsigned int>(40),  "Sets the number of fake boards to be connected locally")
        ("debug-addr",  var<string>(),    "")
        ;

    po::options_description builder("Event builder options");
    builder.add_options()
        ("max-mem,m",          var<unsigned int>(100), "Maximum memory the event builder thread is allowed to consume for its event buffer")
        ("destination-folder", var<string>(""),        "Destination folder (base folder) for the event builder binary data files.")
        ;

    po::options_description runtype("Run type configuration");
    runtype.add_options()
        ("run-type",                     vars<string>(),        "Run type, e.g. data, pedestal, drs-calibration, light-pulser")
        ("enable-dwrite.*",              var<bool>(),           "")
        ("enable-drs.*",                 var<bool>(),           "")
        ("enable-continous-trigger.*",   var<bool>(),           "")
        ("trigger-rate.*",               var<uint16_t>(),       "")
        ("dac.*",                        var<uint16_t>(),       "")
        ("dac-0.*",                      var<uint16_t>(),       "")
        ("dac-1.*",                      var<uint16_t>(),       "")
        ("dac-2.*",                      var<uint16_t>(),       "")
        ("dac-3.*",                      var<uint16_t>(),       "")
        ("dac-4.*",                      var<uint16_t>(),       "")
        ("dac-5.*",                      var<uint16_t>(),       "")
        ("dac-6.*",                      var<uint16_t>(),       "")
        ("dac-7.*",                      var<uint16_t>(),       "")
        ("roi.*",                        var<uint16_t>(),       "")
        ("roi-ch0.*",                    var<uint16_t>(),       "")
        ("roi-ch1.*",                    var<uint16_t>(),       "")
        ("roi-ch2.*",                    var<uint16_t>(),       "")
        ("roi-ch3.*",                    var<uint16_t>(),       "")
        ("roi-ch4.*",                    var<uint16_t>(),       "")
        ("roi-ch5.*",                    var<uint16_t>(),       "")
        ("roi-ch6.*",                    var<uint16_t>(),       "")
        ("roi-ch7.*",                    var<uint16_t>(),       "")
        ("roi-ch8.*",                    var<uint16_t>(),       "")
        ;

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

    conf.AddOptions(control);
    conf.AddOptions(connect);
    conf.AddOptions(builder);
    conf.AddOptions(runtype);
}

void PrintUsage()
{
    cout <<
        "The fadctrl controls the FAD boards.\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: fadctrl [-c type] [OPTIONS]\n"
        "  or:  fadctrl [OPTIONS]\n";
    cout << endl;
}

void PrintHelp()
{
    Main::PrintHelp<StateMachineFAD<StateMachine>>();

    /* Additional help text which is printed after the configuration
     options goes here */
}

int main(int argc, const char* argv[])
{
    Configuration conf(argv[0]);
    conf.SetPrintUsage(PrintUsage);
    Main::SetupConfiguration(conf);
    SetupConfiguration(conf);

    if (!conf.DoParse(argc, argv, PrintHelp))
        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)
    {
        cerr << "Exception: " << e.what() << endl;
        return -1;
    }*/

    return 0;
}
