#if !defined( _IOAFITSFILEBUFFER_H )
#define _IOAFITSFILEBUFFER_H

#include "FileBuffer.h"
#include "FrameBuffer.h"

class IoAFITSFileBuffer : public IoAFileBuffer
{
public:
    IoAFITSFileBuffer( )
    {
        m_FrameXSize    =  0;
        m_FrameYSize    =  0;
        m_CCDCount      =  0;
        m_FramesPerFile =  0;
        m_FileIndex     = -1;

        m_SequenceName[ 0 ] = '\0';

        m_IsOpen = FALSE;
    }

    ~IoAFITSFileBuffer( )
    {
        Free( );
    }

    int Allocate( int FrameXSize, int FrameYSize )
    {
        return Allocate( FrameXSize, FrameYSize, 1, 1 );
    }

    int Allocate( int FrameXSize, int FrameYSize, int FramesPerFile )
    {
        return Allocate( FrameXSize, FrameYSize, 1, FramesPerFile );
    }

    int Allocate( int FrameXSize, int FrameYSize, int CCDCount, int FramesPerFile )
    {
        if ( FrameXSize <= 0 || FrameYSize <= 0 ) return FALSE;
        if ( CCDCount <= 0 || CCDCount > 16 ) return FALSE;
        if ( FramesPerFile <= 0 ) return FALSE;

        Free( );

        // Primary header size = 12 keywords + 2 comments + CCD numbers + END
        int HeaderCardCount = 12 + 2 + CCDCount + 1;

        int BytesPerCard = 80;

        int HeaderBytes = BytesPerCard * 36 * ( ( HeaderCardCount + 35 ) / 36 );  // round up to multiple of 36 cards

        // Maximum frame data size
        int FrameCount = CCDCount * FramesPerFile;

        int FramePixels = FrameXSize * FrameYSize * FrameCount;

        int BytesPerPixel = 2;  // 14 bits = 2 bytes per pixel

        int PixelBytes = 2880 * ( ( FramePixels * BytesPerPixel + 2879 ) / 2880 );  // round up to multiple of 2880

        // Binary table header size = 8 keywords + 2 keywords per field + END
        int FieldCount = 4;

        int TableHeaderCardCount = 8 + 2 * FieldCount + 1;

        int TableHeaderBytes = BytesPerCard * 36 * ( ( TableHeaderCardCount + 35 ) / 36 );  // round up to multiple of 36 cards

        // Binary table record size = { int64 FrameID, int64 FrameTime, int FrameIndex, int SequenceIndex }
        int RecordBytes = 8 + 8 + 4 + 4;

        int TableBytes = 2880 * ( FrameCount * RecordBytes + 2879 ) / 2880;  // round up to multiple of 2880

        // Allocate buffer memory
        int BufferSize = HeaderBytes + PixelBytes + TableHeaderBytes + TableBytes;

        if ( ! IoAFileBuffer::Allocate( BufferSize ) ) return FALSE;

        if ( ! IoAFileBuffer::SetDataSize( BufferSize ) ) return FALSE;

        m_FrameXSize = FrameXSize;
        m_FrameYSize = FrameYSize;

        m_CCDCount = CCDCount;

        m_FramesPerFile = FramesPerFile;

        return TRUE;
    }

    int FrameXSize   ( ) { return m_FrameXSize;    }
    int FrameYSize   ( ) { return m_FrameYSize;    }
    int CCDCount     ( ) { return m_CCDCount;      }
    int FramesPerFile( ) { return m_FramesPerFile; }

    int Open( CString& SequenceName, int FileIndex, int CCDNumber )
    {
        return Open( (LPCTSTR) SequenceName, FileIndex, CCDNumber );
    }

    int Open( CString& SequenceName, int FileIndex, const int CCDNumber[ ], int CCDCount )
    {
        return Open( (LPCTSTR) SequenceName, FileIndex, CCDNumber, CCDCount );
    }

    int Open( const char *SequenceName, int FileIndex, int CCDNumber )
    {
        int CCDNumberArray[ 1 ];

        CCDNumberArray[ 0 ] = CCDNumber;

        return Open( SequenceName, FileIndex, CCDNumberArray, 1 );
    }

