#include "stdafx.h"
#include "AdlinkFrameGrabber.h"

int IoAAdlinkFrameGrabber::Open( int CardCount, int ThreadsPerCard, int FrameBufferSize )
{
    if ( CardCount <= 0 ) return FALSE;
    if ( ThreadsPerCard <= 0 || ThreadsPerCard > 2 ) return FALSE;
    if ( FrameBufferSize <= 0 ) return FALSE;

    Close( );

    // Allocate card-based memory
    m_DataGrabber   = new IoAPCI7300A    [ CardCount ];
    m_DMAThreadData = new DMAThreadData *[ CardCount ];

    // Allocate thread-based memory
    int ThreadCount = CardCount * ThreadsPerCard;

    m_pDMAThreadData = new DMAThreadData[ ThreadCount ];

    // Check for any failure to allocate memory
    if ( m_DataGrabber == NULL || m_DMAThreadData == NULL || m_pDMAThreadData== NULL )
    {
        Close( );

        return FALSE;
    }

    // Set up pointer array to simplify access to individual thread data
    for ( int CardIndex = 0, ThreadIndex = 0; CardIndex < CardCount; CardIndex++, ThreadIndex += ThreadsPerCard )
    {
        m_DMAThreadData[ CardIndex ] = m_pDMAThreadData + ThreadIndex;
    }

    // Convenient scope for handling error recovery
    for ( ; ; )
    {
        int PortBits = 16 * ThreadsPerCard;

        int DMABufferBytes = 128 * 1024 * 1024;  // 128MB - must not exceed IoAPCI7300A::DMABufferSpace( )
        int DMABufferCount = 64;
        int DMABufferSize  = DMABufferBytes / DMABufferCount / ( PortBits / 8 ) / CardCount;

		int CardIndex;

        for ( CardIndex = 0; CardIndex < CardCount; CardIndex++ )
        {
            IoAPCI7300A& DataGrabber = m_DataGrabber[ CardIndex ];

            if ( ! DataGrabber.Register( CardIndex ) ) break;

            if ( ! DataGrabber.Configure( PortBits ) ) break;

            if ( ! DataGrabber.AllocateDMABuffers( 64, DMABufferSize ) ) break;  // 128MB in total

            if ( ! DataGrabber.SetDMACallback( DMACallback, this, ThreadsPerCard ) ) break;

            if ( ! DataGrabber.CreateThreads( ) ) break;

            if ( ! DataGrabber.StartThreads( ) ) break;

            if ( ! DataGrabber.WriteAUXO( 0x0 ) );  // make sure all AUXO bits turned off

            DataGrabber.StopOnDataOverrun ( FALSE );
            DataGrabber.ErrorOnDataOverrun( FALSE );

            int BitsPerPixel = 14;

			int ThreadIndex;

            for ( ThreadIndex = 0; ThreadIndex < ThreadsPerCard; ThreadIndex++ )
            {
                IoAFrameBuffer& FrameBuffer = m_DMAThreadData[ CardIndex ][ ThreadIndex ].FrameBuffer;

                FrameBuffer.SetCardIndex( CardIndex );

                FrameBuffer.SetThreadIndex( ThreadIndex );

                FrameBuffer.SetPixelClockHz( PixelClockHz( ) );

                if ( ! FrameBuffer.Allocate( FrameBufferSize ) ) break;

                if ( ! FrameBuffer.AllocateHistogram( BitsPerPixel ) ) break;
            }

            if ( ThreadIndex < ThreadsPerCard ) break;
        }

        if ( CardIndex < CardCount ) break;

        // All done
        m_CardCount      = CardCount;
        m_ThreadsPerCard = ThreadsPerCard;

        return TRUE;
    }

    // Clean up the mess
    Close( );

    return FALSE;
}

void IoAAdlinkFrameGrabber::Close( )
{
    delete [ ] m_DataGrabber;
    delete [ ] m_DMAThreadData;
    delete [ ] m_pDMAThreadData;

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

    m_CardCount      = 0;
    m_ThreadsPerCard = 0;
}

