#if !defined( _IOAFRAMEBUFFER_H )
#define _IOAFRAMEBUFFER_H

#include "Timer.h"

class IoAFrameBuffer
{
public:
    IoAFrameBuffer( ) : m_pBuffer( NULL ), m_BufferSize( 0 ), m_pHistogram( NULL )
    {
        ClearFrame( ); FreeHistogram( );
    }

    IoAFrameBuffer( int BufferSize ) : m_pBuffer( NULL ), m_BufferSize( 0 ), m_pHistogram( NULL )
    {
        Allocate( BufferSize ); FreeHistogram( );
    }

    // Copy constructor
    IoAFrameBuffer( const IoAFrameBuffer& Other ) : m_pBuffer( NULL ), m_BufferSize( 0 ), m_pHistogram( NULL )
    {
        Copy( Other );
    }

    ~IoAFrameBuffer( )
    {
        Free( ); FreeHistogram( );
    }

    // Memory allocation
    int Allocate( int BufferSize );

    void Free( );

    int IsAllocated( ) const { return m_pBuffer != NULL; }

    void *Address( ) const { return m_pBuffer; }

    unsigned char  *ByteAddress ( ) const { return (unsigned char  *) m_pBuffer; }
    unsigned short *ShortAddress( ) const { return (unsigned short *) m_pBuffer; }
    unsigned long  *LongAddress ( ) const { return (unsigned long  *) m_pBuffer; }

    int BufferSize( ) const { return m_BufferSize; }

    int ByteBufferSize ( ) const { return m_BufferSize / sizeof( unsigned char  ); }
    int ShortBufferSize( ) const { return m_BufferSize / sizeof( unsigned short ); }
    int LongBufferSize ( ) const { return m_BufferSize / sizeof( unsigned long  ); }

    void ZeroBuffer( ) { if ( IsAllocated( ) ) ::ZeroMemory( m_pBuffer, m_BufferSize ); }

    int Copy( const IoAFrameBuffer& Other );

    // Assignment operator
    IoAFrameBuffer& operator=( const IoAFrameBuffer& Other ) { Copy( Other ); return *this; }

    // Frame properties
    int SetFrame( int XSize, int YSize, int BytesPerPixel )
    {
        if ( XSize <= 0 || YSize <= 0 ) return FALSE;
        if ( ! ( BytesPerPixel == 1 || BytesPerPixel == 2 || BytesPerPixel == 4 ) ) return FALSE;
        if ( XSize * YSize * BytesPerPixel > m_BufferSize ) return FALSE;

        m_XSize = XSize;
        m_YSize = YSize;

        m_BytesPerPixel = BytesPerPixel;

        return TRUE;
    }

    int IsFrameValid( ) const { return m_XSize > 0 && m_YSize > 0; }

    void ClearFrame( )
    {
        m_XSize = 0;
        m_YSize = 0;

        m_BytesPerPixel = 1;

        m_MinPixel = -1;
        m_MaxPixel = -1;

        m_CCDNumber   = -1;
        m_FrameNumber = -1;

        m_FrameIndex = -1;
        m_GrabIndex  = -1;
        m_SaveIndex  = -1;

        m_UTCTimeStamp.MakePresent( );

        m_LocalTimeBias = 0;

        m_PixelClockCount = 0;
        m_DataClockCount  = 0;

        m_SequenceName = NULL;
        m_FramesPerFile = 0;
    }

    int XSize( ) const { return m_XSize; }
    int YSize( ) const { return m_YSize; }

    int IsSameSizeAs( const IoAFrameBuffer& Other )
    {
        return IsFrameValid( ) && Other.IsFrameValid( ) && XSize( ) == Other.XSize( ) && YSize( ) == Other.YSize( );
    }

    int BytesPerPixel( ) const { return m_BytesPerPixel; }

    int FramePixels( ) const { return m_XSize * m_YSize; }