    int Open( const char *SequenceName, int FileIndex, const int CCDNumber[ ], int CCDCount )
    {
        if ( SequenceName == NULL || FileIndex < 0 || CCDNumber == NULL ) return FALSE;

        if ( ! IsAllocated( ) ) return FALSE;

        if ( CCDCount != m_CCDCount ) return FALSE;

        int n = ::strlen( SequenceName );

        if ( n == 0 || n > 68 ) return FALSE;

        for ( int i = 0; i < CCDCount; i++ )
        {
            if ( CCDNumber[ i ] < 0 || CCDNumber[ i ] > 15 ) return FALSE;

            m_CCDNumber[ i ] = CCDNumber[ i ];
        }

        // Primary header size = 12 keywords + 2 comments + CCD numbers + END
        int HeaderCardCount = 12 + 2 + m_CCDCount + 1;

        int BytesPerCard = 80;

        int HeaderBytes = BytesPerCard * 36 * ( ( HeaderCardCount + 35 ) / 36 );  // round up to multiple of 36 cards

        // Space occupied by frame data
        int FrameCount = m_CCDCount * m_FramesPerFile;

        int FramePixels = m_FrameXSize * m_FrameYSize * FrameCount;

        int BytesPerPixel = 2;  // 14 bits = 2 bytes per pixel

        int PixelBytes = 2880 * ( ( FramePixels * BytesPerPixel + 2879 ) / 2880 );  // round up to multiple of 2880

        // Binary table header size = 8 keywords + 2 keywords per field + END
        int FieldCount = 4;

        int TableHeaderCardCount = 8 + 2 * FieldCount + 1;

        int TableHeaderBytes = BytesPerCard * 36 * ( ( TableHeaderCardCount + 35 ) / 36 );  // round up to multiple of 36 cards

        // Binary table record size = { int64 FrameID, int64 FrameTime, int FrameIndex, int SequenceIndex }
        int TableRecordBytes = 8 + 8 + 4 + 4;

        int TableBytes = 2880 * ( FrameCount * TableRecordBytes + 2879 ) / 2880;  // round up to multiple of 2880

        // Encode primary header
        char *Header = (char *) ByteAddress( );

        ::FillMemory( Header, HeaderBytes, ' ' );  // fill header with blanks

        SetKeyword( Header,  0, "SIMPLE", "T",             " file does conform to FITS standard" );
        SetKeyword( Header,  1, "BITPIX", "16",            " number of bits per data pixel" );
        SetKeyword( Header,  2, "NAXIS",  "4",             " number of data axes" );
        SetKeyword( Header,  3, "NAXIS1", m_FrameXSize,    " length of data axis 1" );
        SetKeyword( Header,  4, "NAXIS2", m_FrameXSize,    " length of data axis 2" );
        SetKeyword( Header,  5, "NAXIS3", m_CCDCount,      " length of data axis 3" );
        SetKeyword( Header,  6, "NAXIS4", m_FramesPerFile, " length of data axis 4" );
        SetKeyword( Header,  7, "EXTEND", "T",             " FITS dataset may contain extensions" );

        SetComment( Header,  8, "  FITS (Flexible Image Transport System) format is defined in 'Astronomy" );
        SetComment( Header,  9, "  and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H" );

        SetKeyword( Header, 10, "BZERO",    "32768",      " offset data range to that of unsigned short" );
        SetKeyword( Header, 11, "BSCALE",   "1.0",        " default scaling factor" );
        SetKeyword( Header, 12, "SEQNAME",  SequenceName, " name assigned to sequence of frames" );
        SetKeyword( Header, 13, "FILEINDX", FileIndex,    " index value assigned to this FITS file" );

        for ( int CCDIndex = 0; CCDIndex < CCDCount; CCDIndex++ )
        {
            char Key[ 80 ], Comment[ 80 ];

            ::sprintf( Key, "CCD%d", 1 + CCDIndex );
            ::sprintf( Comment, " CCD number associated with camera channel %d", 1 + CCDIndex );

            SetKeyword( Header, 14 + CCDIndex, Key, CCDNumber[ CCDIndex ], Comment );
        }

        SetKeyword( Header, 14 + CCDCount, "END" );

        // Fill frame entries with zeros
        ::ZeroMemory( Header + HeaderBytes, PixelBytes );

        // Encode binary table header
        Header += HeaderBytes + PixelBytes;

        ::FillMemory( Header, TableHeaderBytes, ' ' );  // fill header with blanks

        SetKeyword( Header,  0, "XTENSION", "BINTABLE" );
        SetKeyword( Header,  1, "BITPIX",   "8" );
        SetKeyword( Header,  2, "NAXIS",    "2" );
        SetKeyword( Header,  3, "NAXIS1",   8 + 8 + 4 + 4 );
        SetKeyword( Header,  4, "NAXIS2",   FrameCount );
        SetKeyword( Header,  5, "PCOUNT",   "0" );
        SetKeyword( Header,  6, "GCOUNT",   "1" );
        SetKeyword( Header,  7, "TFIELDS",  "4" );
        SetKeyword( Header,  8, "TTYPE1",   "FrameID" );
        SetKeyword( Header,  9, "TTYPE2",   "FrameTime" );
        SetKeyword( Header, 10, "TTYPE3",   "FrameIndex" );
        SetKeyword( Header, 11, "TTYPE4",   "SequenceIndex" );
        SetKeyword( Header, 12, "TFORM1",   "K" );  // 64-bit integer
        SetKeyword( Header, 13, "TFORM2",   "K" );  // 64-bit integer
        SetKeyword( Header, 14, "TFORM3",   "J" );  // 32-bit integer
        SetKeyword( Header, 15, "TFORM4",   "J" );  // 32-bit integer
        SetKeyword( Header, 16, "END" );

        // Fill binary table entries with zeros
        ::ZeroMemory( Header + TableHeaderBytes, TableBytes );

        ::strcpy( m_SequenceName, SequenceName );

        m_FileIndex = FileIndex;

        m_IsOpen = TRUE;

        return TRUE;
    }

