#ifndef FACT_HeadersFAD
#define FACT_HeadersFAD

#ifdef __cplusplus
#include <ostream>

// For debugging
#include <iostream>

#include "ByteOrder.h"

// ====================================================================

namespace FAD
{
#endif
    enum Enable
    {
        kCmdDrsEnable         = 0x0600,  // CMD_DENABLE/CMD_DISABLE
        kCmdDwrite            = 0x0800,  // CMD_DWRITE_RUN/CMD_DWRITE_STOP
        kCmdSclk              = 0x1000,  // CMD_SCLK_ON/OFF
        kCmdSrclk             = 0x1500,  // CMD_SRCLK_ON/OFF
        kCmdTriggerLine       = 0x1800,  // CMD_TRIGGERS_ON/CMD_TRIGGERS_OFF
        kCmdContTrigger       = 0x1f00,
        kCmdRun               = 0x2200,  // CMD_Start/Stop
        kCmdBusy              = 0x2400,  //
        kCmdResetEventCounter = 0x2A00,  //
        kCmdSocket            = 0x3000,  // CMD_mode_command/CMD_mode_all_sockets
        kCmdSingleTrigger     = 0xA000,  // CMD_Trigger
    };

    enum Commands
    {
        kCmdWriteExecute      = 0x0400,         // Configure FAD with the current config ram

        kCmdWrite             = 0x0500,         // write to Config-RAM
        kCmdWriteRoi          = kCmdWrite|0x00, // Baseaddress ROI-Values
        kCmdWriteDac          = kCmdWrite|0x24, // Baseaddress DAC-Values

        kCmdWriteRate         = kCmdWrite|0x2c, // Continous trigger rate
        kCmdWriteRunNumberMSW = kCmdWrite|0x2d, // Run Number most  significant word
        kCmdWriteRunNumberLSW = kCmdWrite|0x2e, // Run Number least significant word

        /*
         kCmdRead            = 0x0a00,         // read from Config-RAM
         kCmdReadRoi         = kCmdRead|0x00,  // Baseaddress ROI-Values
         kCmdReadDac         = kCmdRead|0x24,  // Baseaddress DAC-Values
         */

        kCmdPhaseIncrease   = 0x1200,         // CMD_PS_DIRINC
        kCmdPhaseDecrease   = 0x1300,         // CMD_PS_DIRDEC
        kCmdPhaseApply      = 0x1400,         // CMD_PS_DO
        kCmdPhaseReset      = 0x1700,         // CMD_PS_RESET
    };

    enum States
    {
        // State Machine states
        kOffline = 1,
        kDisconnected,
        kConnecting,
        kConnected,
        kConfiguring,
        kConfigured
    };

    enum
    {
        kMaxBins            = 1024,
        kNumTemp            = 4,
        kNumDac             = 8,
        kNumChips           = 4,
        kNumChannelsPerChip = 9,
        kNumChannels        = kNumChips*kNumChannelsPerChip,
    };

    enum
    {
        kMaxRegAddr   = 0xff,    // Highest address in config-ram
        kMaxRegValue  = 0xffff,
        kMaxDacAddr   = kNumDac-1,
        kMaxDacValue  = 0xffff,
        kMaxRoiAddr   = kNumChannels-1,
        kMaxRoiValue  = kMaxBins,
        kMaxRunNumber = 0xffffffff,
    };

    enum
    {
        kDelimiterStart = 0xfb01,
        kDelimiterEnd   = 0x04fe,
    };

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

    struct EventHeader
    {
#ifdef __cplusplus
        enum Bits
        {
            kDenable       = 1<<11,
            kDwrite        = 1<<10,
            //kRefClkTooHigh = 1<< 9,
            kRefClkTooLow  = 1<< 8,
            kDcmLocked     = 1<< 7,
            kDcmReady      = 1<< 6,
            kSpiSclk       = 1<< 5,
            kBusy          = 1<< 4,  // Busy enabled
            kTriggerLine   = 1<< 3,  // Trigger line enabled
            kContTrigger   = 1<< 2,  // Cont trigger enabled
            kSock17        = 1<< 1,  // Socket 1-7 for data transfer
        };
#endif
        // Einmalig:     (new header changes entry in array --> send only if array changed)
        // ----------------------------------
        // Event builder stores an array with all available values.
        // Disconnected boards are removed (replaced by def values)
        // Any received header information is immediately put in the array.
        // The array is transmitted whenever it changes.
        // This will usually happen only very rarely when a new connection
        // is opened.
        //
        // Array[40] of BoardId
        // Array[40] of Version
        // Array[40] of DNA

