#if !defined( _IOAADLINKFRAMEGRABBER_H )
#define _IOAADLINKFRAMEGRABBER_H

#include "PCI7300A.h"
#include "FrameBuffer.h"
#include "DataLock.h"

class IoAAdlinkFrameGrabber
{
public:
    class Status
    {
    public:
        Status( ) { Init( ); }

        void Init( )
        {
            XSize           = 0;
            YSize           = 0;
            MinPixel        = 0;
            MaxPixel        = 0;
            CCDNumber       = 0;
            FrameNumber     = 0;
            FrameIndex      = 0;
            GrabIndex       = 0;
            SaveIndex       = 0;
            LocalTimeBias   = 0;
            PixelClockCount = 0;
            DataClockCount  = 0;
            PixelClockHz    = 0;

            BadCCDNumberCount             = 0;
            BadFrameNumberCount           = 0;
            NonZeroStartLineNumberCount   = 0;
            NonConsecutiveLineNumberCount = 0;
            BadLineLengthCount            = 0;
            BadFrameSizeCount             = 0;
            FrameProcessingErrorCount     = 0;
        }

        void Copy( const Status& Other )
        {
            XSize           = Other.XSize;
            YSize           = Other.YSize;
            MinPixel        = Other.MinPixel;
            MaxPixel        = Other.MaxPixel;
            CCDNumber       = Other.CCDNumber;
            FrameNumber     = Other.FrameNumber;
            FrameIndex      = Other.FrameIndex;
            GrabIndex       = Other.GrabIndex;
            SaveIndex       = Other.SaveIndex;
            UTCTimeStamp    = Other.UTCTimeStamp;
            LocalTimeBias   = Other.LocalTimeBias;
            PixelClockCount = Other.PixelClockCount;
            DataClockCount  = Other.DataClockCount;
            PixelClockHz    = Other.PixelClockHz;

            BadCCDNumberCount             = Other.BadCCDNumberCount;
            BadFrameNumberCount           = Other.BadFrameNumberCount;
            NonZeroStartLineNumberCount   = Other.NonZeroStartLineNumberCount;
            NonConsecutiveLineNumberCount = Other.NonConsecutiveLineNumberCount;
            BadLineLengthCount            = Other.BadLineLengthCount;
            BadFrameSizeCount             = Other.BadFrameSizeCount;
            FrameProcessingErrorCount     = Other.FrameProcessingErrorCount;
        }

        void UpdateFrameProperties( const IoAFrameBuffer& FrameBuffer )
        {
            XSize           = FrameBuffer.XSize( );
            YSize           = FrameBuffer.YSize( );
            MinPixel        = FrameBuffer.MinPixel( );
            MaxPixel        = FrameBuffer.MaxPixel( );
            CCDNumber       = FrameBuffer.CCDNumber( );
            FrameNumber     = FrameBuffer.FrameNumber( );
            FrameIndex      = FrameBuffer.FrameIndex( );
            GrabIndex       = FrameBuffer.GrabIndex( );
            SaveIndex       = FrameBuffer.SaveIndex( );
            UTCTimeStamp    = FrameBuffer.UTCTimeStamp( );
            LocalTimeBias   = FrameBuffer.LocalTimeBias( );
            PixelClockCount = FrameBuffer.PixelClockCount( );
            DataClockCount  = FrameBuffer.DataClockCount( );
            PixelClockHz    = FrameBuffer.PixelClockHz( );
        }

        void UpdateErrorCounts( int iBadCCDNumberCount, int iBadFrameNumberCount,
                                int iNonZeroStartLineNumberCount, int iNonConsecutiveLineNumberCount,
                                int iBadLineLengthCount, int iBadFrameSizeCount, int iFrameProcessingErrorCount )
        {
            BadCCDNumberCount             = iBadCCDNumberCount;
            BadFrameNumberCount           = iBadFrameNumberCount;
            NonZeroStartLineNumberCount   = iNonZeroStartLineNumberCount;
            NonConsecutiveLineNumberCount = iNonConsecutiveLineNumberCount;
            BadLineLengthCount            = iBadLineLengthCount;
            BadFrameSizeCount             = iBadFrameSizeCount;
            FrameProcessingErrorCount     = iFrameProcessingErrorCount;
        }