    const char *SequenceName( ) { return m_SequenceName; }

    int FileIndex( ) { return m_FileIndex; }

    int CCDNumber( int CCDIndex ) { return 0 <= CCDIndex && CCDIndex < 16 ? m_CCDNumber[ CCDIndex ] : -1; }

    int MinFrameIndex( ) { return m_FileIndex * m_FramesPerFile; }
    int MaxFrameIndex( ) { return MinFrameIndex( ) + m_FramesPerFile - 1; }

    int IsOpen( ) const { return m_IsOpen; }

    void Close( ) { m_IsOpen = FALSE; }

    int AddFrame( const IoAFrameBuffer& FrameBuffer )
    {
        if ( ! IsOpen( ) ) return FALSE;

        // Make sure we have valid frame size
        if ( FrameBuffer.XSize( ) != m_FrameXSize || FrameBuffer.YSize( ) != m_FrameYSize ) return FALSE;

        // Make sure we have valid frame index
        int minFrameIndex = MinFrameIndex( );
        int maxFrameIndex = MaxFrameIndex( );

        int FrameIndex = FrameBuffer.FrameIndex( );

        if ( FrameIndex < minFrameIndex || FrameIndex > maxFrameIndex ) return FALSE;

        // Make sure we have a valid CCD number
        int CCDIndex;

        for ( CCDIndex = 0; CCDIndex < m_CCDCount; CCDIndex++ )
        {
            if ( FrameBuffer.CameraNumber( ) == m_CCDNumber[ CCDIndex ] ) break;
        }

        if ( CCDIndex == m_CCDCount ) return FALSE;  // CCD number is not listed

        // Copy frame buffer contents to file buffer
        int HeaderCardCount = 12 + 2 + m_CCDCount + 1;

        int BytesPerCard = 80;

        int HeaderBytes = BytesPerCard * 36 * ( ( HeaderCardCount + 35 ) / 36 );  // round up to multiple of 36 cards

        int FrameOffset = m_CCDCount * ( FrameIndex - minFrameIndex ) + CCDIndex;

        int FrameByteOffset = FrameOffset * FrameBuffer.FrameBytes( );

        // Set up pixel pointers
        unsigned short *pSrc = FrameBuffer.ShortAddress( );
        unsigned short *pDst = (unsigned short *) ( ByteAddress( ) + HeaderBytes + FrameByteOffset );

        // Copy byte-swapped pixels to file buffer
        for ( int i = 0; i < FrameBuffer.FramePixels( ); i++ )
        {
            pDst[ i ] = SwapBytes( pSrc[ i ] );
        }

        // Copy frame ID, frame time, frame index and sequenceIndex to binary table
        int FramePixels = m_CCDCount * m_FramesPerFile * FrameBuffer.FrameBytes( );

        int BytesPerPixel = 2;  // 14 bits = 2 bytes per pixel

        int PixelBytes = 2880 * ( ( FramePixels * BytesPerPixel + 2879 ) / 2880 );  // 14 bits = 2 bytes per pixel

        // Binary table header size = 8 keywords + 2 keywords per field + END
        int FieldCount = 4;

        int TableHeaderCardCount = 8 + 2 * FieldCount + 1;

        int TableHeaderBytes = BytesPerCard * 36 * ( ( TableHeaderCardCount + 35 ) / 36 );  // round up to multiple of 36 cards

        char *TableAddress = (char *) ( ByteAddress( ) + HeaderBytes + PixelBytes + TableHeaderBytes );

        // Binary table record size = { int64 FrameID, int64 FrameTime, int FrameIndex, int SequenceIndex }
        int TableRecordBytes = 8 + 8 + 4 + 4;

        char *TableRowAddress = TableAddress + FrameOffset * TableRecordBytes;

        *( (__int64 *) TableRowAddress     ) = SwapBytes( FrameBuffer.FrameID       ( ) );
        *( (__int64 *) TableRowAddress + 8 ) = SwapBytes( FrameBuffer.FrameTimeValue( ) );

        *( (int *) TableRowAddress + 8 + 8     ) = SwapBytes( FrameBuffer.FrameIndex   ( ) );
        *( (int *) TableRowAddress + 8 + 8 + 4 ) = SwapBytes( FrameBuffer.SequenceIndex( ) );

        return TRUE;
    }

private:
    void SetKeyword( char *Header, int CardIndex, const char *Name, int Value, const char *Comment = NULL )
    {
        char ValueString[ 80 ];

        ::sprintf( ValueString, "%d", Value );

        SetKeyword( Header, CardIndex, Name, ValueString, Comment );
    }