        // Slow changes: (new header changes entry in array --> send only if arra changed)
        // -------------------------------------------
        // Event builder stores an array with all available values.
        // Disconnected boards can be kept in the arrays.
        // Any received header information is immediately put in the array.
        // The array is transmitted whenever it changes.
        //
        // Connection status (disconnected, connecting, connected) / Array[40]
        // Consistency of PLLLCK       / Array[  40] of PLLLCK
        // Consistency of Trigger type / Array[  40] of trigger type
        // Consistency of ROI          / Array[1440] of ROI
        // Consistency of RefClock     / Array[  40] of ref clock
        // Consistency of DAC values   / Array[ 400] of DAC values
        // Consistency of run number   / Array[  40] of Run numbers

        // Fast changes  (new header changes value --> send only if something changed)
        // -------------------
        // Event builder stores an internal array of all boards and
        //  transmits the min/max values determined from the array
        //  only if they have changed. Disconnected boards are not considered.
        //
        // Maximum/minimum Event counter of all boards in memory + board id
        // Maximum/minimum time stamp    of all boards in memory + board id
        // Maximum/minimum temp          of all boards in memory + board id

        // Unknown:
        // ------------------
        // Trigger Id ?
        // TriggerGeneratorPrescaler ?
        // Number of Triggers to generate ?


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

        uint16_t fStartDelimiter;     // 0x04FE
        uint16_t fPackageLength;
        uint16_t fVersion;
        uint16_t fStatus;
        //
        uint16_t fTriggerCrc;          // Receiver timeout / CRC ; 1 byte each
        uint16_t fTriggerType;
        uint32_t fTriggerId;
        //
        uint32_t fEventCounter;
        uint32_t fFreqRefClock;
        //
        uint16_t fBoardId;
        uint16_t fAdcClockPhaseShift;
        uint16_t fNumTriggersToGenerate;
        uint16_t fTriggerGeneratorPrescaler;
        //
        uint64_t fDNA; // Xilinx DNA
        //
        uint32_t fTimeStamp;
        uint32_t fRunNumber;
        //
        int16_t  fTempDrs[kNumTemp];   // In units of 1/16 deg(?)
        //
        uint16_t fDac[kNumDac];
        //
#ifdef __cplusplus
        EventHeader() { init(*this); }
        EventHeader(const uint16_t *ptr)
        {
            *this = std::vector<uint16_t>(ptr, ptr+sizeof(EventHeader)/2);
        }

        void operator=(const std::vector<uint16_t> &vec)
        {
            ntohcpy(vec, *this);

            Reverse(((uint16_t*)fEventCounter));
            Reverse(((uint16_t*)fEventCounter)+1);

            Reverse(&fEventCounter);

	    Reverse(((uint16_t*)fTriggerId));
            Reverse(((uint16_t*)fTriggerId)+1);

            Reverse(&fTriggerId);

            Reverse(((uint16_t*)fFreqRefClock));
            Reverse(((uint16_t*)fFreqRefClock)+1);

            Reverse(&fFreqRefClock);

            Reverse(((uint16_t*)&fTimeStamp));
            Reverse(((uint16_t*)&fTimeStamp)+1);

            Reverse(&fTimeStamp);

            Reverse(((uint16_t*)&fRunNumber));
            Reverse(((uint16_t*)&fRunNumber)+1);

            Reverse(&fRunNumber);
            Reverse(&fDNA);

            for (int i=0; i<kNumTemp; i++)
            {
                fTempDrs[i] =
                    (fTempDrs[i]&0x8000) ?
                    ((fTempDrs[i]&0x007fff)^0xffffffff) :
                    (fTempDrs[i]&0x007fff);
                fTempDrs[i]>>=3;
            }

        }