int IoAAdlinkFrameGrabber::ProcessDMABuffer( int Action, IoAPCI7300A *pDataGrabber, int ThreadIndex, int BufferID )
{
    // Decoding done via two LUTs:
    //
    // (1) Transition = StateTransition[ OldState ][ VSync ][ HSync ] => work to do on change of state
    //
    // (2) NewState   = StateMachine[ OldState ][ VSync ][ HSync ]    => work to do according to new state
    //
    // Code further modified for faster running (20-30% speed improvement):
    //
    // (1) LUTs converted into 1D arrays: StateTransition[ Index ] and StateMachine[ Index ],
    //
    //     where Index = ( OldState << 2 ) | ( VSync << 1 ) | ( HSync << 0 );
    //
    // (2) Frequently accessed class data copied into local variables:
    //
    //     State, DMADataCount, FrameBufferDataCount, LineHeader, LineHeaderNext, pFrameBuffer and FrameBufferSize
    //
    // (3) Use if-else-if statements to replace second switch( ) statement

    // Enumerate state transitions
    enum { eStartOfLine, eEndOfLine, eStartOfFrame, eEndOfFrame, eFoundSync, eLostSync, eNoTransition };  // transitions

//    int StateTransition[ 4 ][ 2 ][ 2 ] =
    int StateTransition[ 16 ] =
    { // [0][0]         [0][1]         [1][0]         [1][1]
        eFoundSync,    eNoTransition, eNoTransition, eNoTransition,  // [ eOutOfSync ][ VSync ][ HSync ]
        eNoTransition, eLostSync,     eStartOfFrame, eLostSync,      // [ eInSync    ][ VSync ][ HSync ]
        eEndOfFrame,   eLostSync,     eNoTransition, eStartOfLine,   // [ eInFrame   ][ VSync ][ HSync ]
        eLostSync,     eLostSync,     eEndOfLine,    eNoTransition   // [ eInLine    ][ VSync ][ HSync ]
    };

    // Enumerate states
    enum { eOutOfSync, eInSync, eInFrame, eInLine };  // 0 <= states <= 3

//    int StateMachine[ 4 ][ 2 ][ 2 ] =
    int StateMachine[ 16 ] =
    { // [0][0]      [0][1]      [1][0]      [1][1]
        eInSync,    eOutOfSync, eOutOfSync, eOutOfSync,  // [ eOutOfSync ][ VSync ][ HSync ]
        eInSync,    eOutOfSync, eInFrame,   eOutOfSync,  // [ eInSync    ][ VSync ][ HSync ]
        eInSync,    eOutOfSync, eInFrame,   eInLine,     // [ eInFrame   ][ VSync ][ HSync ]
        eOutOfSync, eOutOfSync, eInFrame,   eInLine      // [ eInLine    ][ VSync ][ HSync ]
    };

//    IoAPrint( "Action, CardIndex, ThreadIndex, BufferID = %s, %d, %d. %d\n", Action, CardIndex, ThreadIndex, BufferID );

    if ( pDataGrabber == NULL ) return FALSE;  // undefined data acquisition card

    if ( ! IsOpen( ) ) return FALSE;  // internal data structures undefined

    // Determine data acquisition card index
	int CardIndex;

    for ( CardIndex = 0; CardIndex < m_CardCount; CardIndex++ )
    {
        if ( &m_DataGrabber[ CardIndex ] == pDataGrabber ) break;
    }

    // Check card index
    if ( CardIndex == m_CardCount ) return FALSE;  // unidentified data acquisition card

    // Check thread index
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    char *ActionString[ 4 ] = { "Start", "Scan ", "Idle ", "Stop " };

//    IoAPrint( "ProcessDMABuffer( Action, CardIndex, ThreadIndex, BufferIndex ) = %s, %d, %d, %d\n", ActionString[ Action ], CardIndex, ThreadIndex, BufferID );

    // Set up reference to this thread's data variables
    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    switch( Action )
    {
        // Start of DMA processing
        case IoAPCI7300A::eStartOfDMA:
        {
            ThreadData.State            = eOutOfSync;
            ThreadData.FirstFrameNumber = -1;
            ThreadData.LastFrameNumber  = -1;
            ThreadData.DMADataCount     = 0;
            ThreadData.FrameCount       = 0;
            ThreadData.LineHeaderNext   = 0;

            ThreadData.SyncCount[0] = ThreadData.SyncCount[1] = ThreadData.SyncCount[2] = ThreadData.SyncCount[3] = 0;

            ThreadData.TransitionCount[0][0] = ThreadData.TransitionCount[0][1] = 0;
            ThreadData.TransitionCount[0][2] = ThreadData.TransitionCount[0][3] = 0;
            ThreadData.TransitionCount[1][0] = ThreadData.TransitionCount[1][1] = 0;
            ThreadData.TransitionCount[1][2] = ThreadData.TransitionCount[1][3] = 0;
            ThreadData.TransitionCount[2][0] = ThreadData.TransitionCount[2][1] = 0;
            ThreadData.TransitionCount[2][2] = ThreadData.TransitionCount[2][3] = 0;
            ThreadData.TransitionCount[3][0] = ThreadData.TransitionCount[3][1] = 0;
            ThreadData.TransitionCount[3][2] = ThreadData.TransitionCount[3][3] = 0;

            ThreadData.OldIndex = -1;

			ThreadData.StartOfFrameDataCount = 0;
			ThreadData.StartOfLineDataCount  = 0;

			ThreadData.EndOfFrameDataCount = -1;
			ThreadData.EndOfLineDataCount  = -1;

			ThreadData.FrameStride = 0;
			ThreadData.LineStride  = 0;

            ThreadData.BadCCDNumberCount             = 0;
            ThreadData.BadFrameNumberCount           = 0;
            ThreadData.NonZeroStartLineNumberCount   = 0;
            ThreadData.NonConsecutiveLineNumberCount = 0;
            ThreadData.BadLineLengthCount            = 0;
            ThreadData.BadFrameSizeCount             = 0;
            ThreadData.FrameProcessingErrorCount     = 0;

            // Propagate current pixel clock frequency into frame buffer
            ThreadData.FrameBuffer.SetPixelClockHz( PixelClockHz( ) );

            if ( ! GrabCallback( eGrabStart, CardIndex, ThreadIndex ) ) return FALSE;
        }
        break;

        // Process contents of DMA buffer
        case IoAPCI7300A::eScanDMABuffer:
        {
            // Fetch DMA buffer parameters
            const void *BufferAddress = m_DataGrabber[ CardIndex ].DMABufferAddress( BufferID );
            int NumberOfSamples       = m_DataGrabber[ CardIndex ].DMABufferSize   ( BufferID );
            int SampleBits            = m_DataGrabber[ CardIndex ].PortWidth( );

            if ( BufferAddress == NULL || NumberOfSamples <= 0 || SampleBits != 16 * m_ThreadsPerCard )
            {
                if ( BufferAddress == NULL ) IoAPrint( "Error = BufferAddress == NULL\n" );
                if ( NumberOfSamples <= 0 ) IoAPrint( "Error = NumberOfSamples <= 0\n" );
                if ( SampleBits != 16 * m_ThreadsPerCard ) IoAPrint( "Error = SampleBits != 16 * m_ThreadsPerCard\n" );
                
                return FALSE;
            }

//            IoAPrint( "(%d,%d) BufferID, BufferAddress,NumberOfSamples, SampleBits = %d, %08x, %d, %d\n", CardIndex, ThreadIndex, BufferID, (unsigned int) BufferAddress, NumberOfSamples, SampleBits );

            // Set up data stride and offset
            int AddressOffset = ThreadIndex;
            int SampleStride  = m_ThreadsPerCard;

            // Set up 16-bit sample count
//            NumberOfSamples *= m_ThreadsPerCard;

            // Restore state variables into local variables
            int State                = ThreadData.State;
            __int64 DMADataCount     = ThreadData.DMADataCount;
            int LineHeaderNext       = ThreadData.LineHeaderNext;
            int FrameBufferDataCount = ThreadData.FrameBufferDataCount;

            // Put frame buffer address and size into local variables
            unsigned short *pFrameBuffer = ThreadData.FrameBuffer.ShortAddress( );
            int FrameBufferSize          = ThreadData.FrameBuffer.ShortBufferSize( );

            // Put line header address into local variable
            unsigned short *LineHeader = ThreadData.LineHeader;

            // Process contents of DMA buffer
            unsigned short *DMABuffer = (unsigned short *) BufferAddress;  // + AddressOffset;

            __int64 StartDMADataCount = DMADataCount;

//            ThreadData.SyncCount[0] = ThreadData.SyncCount[1] = ThreadData.SyncCount[2] = ThreadData.SyncCount[3] = 0;

//            int EndOfFrame = -1;
/*
            for ( int m = 0; m < NumberOfSamples; m++ )
            {
                int Index = m * SampleStride + AddressOffset;

                int VSync = ( DMABuffer[ Index ] & 0x8000 ) == 0;  // VSync = active low
                int HSync = ( DMABuffer[ Index ] & 0x4000 ) == 0;  // HSync = active low

                ThreadData.SyncCount[ VSync + VSync + HSync ] += 1;

                if ( VSync == 0 && HSync == 0 )  // OutOfFrame
                {
                    int VSync1 = ( DMABuffer[ Index - SampleStride ] & 0x8000 ) == 0;  // VSync = active low
                    int HSync1 = ( DMABuffer[ Index - SampleStride ] & 0x4000 ) == 0;  // HSync = active low

                    int VSync2 = ( DMABuffer[ Index + SampleStride ] & 0x8000 ) == 0;  // VSync = active low
                    int HSync2 = ( DMABuffer[ Index + SampleStride ] & 0x4000 ) == 0;  // HSync = active low

                    IoAPrint( "(%d,%d) = %d = %d,%d = %d,%d\n", CardIndex, ThreadIndex, m, VSync1, HSync1, VSync2, HSync2 );
                }
                else if ( VSync == 1 && HSync == 0 )
                {
                    if ( m + 1 < NumberOfSamples )
                    {
                        int VSync2 = ( DMABuffer[ Index + SampleStride ] & 0x8000 ) == 0;  // VSync = active low
                        int HSync2 = ( DMABuffer[ Index + SampleStride ] & 0x4000 ) == 0;  // HSync = active low

                        if ( VSync2 == 0 && HSync2 == 0 )  // EndOfFrame
                        {
                            EndOfFrame = m;

                            for ( int i = 0; i < 35; i++ )
                            {
                                if ( m + i < NumberOfSamples )
                                {
                                    int Index = ( m + i ) * SampleStride + AddressOffset;

                                    int VSync = ( DMABuffer[ Index ] & 0x8000 ) == 0;  // VSync = active low
                                    int HSync = ( DMABuffer[ Index ] & 0x4000 ) == 0;  // HSync = active low

                                    IoAPrint( "(%d,%d) = %d = %d,%d\n", CardIndex, ThreadIndex, m + i, VSync, HSync );
                                }
                            }
                        }
                    }
                }
            }
*/
            for ( int i = 0; i < NumberOfSamples; i++, DMADataCount++ )
            {
                unsigned short DataValue = DMABuffer[ i * SampleStride + AddressOffset ];

                int VSync = ( DataValue & 0x8000 ) == 0;  // VSync = active low
                int HSync = ( DataValue & 0x4000 ) == 0;  // HSync = active low

                int OldState = State;

                int Index = ( OldState << 2 ) | ( VSync << 1 ) | ( HSync << 0 );

//                int NewState = StateMachine[ OldState ][ VSync ][ HSync ];
                int NewState = StateMachine[ Index ];

                // Deal with any change of state
//                switch ( StateTransition[ OldState ][ VSync ][ HSync ] )
                switch ( StateTransition[ Index ] )
                {
                    case eStartOfLine:
                    {
                        // Retrieve camera, frame and line numbers from line header
                        int CCDNumber   = LineHeader[ ( LineHeaderNext + 8 - 3 ) & 0x7 ];
                        int FrameNumber = LineHeader[ ( LineHeaderNext + 8 - 2 ) & 0x7 ];
                        int LineNumber  = LineHeader[ ( LineHeaderNext + 8 - 1 ) & 0x7 ];

						if ( FrameNumber != 0 ) {
							int f = FrameNumber;
						}

                        // First line in frame ...
                        if ( ThreadData.FrameLineCount == 0 )
                        {
                            int NonZeroStartLineNumber = LineNumber != 0;

                            ThreadData.NonZeroStartLineNumberCount += NonZeroStartLineNumber;

                            // Check for valid line number
                            if ( NonZeroStartLineNumber )
                            {
                                NewState = eOutOfSync; break;
                            }

                            // Keep copies of camera and frame number
                            ThreadData.CCDNumber   = CCDNumber;
                            ThreadData.FrameNumber = FrameNumber;

                            // Initialise pixel range
                            ThreadData.FrameMinPixel = DataValue;
                            ThreadData.FrameMaxPixel = DataValue;
                        }

                        // Second or subsequent line in frame
                        else
                        {
                            int BadCCDNumber             = CCDNumber   != ThreadData.CCDNumber;
                            int BadFrameNumber           = FrameNumber != ThreadData.FrameNumber;
                            int NonConsecutiveLineNumber = LineNumber  != ThreadData.FrameLineCount;

                            ThreadData.BadCCDNumberCount             += BadCCDNumber;
                            ThreadData.BadFrameNumberCount           += BadFrameNumber;
                            ThreadData.NonConsecutiveLineNumberCount += NonConsecutiveLineNumber;

                            // Check for valid line header
                            if ( BadCCDNumber || BadFrameNumber || NonConsecutiveLineNumber )
                            {
                                NewState = eOutOfSync; break;
                            }

                            ThreadData.LineStride = (int) ( DMADataCount - ThreadData.StartOfLineDataCount );
                        }

                        ThreadData.StartOfLineDataCount = DMADataCount;
                    }
                    break;

                    case eEndOfLine:
                    {
                        int LinePixelCount = (int) ( DMADataCount - ThreadData.StartOfLineDataCount );

                        // First line in frame
                        if ( ThreadData.FrameLineCount == 0 )
                        {
                            ThreadData.LinePixelCount = LinePixelCount;  // keep copy of line length
                        }

                        // Second or subsequent line in frame
                        else
                        {
                            int BadLineLength = LinePixelCount != ThreadData.LinePixelCount;

                            ThreadData.BadLineLengthCount += BadLineLength;

                            // Check for valid line length (all lines must be same length)
                            if ( BadLineLength )
                            {
//                                IoAPrint( "BadLineLengthCount = %d\n", ThreadData.BadLineLengthCount );

                                NewState = eOutOfSync; break;
                            }
                        }

                        // Count lines
                        ThreadData.FrameLineCount++;

						ThreadData.EndOfLineDataCount = DMADataCount;
                    }
                    break;

                    case eStartOfFrame:
                    {
                        FrameBufferDataCount = 0;

                        ThreadData.FrameLineCount = 0;

                        ThreadData.StartOfFrameDataCount = DMADataCount;
                    }
                    break;

                    case eEndOfFrame:
                    {
                        ThreadData.FrameXSize = ThreadData.LinePixelCount;
                        ThreadData.FrameYSize = ThreadData.FrameLineCount;

                        int XSize = ThreadData.LinePixelCount;
                        int YSize = ThreadData.FrameLineCount;

                        int BadFrameSize = ! ThreadData.FrameBuffer.SetFrame( XSize, YSize, sizeof( unsigned short ) );

                        ThreadData.BadFrameSizeCount += BadFrameSize;

                        if ( BadFrameSize ) return FALSE;  // frame too big for frame buffer

                        // Try to make up for limited 14-bit resolution for frame number scheme
                        __int64 FrameNumber = ThreadData.FrameNumber;

                        // If this is not first frame, restore high-order bits from previous frame
                        if ( ThreadData.LastFrameNumber != -1 )
                        {
                            FrameNumber |= ThreadData.LastFrameNumber & ~0x3fff;

                            // Check to see if 14-bit frame number has wrapped around
                            if ( FrameNumber < ThreadData.LastFrameNumber )
                            {
                                FrameNumber += 16384;  // increment high-order bits
                            }
                        }

                        // Carry out one-time initialisation of start frame number
                        SetStartFrameNumber( FrameNumber );

                        int FrameIndex = (int) ( FrameNumber - StartFrameNumber( ) );

                        // Save frame properties: FrameMinPixel, FrameMaxPixel,
                        //                        CCDNumber, FrameNumber,
                        //                        FrameIndex, GrabIndex,
                        //                        UTCTimeStamp, LocalTimeBias
                        //                        PixelClockCount, DataClockCount, PixelClockHz
                        ThreadData.FrameBuffer.SetMinPixel( ThreadData.FrameMinPixel );
                        ThreadData.FrameBuffer.SetMaxPixel( ThreadData.FrameMaxPixel );
                        ThreadData.FrameBuffer.SetCCDNumber( ThreadData.CCDNumber );
                        ThreadData.FrameBuffer.SetFrameNumber( FrameNumber );
                        ThreadData.FrameBuffer.SetFrameIndex( FrameIndex );
                        ThreadData.FrameBuffer.SetGrabIndex( ThreadData.FrameCount++ );
                        ThreadData.FrameBuffer.SetUTCTimeStamp( );
                        ThreadData.FrameBuffer.SetLocalTimeBias( );
                        ThreadData.FrameBuffer.SetPixelClockCount( ThreadData.StartOfFrameDataCount );
                        ThreadData.FrameBuffer.SetDataClockCount( ThreadData.StartOfFrameDataCount );
                        ThreadData.FrameBuffer.SetPixelClockHz( PixelClockHz( ) );

                        // Save frame number for first acquired frame
                        if ( ThreadData.FirstFrameNumber == -1 )
                        {
                            ThreadData.FirstFrameNumber = ThreadData.FrameNumber;
                        }

                        // Signal that frame buffer is ready to be processed
                        int FrameProcessingError = ! GrabCallback( eGrabFrame, CardIndex, ThreadIndex );
                        
                        ThreadData.FrameProcessingErrorCount += FrameProcessingError;

                        // Work out inter-frame clock interval
                        __int64 FrameDelta = FrameNumber - ThreadData.LastFrameNumber;

                        if ( FrameDelta > 0 )
                        {
                            ThreadData.FrameStride = (int) ( ( DMADataCount - ThreadData.EndOfFrameDataCount ) / FrameDelta );
                        }

                        // Save frame number and data count for next time
                        ThreadData.LastFrameNumber = FrameNumber;

						ThreadData.EndOfFrameDataCount = DMADataCount;

                        // Copy frame properties
                        {
                            // Write lock provides multi-threaded, non-blocking synchronisation mechanism
                            IoAWriteLock WriteLock( ThreadData.DataLock );

                            if ( WriteLock.IsAcquired( ) )
                            {
                                int BufferIndex = WriteLock.BufferIndex( );

                                ThreadData.StatusBuffer[ BufferIndex ].UpdateFrameProperties( ThreadData.FrameBuffer );

                                ThreadData.StatusBuffer[ BufferIndex ].UpdateErrorCounts(
                                    ThreadData.BadCCDNumberCount,
                                    ThreadData.BadFrameNumberCount,
                                    ThreadData.NonZeroStartLineNumberCount,
                                    ThreadData.NonConsecutiveLineNumberCount,
                                    ThreadData.BadLineLengthCount,
                                    ThreadData.BadFrameSizeCount,
                                    ThreadData.FrameProcessingErrorCount
                                );
                            }
                        }
                    }
                    break;

                    case eFoundSync:
                    {
                    }
                    break;

                    case eLostSync:
                    {
                    }
                }
/*
                // Deal with new state
                switch ( NewState )
                {
                    case eOutOfSync:  // VSync = 0, HSync = 1, or any other unrecognized pattern in the data stream
                    {
                    }
                    break;

                    case eInSync:  // VSync = 0, HSync = 0
                    {
                    }
                    break;
*/
                    if ( NewState == eInLine )
//                    case eInLine:  // VSync = 1, HSync = 1
                    {
                        // Copy valid pixel into frame buffer
                        if ( pFrameBuffer != NULL )
                        {
                            if ( FrameBufferDataCount < FrameBufferSize )
                            {
                                pFrameBuffer[ FrameBufferDataCount++ ] = DataValue;
                            }
                        }

                        // Update pixel range
                        if ( DataValue < ThreadData.FrameMinPixel ) ThreadData.FrameMinPixel = DataValue;
                        if ( DataValue > ThreadData.FrameMaxPixel ) ThreadData.FrameMaxPixel = DataValue;
                    }
//                    break;

                    else if ( NewState == eInFrame )
//                    case eInFrame:  // VSync = 1, HSync = 0
                    {
                        // Keep copy of last few invalid pixels => line header when start of line reached
                        LineHeader[ LineHeaderNext++ ] = DataValue & 0x3fff;

						LineHeaderNext &= 0x7;  // 8 element circular buffer
                    }

                    int OldIndex = ThreadData.OldIndex;
                    int NewIndex = Index & 0x3;

                    ThreadData.SyncCount[NewIndex] += 1;

                    if ( OldIndex >= 0 )
                    {
                        ThreadData.TransitionCount[OldIndex][NewIndex] += 1;
                    }

                    ThreadData.OldIndex = NewIndex;
//                }  // switch ( NewState )

                State = NewState;
            }  // for ( int i = 0; i < NumberOfSamples; i++ )

            // Store local variables into state variables
            ThreadData.State                = State;
            ThreadData.DMADataCount         = DMADataCount;
            ThreadData.LineHeaderNext       = LineHeaderNext;
            ThreadData.FrameBufferDataCount = FrameBufferDataCount;
        }
        break;

        // Waiting for next DMA buffer
        case IoAPCI7300A::eWaitingForDMABuffer:
        {
            if ( ! GrabCallback( eGrabIdle, CardIndex, ThreadIndex ) ) return FALSE;
        }
        break;

        // End of DMA processing
        case IoAPCI7300A::eEndOfDMA:
        {
            if ( ! GrabCallback( eGrabStop, CardIndex, ThreadIndex ) ) return FALSE;
        }
    }

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetDMALoopCount( int CardIndex, int &DMALoopCount )
{
	if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex < 0 || CardIndex >= m_CardCount ) return FALSE;

	DMALoopCount = m_DataGrabber[ CardIndex ].DMALoopCount( );

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetSynchronisationCounts( int CardIndex, int ThreadIndex,
                                                     __int64 &VH00Count, __int64 &VH01Count,
                                                     __int64 &VH10Count, __int64 &VH11Count )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    VH00Count = ThreadData.SyncCount[ 0 ];
    VH01Count = ThreadData.SyncCount[ 1 ];
    VH10Count = ThreadData.SyncCount[ 2 ];
    VH11Count = ThreadData.SyncCount[ 3 ];

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetTransitionCounts( int CardIndex, int ThreadIndex,
                                                __int64 &StartOfFrameCount, __int64 &EndOfFrameCount,
                                                __int64 &StartOfLineCount,  __int64 &EndOfLineCount,
                                                __int64 &InvalidTransitionCount )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    StartOfFrameCount = ThreadData.TransitionCount[ 0 ][ 2 ];  // VH = 00 -> 10
    EndOfFrameCount   = ThreadData.TransitionCount[ 2 ][ 0 ];  // VH = 10 -> 00
    StartOfLineCount  = ThreadData.TransitionCount[ 2 ][ 3 ];  // VH = 10 -> 11
    EndOfLineCount    = ThreadData.TransitionCount[ 3 ][ 2 ];  // VH = 11 -> 10

    // Invalid transition = any transition from/to VH = 01 and also any transiton between VH = 00 and VH = 11
    InvalidTransitionCount = ThreadData.TransitionCount[ 0 ][ 1 ] + ThreadData.TransitionCount[ 1 ][ 0 ] +
                             ThreadData.TransitionCount[ 1 ][ 2 ] + ThreadData.TransitionCount[ 2 ][ 1 ] +
                             ThreadData.TransitionCount[ 1 ][ 3 ] + ThreadData.TransitionCount[ 3 ][ 1 ] +
                             ThreadData.TransitionCount[ 0 ][ 3 ] + ThreadData.TransitionCount[ 3 ][ 0 ];

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetStrideParameters( int CardIndex, int ThreadIndex, int& LineStride, int& FrameStride )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    LineStride  = ThreadData.LineStride;
    FrameStride = ThreadData.FrameStride;

    return TRUE;
}