        int DroppedFrames( ) const { return GrabDroppedFrames( ) + SaveDroppedFrames( ); }

        int GrabDroppedFrames( ) const { return FrameIndex - GrabIndex; }
        int SaveDroppedFrames( ) const { return GrabIndex  - SaveIndex; }

        int DroppedData( ) const { return (int) ( PixelClockCount - DataClockCount ); }

        int FramePeriod( const Status& Other ) const
        {
            int IndexDelta = FrameIndex - Other.FrameIndex;

            if ( IndexDelta == 0 ) return 0;

            return (int) ( ( PixelClockCount - Other.PixelClockCount ) / IndexDelta );
        }

        double FrameRate( const Status& Other ) const
        {
            __int64 ClockDelta = PixelClockCount - Other.PixelClockCount;

            if ( ClockDelta == 0 ) return 0.0;

            return (double) PixelClockHz * (double) ( FrameIndex - Other.FrameIndex ) / (double) ClockDelta;
        }

        int XSize, YSize;
        int MinPixel, MaxPixel;
        int CCDNumber;
        __int64 FrameNumber;
        int FrameIndex, GrabIndex, SaveIndex;
        IoATime UTCTimeStamp;
        int LocalTimeBias;
        __int64 PixelClockCount, DataClockCount;
        int PixelClockHz;

        int BadCCDNumberCount, BadFrameNumberCount;
        int NonZeroStartLineNumberCount, NonConsecutiveLineNumberCount;
        int BadLineLengthCount, BadFrameSizeCount, FrameProcessingErrorCount;
    };

private:
    class DMAThreadData
    {
    public:
        IoAFrameBuffer FrameBuffer;

        IoADataLock DataLock;

        Status StatusBuffer[ 3 ];

        int State;
        int FrameCount;
        __int64 DMADataCount;
        __int64 StartOfLineDataCount, StartOfFrameDataCount;
        __int64 EndOfLineDataCount, EndOfFrameDataCount;
        int FrameMinPixel, FrameMaxPixel;
		int FrameStride, LineStride;
		int FrameXSize, FrameYSize;
        int FrameBufferDataCount;
        unsigned short LineHeader[ 8 ];
        int LineHeaderNext;
        int CCDNumber;
        int LinePixelCount, FrameLineCount;
        __int64 FrameNumber, FirstFrameNumber, LastFrameNumber;

        int BadCCDNumberCount, BadFrameNumberCount;
        int NonZeroStartLineNumberCount, NonConsecutiveLineNumberCount;
        int BadLineLengthCount, BadFrameSizeCount, FrameProcessingErrorCount;

        __int64 SyncCount[4];
        __int64 TransitionCount[4][4];
        int OldIndex;

        DMAThreadData( )
        {
            DMADataCount                  = 0;
            FrameCount                    = 0;
            FirstFrameNumber              = 0;
            LastFrameNumber               = 0;
            CCDNumber                     = 0;
            FrameNumber                   = 0;
            FrameMinPixel                 = 0;
            FrameMaxPixel                 = 0;
            LineStride                    = 0;
            FrameStride                   = 0;
            FrameXSize                    = 0;
            FrameYSize                    = 0;
            BadCCDNumberCount             = 0;
            BadFrameNumberCount           = 0;
            NonZeroStartLineNumberCount   = 0;
            NonConsecutiveLineNumberCount = 0;
            BadLineLengthCount            = 0;
            BadFrameSizeCount             = 0;
            FrameProcessingErrorCount     = 0;

            LinePixelCount = -1;
            FrameLineCount = -1;

            SyncCount[0] = SyncCount[1] = SyncCount[2] = SyncCount[3] = -1;

            TransitionCount[0][0] = TransitionCount[0][1] = TransitionCount[0][2] = TransitionCount[0][3] = -1;
            TransitionCount[1][0] = TransitionCount[1][1] = TransitionCount[1][2] = TransitionCount[1][3] = -1;
            TransitionCount[2][0] = TransitionCount[2][1] = TransitionCount[2][2] = TransitionCount[2][3] = -1;
            TransitionCount[3][0] = TransitionCount[3][1] = TransitionCount[3][2] = TransitionCount[3][3] = -1;

            OldIndex = -1;
        }