    int FrameBytes( ) const { return m_XSize * m_YSize * m_BytesPerPixel; }

    void ZeroFrame( )
    {
        if ( IsAllocated( ) && IsFrameValid( ) )
        {
            ::ZeroMemory( m_pBuffer, m_XSize * m_YSize * m_BytesPerPixel );
        }
    }

    void SetMinPixel( int MinPixel ) { m_MinPixel = MinPixel; }
    void SetMaxPixel( int MaxPixel ) { m_MaxPixel = MaxPixel; }

    int MinPixel( ) const { return m_MinPixel; }
    int MaxPixel( ) const { return m_MaxPixel; }

    // Acquisition time stamp (UTC)
    void SetUTCTimeStamp( ) { m_UTCTimeStamp.MakePresent( ); }

    IoATime UTCTimeStamp( ) const { return m_UTCTimeStamp; }

    __int64 FrameTimeValue( ) const { return (__int64) m_UTCTimeStamp; }

    // Local time bias - difference between UTC and local time in minutes
    void SetLocalTimeBias( ) { m_LocalTimeBias = IoATime::LocalTimeBias( ); }
    int LocalTimeBias( ) const { return m_LocalTimeBias; }

    // Frame index (sequence number relative to start of grab)
    int SetFrameIndex( int FrameIndex )
    {
        if ( FrameIndex < 0 ) return FALSE;

        m_FrameIndex = FrameIndex;

        return TRUE;
    }

    int FrameIndex( ) const { return m_FrameIndex; }

    int DroppedFrameCount( ) const { return FrameIndex( ) - SaveIndex( ); }

    // Grab index (grab sequence number)
    int SetGrabIndex( int GrabIndex )
    {
        if ( GrabIndex < 0 ) return FALSE;

        m_GrabIndex = GrabIndex;

        return TRUE;
    }

    int GrabIndex( ) const { return m_GrabIndex; }

    int GrabDroppedFrameCount( ) const { return FrameIndex( ) - GrabIndex( ); }

    // Save index (save sequence number)
    int SetSaveIndex( int SaveIndex )
    {
        if ( SaveIndex < 0 ) return FALSE;

        m_SaveIndex = SaveIndex;

        return TRUE;
    }

    int SaveIndex( ) const { return m_SaveIndex; }

    int SaveDroppedFrameCount( ) const { return GrabIndex( ) - SaveIndex( ); }

    // CCD (Camera) number (as determined by camera system)
    int SetCCDNumber( int CCDNumber )
    {
        if ( CCDNumber < 0 ) return FALSE;

        m_CCDNumber = CCDNumber;

        return TRUE;
    }

    int CCDNumber( ) const { return m_CCDNumber; }

    int SetCameraNumber( int CameraNumber ) { return SetCCDNumber( CameraNumber ); }

    int CameraNumber( ) const { return CCDNumber( ); }

    // Frame number (as determined by camera system)
    int SetFrameNumber( __int64 FrameNumber )
    {
        if ( FrameNumber < 0 ) return FALSE;

        m_FrameNumber = FrameNumber;

        return TRUE;
    }

    __int64 FrameNumber( ) const { return m_FrameNumber; }

    // FrameID = combined bitfield representation of CCD + frame numbers
    __int64 FrameID( ) const {
        return ( (__int64) m_CCDNumber << 56 ) | ( m_FrameNumber & 0xffffffffffffff );
    }

    int AllocateHistogram( int BitsPerPixel );
    void FreeHistogram( );

    int IsHistogramAllocated( ) const { return m_BitsPerPixel && m_HistogramSize && m_pHistogram; }

    unsigned int *Histogram( ) const { return m_pHistogram;       }
    int HistogramSize      ( ) const { return m_HistogramSize;    }
    int BitsPerPixel       ( ) const { return m_BitsPerPixel;     }
    int NonEmptyBinCount   ( ) const { return m_NonEmptyBinCount; }
    int IsHistogramValid   ( ) const { return m_HistogramValid;   }