    void SetKeyword( char *Header, int CardIndex, const char *Name, __int64 Value, const char *Comment = NULL )
    {
        char ValueString[ 80 ];

        ::sprintf( ValueString, "%I64d", Value );

        SetKeyword( Header, CardIndex, Name, ValueString, Comment );
    }

    void SetKeyword( char *Header, int CardIndex, const char *Name, double Value, const char *Comment = NULL )
    {
        char ValueString[ 80 ];

        ::sprintf( ValueString, "%g", Value );

        SetKeyword( Header, CardIndex, Name, ValueString, Comment );
    }

    void SetKeyword( char *Header, int CardIndex, const char *Name, const char *Value = NULL, const char *Comment = NULL )
    {
        char *Card = Header + 80 * CardIndex;

        // Assume strlen( Name ) <= 8
        ::strcpy( Card, Name );  // columns 1 -> 8

        if ( Value != NULL )
        {
            ::strcpy( Card + 8, "= " );  // columns 9 -> 10

            int maxComment;

            int nValue = ::strlen( Value );

            if ( nValue > 20 )
            {
                // Assume strlen( Value ) <= 70
                ::strncpy( Card + 10, Value, nValue );  // columns 11 -> 80, left-justified

                maxComment = 68 - nValue;  // = 80 - ( 10 + nValue + 2 );
            }
            else
            {
                ::strncpy( Card + 30 - nValue, Value, nValue );  // columns 11 -> 30, right-justified

                maxComment = 48;  // = 80 - ( 30 + 2 )
            }

            if ( Comment != NULL && maxComment > 0 )
            {
                ::strcpy( Card + 80 - maxComment - 2, " /" );  // append comment separator

                int nComment = ::strlen( Comment );

                if ( nComment > maxComment ) nComment = maxComment;

                ::strncpy( Card + 80 - maxComment, Comment, nComment );  // append comment
            }
        }
    }