        ~DMAThreadData( ) { }
    };

public:
    IoAAdlinkFrameGrabber( )
    {
        m_CardCount      = 0;
        m_ThreadsPerCard = 0;

        m_DataGrabber    = NULL;
        m_DMAThreadData  = NULL;
        m_pDMAThreadData = NULL;

        m_pGrabCallbackFunction = NULL;
        m_pGrabCallbackArgument = NULL;

        ClearStartFrameNumber( );
    }

    ~IoAAdlinkFrameGrabber( )
    {
        Close( );
    }

//    int Open( int FrameBufferSize );
    int Open( int CardCount, int ThreadsPerCard, int FrameBufferSize );
    void Close( );

    int IsOpen( ) const { return m_DataGrabber && m_DMAThreadData && m_pDMAThreadData && m_CardCount > 0 && m_ThreadsPerCard > 0; }

    int CardCount     ( ) const { return m_CardCount;      }
    int ThreadsPerCard( ) const { return m_ThreadsPerCard; }

    int SetGrabCallback( int ( *pGrabCallbackFunction )( void *, int, int, int, IoAFrameBuffer& ), void *pGrabCallbackArgument )
    {
        if ( pGrabCallbackFunction == NULL ) return FALSE;

        m_pGrabCallbackFunction = pGrabCallbackFunction;
        m_pGrabCallbackArgument = pGrabCallbackArgument;

        return TRUE;
    }

    void SetPixelClockHz( int PixelClockHz ) { m_PixelClockHz = PixelClockHz; }
    int PixelClockHz( ) const { return m_PixelClockHz; }

    int ResetPLD( )
    {
        for ( int CardIndex = 0; CardIndex < m_CardCount; CardIndex++ )
        {
            if ( ! m_DataGrabber[ CardIndex ].WriteAUXO( 3, 0x1 ) ) return FALSE;  // Pulse AUXO3 to reset PLD logic
            if ( ! m_DataGrabber[ CardIndex ].WriteAUXO( 3, 0x0 ) ) return FALSE;  // Note: AUXO3 turned off on Open( )
        }

        return TRUE;
    }

    void ClearStartFrameNumber( )
    {
#ifdef WindowsXP  // pre Windows Vista
        m_StartFrame = FALSE;  // subsequently changes to TRUE only on first call to InterlockedCompareExchange( )
#endif
        m_StartFrameNumber = -1;  // until very first call to SetStartFrameNumber( )
    }

    void SetStartFrameNumber( __int64 StartFrameNumber )
    {
#ifdef WindowsXP  // pre Windows Vista
        // The call to InterlockedCompareExchange( ) is set up so that only the very first call across
        // all threads returns FALSE (allowing the thread to set m_StartFrameNumber). All other calls are
        // guaranteed to return TRUE. The wait induced by the while( ) loop is there in case the setting
        // thread is pre-empted before it gets to set m_StartFrameNumber.

        if ( ::InterlockedCompareExchange( (PVOID *) &m_StartFrame, (PVOID) TRUE, FALSE ) )
        {
            // Wait till intialisation done (any non-setting thread)
            while ( m_StartFrameNumber < 0 )
            {
                Sleep( 0 );  // temporarily suspend this thread, allowing setting thread to complete its job
            }
        }
        else
        {
            m_StartFrameNumber = StartFrameNumber;  // do one-time initialisation (single setting thread)
        }
#else
        // Atomically set 64-bit m_StartFrameNumber to the start number only if it is equal to -1
        // InterlockedCompareExchange64( ) is guaranteed to work in an atomic and therefore thread-safe manner
        ::InterlockedCompareExchange64( &m_StartFrameNumber, StartFrameNumber, (__int64) -1 );
#endif
    }

    __int64 StartFrameNumber( ) { return m_StartFrameNumber; }

    int Start( )
    {
        // Initialise one-time setting of start frame number
        ClearStartFrameNumber( );

        if ( ! ResetPLD( ) ) return FALSE;

        for ( int CardIndex = 0; CardIndex < m_CardCount; CardIndex++ )
        {
            if ( ! m_DataGrabber[ CardIndex ].Start( ) ) return FALSE;
        }

        return TRUE;
    }

