#if !defined( _IOAFITSFILEBUFFER_H )
#define _IOAFITSFILEBUFFER_H

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

class IoAFITSFileBuffer : public IoAFileBuffer
{
public:
    IoAFITSFileBuffer( ) { ClearFileName( ); }

    ~IoAFITSFileBuffer( ) { IoAFileBuffer::Free( ); }

    void Empty( )
    {
        IoAFileBuffer::Empty( );

        ClearFileName( );
    }

    int AddFrame( const IoAFrameBuffer& FrameBuffer )
    {
        // Make sure buffer memory is allocated
        if ( ! IoAFileBuffer::IsAllocated( ) ) return FALSE;

        // Encode FITS header
        if ( IoAFileBuffer::IsEmpty( ) )
        {
            if ( ! AddPrimaryHeader( FrameBuffer ) ) return FALSE;

            // Construct file name string containing sequence name, card index, thread index, CCD number and file index
            if ( ! SetFileName( FrameBuffer ) ) return FALSE;
        }
        else
        {
            if ( ! AddExtensionHeader( FrameBuffer ) ) return FALSE;
        }

        if ( ! AddFramePixels( FrameBuffer ) ) return FALSE;

        return TRUE;
    }

    int SetFileName( const IoAFrameBuffer& FrameBuffer )
    {
        char FileName[ 200 ];

        const char *Format = "%s_%d_%d_%02d_%09d";

        const char *SequenceName = FrameBuffer.SequenceName( );

        int CardIndex   = FrameBuffer.CardIndex( );
        int ThreadIndex = FrameBuffer.ThreadIndex( );

        int CCDNumber = FrameBuffer.CCDNumber( );

        int FileIndex = FrameBuffer.FrameIndex( ) / FrameBuffer.FramesPerFile( );

        ::sprintf( FileName, Format, SequenceName, CardIndex, ThreadIndex, CCDNumber, FileIndex );

        return SetFileName( FileName );
    }

    int SetFileName( const char *FileName )
    {
        ClearFileName( );

        if ( FileName == NULL ) return FALSE;

        int s = ::strlen( FileName );

        if ( s == 0 || s + 1 > sizeof( m_FileName ) ) return FALSE;

        ::strcpy( m_FileName, FileName );

        return TRUE;
    }

    void ClearFileName( ) { m_FileName[ 0 ] = '\0'; }

    const char *FileName( ) const { return m_FileName; }

    int IsFileNameSet( ) const { return m_FileName[ 0 ] != '\0'; }

    int SetFileExtension( const char *FileExtension )
    {
        ClearFileExtension( );

        if ( FileExtension == NULL ) return FALSE;

        int s = ::strlen( FileExtension );

        if ( s == 0 || s + 1 > sizeof( m_FileExtension ) ) return FALSE;

        ::strcpy( m_FileExtension, FileExtension );

        return TRUE;
    }

    void ClearFileExtension( ) { m_FileExtension[ 0 ] = '\0'; }

    const char *FileExtension( ) const { return m_FileExtension; }

    int IsFileExtensionSet( ) const { return m_FileExtension[ 0 ] != '\0'; }

    int Write( const char *RootFolder )
    {
        if ( RootFolder == NULL ) return FALSE;

        if ( ! IsFileNameSet( ) ) return FALSE;

        if ( ! IsFileExtensionSet( ) ) return FALSE;

        char FilePath[ 400 ];

        ::sprintf( FilePath, "%s\\%s.%s", RootFolder, FileName( ), FileExtension( ) );

        if ( ! IoAFileBuffer::AsynchronousWrite( FilePath ) ) return FALSE;

        return TRUE;
    }

private:
    int AddPrimaryHeader( const IoAFrameBuffer& FrameBuffer );
    int AddExtensionHeader( const IoAFrameBuffer& FrameBuffer );
    int AddFramePixels( const IoAFrameBuffer& FrameBuffer );

    class Argument
    {
    public:
        virtual const char *Value( ) const = 0;  // pure virtual declaration => abstract class
    };

    class Number : public Argument  // any numerical quantity, integer or floating-point
    {
    public:
        Number( int     Value ) { ::sprintf( m_Number, "%d",    Value ); }
        Number( double  Value ) { ::sprintf( m_Number, "%g",    Value ); }
        Number( __int64 Value ) { ::sprintf( m_Number, "%I64d", Value ); }

        const char *Value( ) const { return m_Number; }

    private:
        char m_Number[ 71 ];  // number string buffer
    };

    class String : public Argument  // any string of characters, automatically enclosed in quotes
    {
    public:
        String( const char *Value )
        {
            int n;

            m_String[ 0 ] = '\'';

            if ( Value != NULL )
            {
                n = ::strlen( Value );

                if ( n > sizeof( m_String ) - 3 ) n = sizeof( m_String ) - 3;

                ::CopyMemory( m_String + 1, Value, n );
            }
            else
            {
                n = 0;
            }

            m_String[ n + 1 ] = '\'';

            m_String[ n + 2 ] = '\0';
        }

        const char *Value( ) const { return m_String; }

    private:
        char m_String[ 71 ];  // character string buffer
    };

    class Constant : public Argument  // any raw, unquoted string of characters
    {
    public:
        Constant( const char *Value ) { m_Constant = Value; }

        const char *Value( ) const { return m_Constant; }

    private:
        const char *m_Constant;
    };

    class Logical : public Constant  // T or F
    {
    public:
        Logical( int Value ) : Constant( Value ? "T" : "F" ) { }
    };

    void SetKeyword( char *Card, const char *Name, const Argument& Argument, const char *Comment = NULL )
    {
        SetKeyword( Card, Name, Argument.Value( ), Comment );
    }

    void SetKeyword( char *Card, const char *Name, const char *Comment = NULL )
    {
        SetKeyword( Card, Name, NULL, Comment );
    }

    void SetKeyword( char *Card, const char *Name, const char *Value, const char *Comment );

    void SetComment( char *Card, const char *Comment );

    void SetHistory( char *Card, const char *History );

    // Swap bytes in 16/32/64-bit words
    unsigned short SwapBytes( unsigned short Value16 )
    {
        return ( Value16 << 8 ) | ( Value16 >> 8 );
    }

    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 );
    }

    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 );
    }

      short SwapBytes(   short Value16 ) { return (  short) SwapBytes( (unsigned   short) Value16 ); }
      int   SwapBytes(   int   Value32 ) { return (  int  ) SwapBytes( (unsigned   int  ) Value32 ); }
    __int64 SwapBytes( __int64 Value64 ) { return (__int64) SwapBytes( (unsigned __int64) Value64 ); }

    char m_FileName[ 100 ];
    char m_FileExtension[ 10 ];
};

#endif