    void SetComment( char *Header, int CardIndex, const char *Comment )
    {
        char *Card = Header + 80 * CardIndex;

        ::strcpy( Card, "COMMENT" );  // columns 1 -> 8

        int nComment = ::strlen( Comment );

        if ( nComment > 72 ) nComment = 72;

        ::strncpy ( Card + 8, Comment, nComment );  // columns 9 -> 80
    }

    void SetHistory( char *Header, int CardIndex, const char *History )
    {
        char *Card = Header + 80 * CardIndex;

        ::strcpy( Card, "HISTORY" );  // columns 1 -> 8

        int nHistory = ::strlen( History );

        if ( nHistory > 72 ) nHistory = 72;

        ::strncpy ( Card + 8, History, nHistory );  // columns 9 -> 80
    }

    unsigned short SwapBytes( unsigned short Value16 )
    {
        return ( Value16 << 8 ) | ( Value16 >> 8 );
    }

    short SwapBytes( short Value16 ) { return (short) SwapBytes( (unsigned short) Value16 ); }

    unsigned int SwapBytes( unsigned int Value32 )
    {
        // Swap adjacent bytes
        Value32 = ( ( Value32 >> 8 ) & 0x00ff00ff ) | ( ( Value32 << 8 ) & 0xff00ff00 );

        // Swap adjacent 16-bit words
        return ( Value32 << 16 ) | ( Value32 >> 16 );
    }

    int SwapBytes( int Value32 ) { return (int) SwapBytes( (unsigned int) Value32 ); }

    unsigned __int64 SwapBytes( unsigned __int64 Value64 )
    {
        // Swap adjacent bytes
        Value64 = ( ( Value64 >>  8 ) & 0x00ff00ff00ff00ff ) | ( ( Value64 <<  8 ) & 0xff00ff00ff00ff00 );

        // Swap adjacent 16-bit words
        Value64 = ( ( Value64 >> 16 ) & 0x0000ffff0000ffff ) | ( ( Value64 << 16 ) & 0xffff0000ffff0000 );

        // Swap adjacent 32-bit words
        return ( Value64 << 32 ) | ( Value64 >> 32 );
    }

    __int64 SwapBytes( __int64 Value64 ) { return (__int64) SwapBytes( (unsigned __int64) Value64 ); }

private:
    int m_FrameXSize;
    int m_FrameYSize;
    int m_CCDCount;
    int m_FramesPerFile;

    int m_FileIndex;

    int m_IsOpen;

    char m_SequenceName[ 80 ];

    int m_CCDNumber[ 16 ];
};