        std::vector<uint16_t> HtoN() const
        {
            EventHeader h(*this);

            Reverse(((uint16_t*)h.fEventCounter));
            Reverse(((uint16_t*)h.fEventCounter)+1);

            Reverse(&h.fEventCounter);

            Reverse(((uint16_t*)h.fFreqRefClock));
            Reverse(((uint16_t*)h.fFreqRefClock)+1);

            Reverse(&h.fFreqRefClock);

            Reverse(((uint16_t*)&h.fTimeStamp));
            Reverse(((uint16_t*)&h.fTimeStamp)+1);

            Reverse(&h.fTimeStamp);

            Reverse(((uint16_t*)&h.fRunNumber));
            Reverse(((uint16_t*)&h.fRunNumber)+1);

            Reverse(&h.fRunNumber);
            Reverse(&h.fDNA);

            for (int i=0; i<kNumTemp; i++)
            {
                h.fTempDrs[i] <<= 3;
                h.fTempDrs[i] = ((h.fTempDrs[i]&0x800000) ?
                               (((h.fTempDrs[i]^0xffffffff)&0x007fff)|0x8000) :
                               (h.fTempDrs[i]&0x007fff));
            }

            return htoncpy(h);
        }

        bool operator==(const EventHeader &h) const
        {
            return
                fStatus == h.fStatus &&
                fRunNumber == h.fRunNumber &&
                fEventCounter == h.fEventCounter &&
                fAdcClockPhaseShift == h.fAdcClockPhaseShift &&
                fTriggerGeneratorPrescaler == h.fTriggerGeneratorPrescaler &&
                memcmp(fDac, h.fDac, sizeof(fDac))==0;
        }
        bool operator!=(const EventHeader &h) const { return !operator==(h); }

        float GetTemp(int i) const { return fTempDrs[i]/16.; }

        /*
        float GetTemp(int i) const
        {
            return (((fTempDrs[i]&0x8000) ?
                     ((fTempDrs[i]&0x007fff)^0xffffffff)
                     : (fTempDrs[i]&0x007fff))>>3)/16.; }*/

        uint8_t PLLLCK() const         { return fStatus>>12; }

        bool HasDenable() const        { return fStatus&kDenable; }
        bool HasDwrite() const         { return fStatus&kDwrite; }
//        bool IsRefClockTooHigh() const { return fStatus&kRefClkTooHigh; }
        bool IsRefClockTooLow() const  { return fStatus&kRefClkTooLow; }
        bool IsDcmLocked() const       { return fStatus&kDcmLocked; }
        bool IsDcmReady() const        { return fStatus&kDcmReady; }
        bool HasSpiSclk() const        { return fStatus&kSpiSclk; }
        bool HasBusy() const           { return fStatus&kBusy; }
        bool HasTriggerEnabled() const { return fStatus&kTriggerLine; }
        bool HasContTriggerEnabled() const { return fStatus&kContTrigger; }
        bool IsInSock17Mode() const    { return fStatus&kSock17; }

        uint16_t Crate() const { return fBoardId>>8; }
        uint16_t Board() const { return fBoardId&0xff; }

        uint16_t Id() const { return Crate()*10+Board(); }

        void Enable(Bits pos, bool enable=true)
        {
            if (enable)
                fStatus |= pos;
            else
                fStatus &= ~pos;
        }

        void clear() { reset(*this); }
        void print(std::ostream &out) const;
#endif

    } __attribute__((__packed__));

    struct ChannelHeader
    {
        uint16_t fId;
        uint16_t fStartCell;
        uint16_t fRegionOfInterest;
        uint16_t fDummy;
        // uint16_t fData[];

#ifdef __cplusplus
        ChannelHeader() { init(*this); }

        void operator=(const std::vector<uint16_t> &vec)
        {
            ntohcpy(vec, *this);
        }

        std::vector<uint16_t> HtoN() const
        {
            ChannelHeader h(*this);
            return htoncpy(h);
        }

        void clear() { reset(*this); }
        void print(std::ostream &out) const;

        uint16_t Chip() const    { return fId>>4; }
        uint16_t Channel() const { return fId&0xf; }
#endif
    } __attribute__((__packed__));

    // Package ends with:
    //   0x4242
    //   0x04fe

    struct Configuration
    {
        bool     fDwrite;
        bool     fDenable;
        bool     fContinousTrigger;
        uint16_t fTriggerRate;
        uint16_t fRoi[FAD::kNumChannelsPerChip];
        uint16_t fDac[FAD::kNumDac];

#ifdef __cplusplus
        Configuration() { init(*this); }
#endif
    };

    // --------------------------------------------------------------------
#ifdef __cplusplus
    inline std::ostream &operator<<(std::ostream &out, const EventHeader &h)
    {
        h.print(out);
        return out;
    }

    inline std::ostream &operator<<(std::ostream &out, const ChannelHeader &h)
    {
        h.print(out);
        return out;
    }
#endif

#ifdef __cplusplus
};
#endif

#endif