const IoAAdlinkFrameGrabber::Status *IoAAdlinkFrameGrabber::GetCurrentStatus( int CardIndex, int ThreadIndex )
{
    if ( ! IsOpen( ) ) return NULL;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return NULL;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return NULL;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    int PropertiesIndex = ThreadData.DataLock.ReadBufferIndex( );

    if ( PropertiesIndex < 0 ) return NULL;

    return ThreadData.StatusBuffer + PropertiesIndex;
}

int IoAAdlinkFrameGrabber::GetFrameCounts( int CardIndex, int ThreadIndex,
                                           int &CameraFrameCount, int &GrabFrameCount, int &DropFrameCount )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    if ( ThreadData.FirstFrameNumber == -1 || ThreadData.LastFrameNumber == -1 )
    {
        CameraFrameCount = 0;
    }
    else
    {
        CameraFrameCount = (int) ( 1 + ThreadData.LastFrameNumber - ThreadData.FirstFrameNumber );
    }

    GrabFrameCount   = (int) ThreadData.FrameCount;
    DropFrameCount   = CameraFrameCount - GrabFrameCount;

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetDMADataCount( int CardIndex, int ThreadIndex, __int64 &DMADataCount )
{
	if ( ! IsOpen( ) ) return FALSE;

	if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

	DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

	DMADataCount = ThreadData.DMADataCount;

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetCameraNumber( int CardIndex, int ThreadIndex, int &CameraNumber )
{
    return GetCCDNumber( CardIndex, ThreadIndex, CameraNumber );
}

int IoAAdlinkFrameGrabber::GetCCDNumber( int CardIndex, int ThreadIndex, int &CCDNumber )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    CCDNumber = ThreadData.CCDNumber;

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetFrameNumber( int CardIndex, int ThreadIndex, __int64 &FrameNumber )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    FrameNumber = ThreadData.FrameNumber;

    return TRUE;
}

int IoAAdlinkFrameGrabber::GetFrameParameters( int CardIndex, int ThreadIndex, int& FrameXSize, int& FrameYSize,
                                               int& LineStride, int& FrameStride )
{
    if ( ! IsOpen( ) ) return FALSE;

    if ( CardIndex   < 0 || CardIndex   >= m_CardCount      ) return FALSE;
    if ( ThreadIndex < 0 || ThreadIndex >= m_ThreadsPerCard ) return FALSE;

    DMAThreadData& ThreadData = m_DMAThreadData[ CardIndex ][ ThreadIndex ];

    FrameXSize  = ThreadData.FrameXSize;
    FrameYSize  = ThreadData.FrameYSize;
    LineStride  = ThreadData.LineStride;
    FrameStride = ThreadData.FrameStride;

    return TRUE;
}