/*
// Utility class for encoding/decoding FITS header
class IoAFITSHeader
{
public:
    // Set up access to old header
    IoAFITSHeader( const void *BufferAddress, int BufferSize )
    {
        m_pHeader = (const char *) BufferAddress;

        m_MaxGetCount = 0;

        if ( BufferSize > 0 )
        {
            int MaxCardCount   = BufferSize   / 80;
            int MaxRecordCount = MaxCardCount / 36;

            if ( MaxRecordCount > 0 )  // Buffersize >= 36 * 80 = 2880
            {
                m_MaxGetCount = MaxRecordCount * 36;

                int Index = GetIndex( "END" );  // search for "END" keyword

                m_MaxGetCount = Index >= 0 ? Index + 1 : 0;
            }
        }

        m_MaxSetCount = 0;
    }

    // Set up access to new header
    IoAFITSHeader( void *BufferAddress, int BufferSize, int MaxCardCount )
    {
        m_pHeader = (const char *) BufferAddress;

        m_MaxGetCount = 0;
        m_MaxSetCount = 0;

        if ( BufferSize > 0 )
        {
            int MaxCardCount   = BufferSize   / 80;
            int MaxRecordCount = MaxCardCount / 36;

            if ( MaxRecordCount > 0 )  // Buffersize >= 36 * 80 = 2880
            {
                MaxCardCount  = MaxRecordCount * 36;
                MaxBufferSize = MaxCardCount   * 80;
            }
        }
    }

    int SetKeyword( int Index, const char *Name, const char *Value = NULL, const char *Comment = NULL )
    {
        return SetName( Index, Name ) && SetValue( Index, Value ) && SetComment( Index, Comment );
    }

    int SetName( int Index, const char *Name, const char *Comment = NULL)
    {
        if ( Index < 0 || Index >= m_MaxSetCount ) return FALSE;

        if ( Name != NULL) return FALSE;

        int Count[ 4 ];

        Parse( Name, Count );

        int length = Count[ 0 ];  // string length
        int blanks = Count[ 1 ];  // blank count
        int nlead  = Count[ 2 ];  // leading blank count
        int ntrail = Count[ 3 ];  // trailing blank count

        // Make sure there are no embedded blanks (i.e. all blanks are leading or trailing)
        if ( blanks > nlead + ntrail ) return FALSE;

        length = length - ( nlead + ntrail );  // discard leading + trailing blanks

        if ( length < 8 ) return FALSE;

        char *pHeader = m_pHeader + Index * 80;

        ::CopyMemory( pHeader, Name + nlead, length - blanks );  // copy name string

        ::FillMemory( pHeader + length, 8 - length, ' ' );  // blank fill rest of name field

        return TRUE;
    }

    int SetValue( int Index, const char *Value, const char *Comment = NULL )
    {
        if ( Index < 0 || Index >= m_MaxSetCount ) return FALSE;

        if ( Value != NULL) return FALSE;

        int Count[ 4 ];

        Parse( Value, Count );

        int length = Count[ 0 ];  // string length
        int blanks = Count[ 1 ];  // blank count
        int nlead  = Count[ 2 ];  // leading blank count
        int ntrail = Count[ 3 ];  // trailing blank count

        // See if value is non-blank string
        if ( length > blanks )
        {
            char *pHeader = m_pHeader + Index * 80;

            // Encode value indicator = "= "
            m_pHeader[ 8 ] = '=';
            m_pHeader[ 9 ] = ' ';

            length = length - ( nlead + ntrail );  // discard leading + trailing blanks

            // If value occupies more than 20 characters, left-justify from column 10
            if ( length > 20 )
            {
                ::CopyMemory( pHeader + 10, Value + nlead, length );  // copy value string

                :: FillMemory( pHeader + 10 + length, 80 - 10 + length, ' ' );  // blank fill rest of card image
            }

            // Otherwise, right-justify from column 30
            else
            {
                ::CopyMemory( pHeader + 30 - length, Value + nlead, length );  // copy value string

                :: FillMemory( pHeader + 30, 50, ' ' );  // blank fill rest of card image
            }
        }

        return TRUE;
    }

    void Parse( const char *String, int Count[ 3 ] )
    {
        int ns = 0, nb = 0, nl = 0; nt = 0;

        if ( String != NULL )
        {
            while ( String[ ns ] != '\0' )
            {
                if ( String[ ns ] ) nb += 1;

                ns += 1;
            }

            while ( nl < ns && String[              nl ] == ' ' ) nl += 1;

            while ( nt < ns && String[ ( ns - 1 ) - nt ] == ' ' ) nt += 1;
        }

        Count[ 0 ] = ns;  // string length
        Count[ 1 ] = nb;  // blank count
        Count[ 2 ] = nl;  // leading blank count
        Count[ 3 ] = nt;  // trailing blank count
    }

    int GetIndex( const char *pName )
    {
        if ( m_MaxGetCount == 0 ) return FALSE;

        if ( ::strlen( pName ) > 8 ) return FALSE;

        for ( int Index = 0; Index < m_MaxGetCount; Index++ )
        {
            char Name[ 9 ];

            if ( GetName( Index, Name ) )
            {
                if ( ::strcmp( Name, pName ) == 0 )
                {
                    return Index;
                }
            }
        }

        return -1;
    }

    int GetName( int Index, char *pName)
    {
        if ( Index < 0 || Index >= m_MaxGetCount ) return FALSE;

        const char *pHeader = m_pGetHeader + Index * 80;

        // Copy up to 8 non-blank characters
        for ( int i = 0; i < 8 && *pHeader != ' '; i++ ) *pName++ = *pHeader++;

        // Add terminating null
        *pName = '\0';

        return TRUE;
    }

    int GetValue( int Index, char *Value)
    {
        if ( Index < 0 || Index >= m_MaxGetCount ) return FALSE;

        const char *pHeader = m_pGetHeader + Index * 80 + 10;

        return TRUE;
    }

    int GetComment( int Index, char *Comment)
    {
        if ( Index < 0 || Index >= m_MaxGetCount ) return FALSE;

        return TRUE;
    }

    int GetKeyword( int Index, char *Name, char *Value = NULL, char *Comment = NULL )
    {
        return GetName( Index, Name ) && GetValue( Index, Value ) && GetComment( Index, Comment );
    }

private:
    char *m_pHeader;

    int m_MaxGetCount;  // new + old headers
    int m_MaxSetCount;  // new headers only
};

class IoAOldFITSFileBuffer : public IoAFileBuffer
{
public:
    IoAOldFITSFileBuffer( )
    {
        m_MaxKeywordCount   = 0;
        m_MaxDataBytes      = 0;
        m_MaxExtensionBytes = 0;

        m_KeywordCount   = 0;
        m_DataBytes      = 0;
        m_ExtensionBytes = 0;
    }

    // FITS Buffer
    int Allocate( int MaxKeywordCount, int MaxDataBytes, int MaxExtensionBytes = 0 );

    int MaxKeywordCount  ( ) { return m_MaxKeywordCount; }
    int MaxDataBytes     ( ) { return m_MaxDataBytes; }
    int MaxExtensionBytes( ) { return m_MaxExtensionBytes; }

    int KeywordCount  ( ) { return m_KeywordCount; }
    int DataBytes     ( ) { return m_DataBytes; }
    int ExtensionBytes( ) { return m_ExtensionBytes; }

    void Free( );

    int HeaderByteCount( ) { return 80 * 36 * ( ( m_MaxKeywordCount + 35 ) / 36 ); }

    int DataByteCount( ) { return 2880 * ( ( m_DataBytes + 2879 ) / 2880 ); }

    int ExtensionByteCount( ) { return 2880 * ( ( m_ExtensionBytes + 2879 ) / 2880 ); }

    int FileByteCount( ) { return HeaderByteCount( ) + DataByteCount( ) + ExtensionByteCount( ); }

    char *HeaderAddress( ) { return (char *) ByteAddress( ); }

    void *DataAddress( ) { return (void *) ( ByteAddress( ) + HeaderByteCount( ) ); }

    void *ExtensionAddress( ) { return (void *) ( ByteAddress( ) + HeaderByteCount( ) + DataByteCount( ) ); }

    void ClearHeader( )
    {
        // Make sure file buffer is allocated
        if ( IsAllocated( ) ) {
            ::FillMemory( HeaderAddress( ), HeaderByteCount( ), ' ' );  //blank fill header
        }

        m_KeywordCount = 0;
    }

    void ClearData ( ) { m_DataBytes = 0; }

    void ClearExtension( ) { m_ExtensionBytes = 0; }

    void Clear( ) { ClearHeader( ); ClearData( ); ClearExtension( ); }

    // FITS Header = collection of FITS keywords

    // Add a new keyword
    int NewKeyword( const char *Name );

    // Find a keyword by name
    int KeywordIndex( const char *Name );

    // Set keyword value to floating-point number
    int SetValue( const char *Name, double Value, const char *Comment = NULL )
    {
        return SetValue( KeywordIndex( Name ), Value, Comment );
    }

    int SetValue( int KeywordIndex, double Value, const char *Comment = NULL )
    {
        char ValueString[ 80 ];

        ::_gcvt( Value, 13, ValueString );  // convert floating-point value to string

        return SetValue( KeywordIndex, ValueString, Comment );
    }

    // Set keyword value to 64-bit integer number
    int SetValue( const char *Name, __int64 Value, const char *Comment = NULL )
    {
        return SetValue( KeywordIndex( Name ), Value, Comment );
    }

    int SetValue( int KeywordIndex, __int64 Value, const char *Comment = NULL )
    {
        char ValueString[ 80 ];

        ::_i64toa( Value, ValueString, 10 );  // convert 64-bit integer value to string

        return SetValue( KeywordIndex, ValueString, Comment );
    }

    // Set keyword value to 32-bit integer number
    int SetValue( const char *Name, int Value, const char *Comment = NULL )
    {
        return SetValue( KeywordIndex( Name ), Value, Comment );
    }

    int SetValue( int KeywordIndex, int Value, const char *Comment = NULL )
    {
        char ValueString[ 80 ];

        ::_itoa( Value, ValueString, 10 );  // convert integer value to string

        return SetValue( KeywordIndex, ValueString, Comment );
    }

    // Set keyword value to character string
    int SetValue( const char *Name, const char *Value, const char *Comment = NULL )
    {
        return SetValue( KeywordIndex( Name ), Value, Comment );
    }

    int SetValue( int KeywordIndex, const char *Value, const char *Comment = NULL );

    // Get keyword value as 32-bit integer number
    int IntegerValue( const char *Name, int DefaultValue = 0 )
    {
        return IntegerValue( KeywordIndex( Name ), DefaultValue );
    }

    int IntegerValue( int KeywordIndex, int DefaultValue = 0 )
    {
        char ValueString[ 80 ];

        return GetValue( KeywordIndex, ValueString ) > 0 ? ::atoi( ValueString ) : DefaultValue;
    }

    // Get keyword value as 64-bit integer number
    __int64 LongIntegerValue( const char *Name, __int64 DefaultValue = 0 )
    {
        return LongIntegerValue( KeywordIndex( Name ), DefaultValue );
    }

    __int64 LongIntegerValue( int KeywordIndex, __int64 DefaultValue = 0 )
    {
        char ValueString[ 80 ];

        return GetValue( KeywordIndex, ValueString ) > 0 ? _atoi64( ValueString ) : DefaultValue;
    }

    // Get keyword value as floating-point number
    double FloatingPointValue( const char *Name, double DefaultValue = 0.0 )
    {
        return FloatingPointValue( KeywordIndex( Name ), DefaultValue );
    }

    double FloatingPointValue( int KeywordIndex, double DefaultValue = 0.0 )
    {
        char ValueString[ 80 ];

        return GetValue( KeywordIndex, ValueString ) > 0 ? ::strtod( ValueString, NULL ) : DefaultValue;
    }

    // Get keyword value as character string
    int GetValue( const char *Name, char *Value )
    {
        return GetValue( KeywordIndex( Name ), Value );
    }

    int GetValue( int KeywordIndex, char *Value );

    // Commentary keyword
    int NewComment( const char *Comment ) { return NewComment( "COMMENT", Comment ); }

    int NewComment( const char *Name, const char *Comment );

    // FITS data = collection of data records
    int NewData( const void *Data, int DataBytes );

    int GetData( int DataOffset, void *Data, int DataBytes );

    // FITS extension = collection of extension records
    int NewExtension( const void *Extension, int ExtensionBytes );

    int GetExtension( int ExtensionOffset, void *Extension, int ExtensionBytes );

private:
    int ValidKeyword( const char *Name )
    {
        if ( Name == NULL ) return FALSE;

        int nName = ::strlen( Name );

        if ( nName > 8 ) return FALSE;

        for ( int iName = 0; iName < nName; iName++ )
        {
            if ( ! ValidCharacter( Name[ iName ] ) ) return FALSE;
        }

        return TRUE;
    }

    int ValidCharacter( char cha )
    {
        if ( 'A' <= cha && cha <= 'Z' ) return TRUE;  // upper-case
        if ( '0' <= cha && cha <= '9' ) return TRUE;  // decimal numeric

        if ( cha == '-' ) return TRUE;  // hyphen
        if ( cha == '_' ) return TRUE;  // underscore

        return FALSE;
    }

    int m_MaxKeywordCount;
    int m_MaxDataBytes;
    int m_MaxExtensionBytes;

    int m_KeywordCount;
    int m_DataBytes;
    int m_ExtensionBytes;
};
*/
#endif