    int HistogramBytes( ) const { return HistogramSize( ) * 2; }

    int UpdateHistogram( const unsigned int *Histogram );
    int UpdateHistogram( );

    int IsEqual( const IoAFrameBuffer& Other )
    {
        if ( ! IsSameSizeAs( Other ) ) return FALSE;

        if ( m_CCDNumber != Other.CCDNumber( ) ) return FALSE;

        if ( m_FrameNumber != Other.FrameNumber( ) ) return FALSE;

        unsigned char *pByte1 = ByteAddress( );
        unsigned char *pByte2 = Other.ByteAddress( );

        for ( int i = 0; i < FrameBytes( ); i++ )
        {
            if ( pByte1[ i ] != pByte2[ i ] )
            {
                return FALSE;
            }
        }

        return TRUE;
    }

    // Pixel/data clock count
    void SetPixelClockCount( __int64 PixelClockCount ) { m_PixelClockCount = PixelClockCount; }
    __int64 PixelClockCount( ) const { return m_PixelClockCount; }

    void SetDataClockCount( __int64 DataClockCount ) { m_DataClockCount = DataClockCount; }
    __int64 DataClockCount( ) const { return m_DataClockCount; }

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

    // Grabber card/thread index values
    void SetCardIndex( int CardIndex ) { m_CardIndex = CardIndex; }
    int CardIndex( ) const { return m_CardIndex; }

    void SetThreadIndex( int ThreadIndex ) { m_ThreadIndex = ThreadIndex; }
    int ThreadIndex( ) const { return m_ThreadIndex; }

    // Save properties: sequence name, frames per file
    void SetSequenceName( const char *SequenceName ) { m_SequenceName = SequenceName; }
    const char *SequenceName( ) const { return m_SequenceName; }

    void SetFramesPerFile( int FramesPerFile ) { m_FramesPerFile = FramesPerFile; }
    int FramesPerFile( ) const { return m_FramesPerFile; }

private:
    // Buffer properties
    void *m_pBuffer;
    int   m_BufferSize;

    // Frame properties
    int m_XSize;
    int m_YSize;
    int m_BytesPerPixel;

    int m_MinPixel;
    int m_MaxPixel;

      int   m_CCDNumber;    // hardware-registered CCD number
    __int64 m_FrameNumber;  // hardware-generated frame number

    // Note: frame index = current frame number - start frame number = frame count - 1
    int m_FrameIndex;  // FrameIndex + 1 = current number of frames output by camera

    int m_GrabIndex;  // GrabIndex + 1 = current number of frames acquired from camera
    int m_SaveIndex;  // SaveIndex + 1 = current number of frames saved to file system

    // FrameIndex - GrabIndex = current number of dropped frames due to FIFO overflow/DMA buffer overrun
    // GrabIndex  - SaveIndex = current number of dropped frames due to file buffer overrun
    // FrameIndex - SaveIndex = current total number of dropped frames

    IoATime m_UTCTimeStamp;  // absolute UTC time + date (when frame acquired by host system)

    int m_LocalTimeBias;  // UTC - local time (minutes)

    // Pixel/data count (PixelClockCount - DataClockCount = dropped data count)
    __int64 m_PixelClockCount;  // pixel clock count @ StartOfFrame
    __int64 m_DataClockCount;   // data  clock count @ StartOfFrame

    int m_PixelClockHz;  // pixel clock frequency

    int m_CardIndex;    // data acquisition card
    int m_ThreadIndex;  // DMA thread

    const char *m_SequenceName;  // sequence name
    int  m_FramesPerFile;        // frames per file

    // Histogram
    int m_BitsPerPixel;
    int m_HistogramSize;
    int m_NonEmptyBinCount;
    int m_HistogramValid;

    unsigned int *m_pHistogram;
};

#endif