    int Stop( )
    {
        for ( int CardIndex = 0; CardIndex < m_CardCount; CardIndex++ )
        {
            if ( ! m_DataGrabber[ CardIndex ].Stop( ) ) return FALSE;
        }

        return TRUE;
    }

    // Grab callback
    enum GrabCallbackAction
    {
        eGrabStart,
        eGrabFrame,
        eGrabIdle,
        eGrabStop
    };

	int GetDMALoopCount( int CardIndex, int &DMALoopCount );

	int GetDMADataCount( int CardIndex, int ThreadIndex, __int64 &DMADataCount );

    int GetSynchronisationCounts( int CardIndex, int ThreadIndex,
                                  __int64 &VH00Count, __int64 &VH01Count,
                                  __int64 &VH10Count, __int64 &VH11Count );

    int GetTransitionCounts( int CardIndex, int ThreadIndex,
                             __int64 &StartOfFrameCount, __int64 &EndOfFrameCount,
                             __int64 &StartOfLineCount,  __int64 &EndOfLineCount,
                             __int64 &InvalidTransitionCount );

    int GetStrideParameters( int CardIndex, int ThreadIndex, int& LineStride, int& FrameStride );

    int GetFrameCounts( int CardIndex, int ThreadIndex, int &CameraFrameCount, int &GrabFrameCount, int &DropFrameCount );

    int GetCameraNumber( int CardIndex, int ThreadIndex, int &CameraNumber );
    int GetCCDNumber   ( int CardIndex, int ThreadIndex, int &CCDNumber    );
    int GetFrameNumber ( int CardIndex, int ThreadIndex, __int64 &FrameNumber  );

    int GetFrameParameters( int CardIndex, int ThreadIndex, int& FrameXSize, int& FrameYSize,
                            int& LineStride, int& FrameStride );

    const Status *GetCurrentStatus( int CardIndex, int ThreadIndex );

private:
    // DMA callback
    static int DMACallback( void *pArgument, int Action, IoAPCI7300A *pDataGrabber, int ThreadIndex, int BufferID )
    {
        if ( pArgument == NULL ) return FALSE;

        // Convert static member function call into call to regular member function
        IoAAdlinkFrameGrabber *This = (IoAAdlinkFrameGrabber *) pArgument;

        return This->ProcessDMABuffer( Action, pDataGrabber, ThreadIndex, BufferID );
    }

    int ProcessDMABuffer( int Action, IoAPCI7300A *pDataGrabber, int ThreadIndex, int BufferID );

    // Grab callback
    int GrabCallback( int Action, int CardIndex, int ThreadIndex )
    {
        if ( m_pGrabCallbackFunction && m_DMAThreadData )
        {
            // Handle all exceptions thrown in grab callback function
            try
            {
                // Action = eGrabStart  first call, before first frame
                //        = eGrabFrame  process contents of next frame buffer
                //        = eGrabIdle   no more frames for now
                //        = eGrabStop   last call, after last frame

                IoAFrameBuffer& FrameBuffer = m_DMAThreadData[ CardIndex ][ ThreadIndex ].FrameBuffer;

                // Call grab callback function
                return ( *m_pGrabCallbackFunction )( m_pGrabCallbackArgument, Action, CardIndex, ThreadIndex, FrameBuffer );
            }
            catch ( ... )
            {
                return FALSE;  // exception thrown in callback function
            }
        }

        return FALSE;
    }

    // Data acquisition card(s)
    int m_CardCount;  // actual number of data acquisition cards
    IoAPCI7300A *m_DataGrabber;

    // Data acquisition thread(s)
    int m_ThreadsPerCard;
    DMAThreadData *m_pDMAThreadData;  // array size = CardCount * ThreadsPerCard
    DMAThreadData **m_DMAThreadData;

    // Grab callback
    int (*m_pGrabCallbackFunction)( void *pArgument, int Action, int CardIndex, int ThreadIndex, IoAFrameBuffer& FrameBuffer );
    void *m_pGrabCallbackArgument;

    // Start frame number (shared across all CCD channels)
    volatile __int64 m_StartFrameNumber;  // has to be volatile because it may be set by another thread
#ifdef WindowsXP  // pre Windows Vista
    volatile int m_StartFrame;  // to synchronize setting of start frame number
#endif

    int m_PixelClockHz;  // pixel clock frequency
};

#endif
