#include "stdafx.h"
#include "FITSFileBuffer.h"

int IoAFITSFileBuffer::AddPrimaryHeader( const IoAFrameBuffer& FrameBuffer )
{
    // Frame size
    int FrameXSize = FrameBuffer.XSize( );
    int FrameYSize = FrameBuffer.YSize( );

    // Range of frame pixel values
    int MinFramePixel = FrameBuffer.MinPixel( );
    int MaxFramePixel = FrameBuffer.MaxPixel( );

    // Grab sequence name and maximum number of frames per file
    const char *SequenceName  = FrameBuffer.SequenceName( );
    int         FramesPerFile = FrameBuffer.FramesPerFile( );

    // Acquisition card index and thread index
    int CardIndex   = FrameBuffer.CardIndex( );
    int ThreadIndex = FrameBuffer.ThreadIndex( );

    // Camera-encoded CCD number and frame number
      int   CCDNumber   = FrameBuffer.CCDNumber( );
    __int64 FrameNumber = FrameBuffer.FrameNumber( );

    // Frame and sequence index values (difference = dropped frame count)
    int FrameIndex = FrameBuffer.FrameIndex( );
    int GrabIndex  = FrameBuffer.GrabIndex( );
    int SaveIndex  = FrameBuffer.SaveIndex( );

    // Acquisition time and date
    FILETIME FileTimeOrigin;  // midnight at start of 1st January 1970

    SYSTEMTIME SystemTimeOrigin = { 1970, 1, 0, 1, 0, 0, 0, 0 };

    ::SystemTimeToFileTime( &SystemTimeOrigin, &FileTimeOrigin );

    IoATime TimeOrigin( FileTimeOrigin );

    // Convert to millisecond count relative to time origin
    __int64 UTCTimeStamp = ( (__int64) FrameBuffer.UTCTimeStamp( ) - (__int64) TimeOrigin ) / 10000;

    // Local time bias = UTC - local time (minutes)
    int LocalTimeBias = FrameBuffer.LocalTimeBias( );

    // Pixel/data clock count
    __int64 PixelClock = FrameBuffer.PixelClockCount( );
    __int64 DataClock  = FrameBuffer.DataClockCount( );

    // Pixel clock frequency
    int PixelClockHz = FrameBuffer.PixelClockHz( );

    // Note: FileName = SequenceName_CardIndex_ThreadIndex_CCDNumber_FileIndex
    int FileIndex = FrameIndex / FramesPerFile;

    // Encode header information as series of 80 character records
    char Header[ 2 * 36 ][ 80 ];

    // Fill header with blanks
    ::FillMemory( Header, sizeof( Header ), ' ' );

    int nCard = 0;

    SetKeyword( Header[ nCard++ ], "SIMPLE  ", Logical( TRUE ),      " file does conform to FITS standard" );
    SetKeyword( Header[ nCard++ ], "BITPIX  ", Constant( "16" ),     " number of bits per data pixel" );
    SetKeyword( Header[ nCard++ ], "NAXIS   ", Constant( "2" ),      " number of data axes" );
    SetKeyword( Header[ nCard++ ], "NAXIS1  ", Number( FrameXSize ), " length of data axis 1" );
    SetKeyword( Header[ nCard++ ], "NAXIS2  ", Number( FrameYSize ), " length of data axis 2" );
    SetKeyword( Header[ nCard++ ], "EXTEND  ", Logical( TRUE ),      " FITS dataset may contain extensions" );

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

    if ( MaxFramePixel >= 32768 )
    {
        SetKeyword( Header[ nCard++ ], "BZERO   ", Constant( "32768.0" ), " offset data range to that of unsigned short" );
        SetKeyword( Header[ nCard++ ], "BSCALE  ", Constant( "1.0" ),     " default scaling factor" );
    }

    SetKeyword( Header[ nCard++ ], "DATAMIN ", Number( MinFramePixel ), " minimum pixel value" );
    SetKeyword( Header[ nCard++ ], "DATAMAX ", Number( MaxFramePixel ), " maximum pixel value" );

    SetComment( Header[ nCard++ ], "  Non-standard Lucky keywords" );

    SetKeyword( Header[ nCard++ ], "SEQNAME ", String( SequenceName ),  " name assigned to sequence of frames" );
    SetKeyword( Header[ nCard++ ], "FILEINDX", Number( FileIndex ),     " index value assigned to this FITS file" );
    SetKeyword( Header[ nCard++ ], "FRAMEMAX", Number( FramesPerFile ), " maximum number of frames per file" );

    SetComment( Header[ nCard++ ], "  Note: FILEINDX = FRAMINDX / FRAMEMAX, for all frames in file" );

    SetKeyword( Header[ nCard++ ], "CARDINDX", Number( CardIndex ),   " data acquisition card index" );
    SetKeyword( Header[ nCard++ ], "THRDINDX", Number( ThreadIndex ), " data acquisition thread index" );

    SetKeyword( Header[ nCard++ ], "CCDNUM  ", Number( CCDNumber ),   " camera-encoded CCD number" );
    SetKeyword( Header[ nCard++ ], "FRAMENUM", Number( FrameNumber ), " camera-encoded frame number" );

    SetKeyword( Header[ nCard++ ], "FRAMINDX", Number( FrameIndex ), " zero-based, frame index" );
    SetKeyword( Header[ nCard++ ], "GRABINDX", Number( GrabIndex ),  " zero-based, grab index" );
    SetKeyword( Header[ nCard++ ], "SAVEINDX", Number( SaveIndex ),  " zero-based, save index" );

    SetComment( Header[ nCard++ ], "  Note: dropped frame count (grab) = FRAMINDX - GRABINDX" );
    SetComment( Header[ nCard++ ], "        dropped frame count (save) = GRABINDX - SAVEINDX" );
    SetComment( Header[ nCard++ ], "                       lost frames = FRAMINDX - SAVEINDX" );

    SetKeyword( Header[ nCard++ ], "FRAMTIME", Number( UTCTimeStamp ),  " UTC time stamp (msecs since 00:00, 1/1/70)" );
    SetKeyword( Header[ nCard++ ], "TIMEBIAS", Number( LocalTimeBias ), " (UTC - local time) (minutes)" );

    SetKeyword( Header[ nCard++ ], "PIXCLOCK", Number( PixelClock ), " camera-encoded pixel clock count" );
    SetKeyword( Header[ nCard++ ], "DATCLOCK", Number( DataClock ),  " acquisition card data clock count" );

    SetKeyword( Header[ nCard++ ], "PIXCLKHZ", Number( PixelClockHz ), " pixel clock frequency" );

    SetComment( Header[ nCard++ ], "  Note: dropped pixel count = PIXCLOCK - DATCLOCK" );
    SetComment( Header[ nCard++ ], "        for any pair of frames with same CCDNUM," );
    SetComment( Header[ nCard++ ], "        frame period = DELTA(PIXCLOCK) / DELTA(FRAMENUM)" );

    SetKeyword( Header[ nCard++ ], "END     " );

    // Make sure there is enough space in buffer for header
    int FITSHeaderBytes = 80 * 36 * ( ( nCard + 35 ) / 36 );

    if ( FITSHeaderBytes > IoAFileBuffer::FreeByteCount( ) ) return FALSE;

    // Copy header to file buffer
    char *Buffer = (char *) ( IoAFileBuffer::ByteAddress( ) + IoAFileBuffer::ByteDataSize( ) );

    ::CopyMemory( Buffer, Header, FITSHeaderBytes );

    IoAFileBuffer::ReduceFreeByteCount( FITSHeaderBytes );

    return TRUE;
}

int IoAFITSFileBuffer::AddExtensionHeader( const IoAFrameBuffer& FrameBuffer )
{
    // Frame size
    int FrameXSize = FrameBuffer.XSize( );
    int FrameYSize = FrameBuffer.YSize( );

    // Range of frame pixel values
    int MinFramePixel = FrameBuffer.MinPixel( );
    int MaxFramePixel = FrameBuffer.MaxPixel( );

    // Camera-encoded CCD number and frame number
      int   CCDNumber   = FrameBuffer.CCDNumber( );
    __int64 FrameNumber = FrameBuffer.FrameNumber( );

    // Frame and sequence index values (difference = dropped frame count)
    int FrameIndex = FrameBuffer.FrameIndex( );
    int GrabIndex  = FrameBuffer.GrabIndex( );
    int SaveIndex  = FrameBuffer.SaveIndex( );

    // Acquisition time and date => milliseconds since midnight, 1 Jan 1970 (aka Java time)
    FILETIME FileTimeOrigin;  // FILETIME => 100 nanosecond units since midnight, 1 Jan 1601

    SYSTEMTIME SystemTimeOrigin = { 1970, 1, 0, 1, 0, 0, 0, 0 };  // acquisition time origin

    ::SystemTimeToFileTime( &SystemTimeOrigin, &FileTimeOrigin );

    IoATime TimeOrigin( FileTimeOrigin );

    // Convert to millisecond count relative to time origin
    __int64 UTCTimeStamp = ( (__int64) FrameBuffer.UTCTimeStamp( ) - (__int64) TimeOrigin ) / 10000;
    
    // Local time bias = UTC - local time (minutes)
    int LocalTimeBias = FrameBuffer.LocalTimeBias( );

    // Pixel/data clock count
    __int64 PixelClock = FrameBuffer.PixelClockCount( );
    __int64 DataClock  = FrameBuffer.DataClockCount( );

    // Pixel clock frequency
    int PixelClockHz = FrameBuffer.PixelClockHz( );

    // Encode header information as series of 80 character records
    char Header[ 2 * 36 ][ 80 ];

    // Fill header with blanks
    ::FillMemory( Header, sizeof( Header ), ' ' );

    int nCard = 0;

    SetKeyword( Header[ nCard++ ], "XTENSION", String( "IMAGE   " ), " image extension" );
    SetKeyword( Header[ nCard++ ], "BITPIX  ", Constant( "16" ),     " number of bits per data pixel" );
    SetKeyword( Header[ nCard++ ], "NAXIS   ", Constant( "2" ),      " number of data axes" );
    SetKeyword( Header[ nCard++ ], "NAXIS1  ", Number( FrameXSize ), " length of data axis 1" );
    SetKeyword( Header[ nCard++ ], "NAXIS2  ", Number( FrameYSize ), " length of data axis 2" );
    SetKeyword( Header[ nCard++ ], "PCOUNT  ", Constant( "0" ),      " required count settings" );
    SetKeyword( Header[ nCard++ ], "GCOUNT  ", Constant( "1" ),      " for image extension" );

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

    if ( MaxFramePixel >= 32768 )
    {
        SetKeyword( Header[ nCard++ ], "BZERO   ",  Constant( "32768.0" ), " offset data range to that of unsigned short" );
        SetKeyword( Header[ nCard++ ], "BSCALE  ",  Constant( "1.0" ),     " default scaling factor" );
    }

    SetKeyword( Header[ nCard++ ], "DATAMIN ",  Number( MinFramePixel ), " minimum pixel value" );
    SetKeyword( Header[ nCard++ ], "DATAMAX ",  Number( MaxFramePixel ), " maximum pixel value" );

    SetComment( Header[ nCard++ ], "  Non-standard Lucky keywords" );

    SetKeyword( Header[ nCard++ ], "CCDNUM  ", Number( CCDNumber ),   " camera-encoded CCD number" );
    SetKeyword( Header[ nCard++ ], "FRAMENUM", Number( FrameNumber ), " camera-encoded frame number" );

    SetKeyword( Header[ nCard++ ], "FRAMINDX", Number( FrameIndex ), " zero-based, frame index" );
    SetKeyword( Header[ nCard++ ], "GRABINDX", Number( GrabIndex ),  " zero-based, grab index" );
    SetKeyword( Header[ nCard++ ], "SAVEINDX", Number( SaveIndex ),  " zero-based, save index" );

    SetComment( Header[ nCard++ ], "  Note: dropped frame count (grab) = FRAMINDX - GRABINDX" );
    SetComment( Header[ nCard++ ], "        dropped frame count (save) = GRABINDX - SAVEINDX" );
    SetComment( Header[ nCard++ ], "                       lost frames = FRAMINDX - SAVEINDX" );

    SetKeyword( Header[ nCard++ ], "FRAMTIME", Number( UTCTimeStamp ),  " UTC time stamp (msecs since 00:00, 1/1/70)" );
    SetKeyword( Header[ nCard++ ], "TIMEBIAS", Number( LocalTimeBias ), " (UTC - local time) (minutes)" );

    SetKeyword( Header[ nCard++ ], "PIXCLOCK", Number( PixelClock ), " camera-encoded pixel clock count" );
    SetKeyword( Header[ nCard++ ], "DATCLOCK", Number( DataClock ),  " acquisition card data clock count" );

    SetKeyword( Header[ nCard++ ], "PIXCLKHZ", Number( PixelClockHz ), " pixel clock frequency" );

    SetComment( Header[ nCard++ ], "  Note: dropped pixel count = PIXCLOCK - DATCLOCK" );
    SetComment( Header[ nCard++ ], "        for any pair of frames with same CCDNUM," );
    SetComment( Header[ nCard++ ], "        frame period = DELTA(PIXCLOCK) / DELTA(FRAMENUM)" );

    SetKeyword( Header[ nCard++ ], "END     " );

    // Make sure there is enough space in buffer for header
    int FITSHeaderBytes = 80 * 36 * ( ( nCard + 35 ) / 36 );

    if ( FITSHeaderBytes > IoAFileBuffer::FreeByteCount( ) ) return FALSE;

    // Copy header to file buffer
    char *Buffer = (char *) ( IoAFileBuffer::ByteAddress( ) + IoAFileBuffer::ByteDataSize( ) );

    ::CopyMemory( Buffer, Header, FITSHeaderBytes );

    IoAFileBuffer::ReduceFreeByteCount( FITSHeaderBytes );

    return TRUE;
}

int IoAFITSFileBuffer::AddFramePixels( const IoAFrameBuffer& FrameBuffer )
{
    // Make sure there is enough space in buffer for data
    int DataBytes = 2880 * ( ( FrameBuffer.FrameBytes( ) + 2879 ) / 2880 );  // round up to mutliple of 2880 bytes

    if ( DataBytes > IoAFileBuffer::FreeByteCount( ) ) return FALSE;

    // Set up pixel pointers
    unsigned short *pSrc = FrameBuffer.ShortAddress( );
    unsigned short *pDst = IoAFileBuffer::ShortAddress( ) + IoAFileBuffer::ShortDataSize( );

    // Maximum frame pixel value
    int MaxFramePixel = FrameBuffer.MaxPixel( );

    // Copy byte-swapped pixels to file buffer
    if ( MaxFramePixel < 32768 )
    {
        // Pixel values within signed 16-bit integer range => no conversion required (BZERO keyword = 0, default)
        for ( int i = 0; i < FrameBuffer.FramePixels( ); i++ )
        {
            pDst[ i ] = SwapBytes( pSrc[ i ] );
        }
    }
    else
    {
        // Pixel values exceed signed 16-bit integer range => bring values into range (BZERO keyword = 32768)
        for ( int i = 0; i < FrameBuffer.FramePixels( ); i++ )
        {
            pDst[ i ] = SwapBytes( pSrc[ i ] - 32768 ); 
        }
    }

    // Pad data with zeros up to mutliple of 2880 bytes
    int ZeroBytes = DataBytes - FrameBuffer.FrameBytes( );

    ::ZeroMemory( pDst + FrameBuffer.FramePixels( ), ZeroBytes );

    IoAFileBuffer::ReduceFreeByteCount( DataBytes );

    return TRUE;
}

void IoAFITSFileBuffer::SetKeyword( char *Card, const char *Name, const char *Value, const char *Comment )
{
    int nName = ::strlen( Name );

    if ( nName > 8 ) nName = 8;

    ::CopyMemory( Card, Name, nName );  // columns 1 -> 8

    if ( nName < 8 )
    {
        ::FillMemory( Card + nName, 8 - nName, ' ' );  // blank fill rest of name field
    }

    int maxComment = 68;  // = 80 - ( 10 + 2 )

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

        int nValue = ::strlen( Value );

        if ( nValue > 20 )
        {
            if ( nValue > 70 ) nValue = 70;

            ::CopyMemory( Card + 10, Value, nValue );  // columns 11 -> 80, left-justified

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

            maxComment -= 20;  // = 48
        }
    }

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

        int nComment = ::strlen( Comment );

        if ( nComment > maxComment ) nComment = maxComment;

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

void IoAFITSFileBuffer::SetComment( char *Card, const char *Comment )
{
    ::CopyMemory( Card, "COMMENT ", 8 );  // columns 1 -> 8

    int nComment = ::strlen( Comment );

    if ( nComment > 72 ) nComment = 72;

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

void IoAFITSFileBuffer::SetHistory( char *Card, const char *History )
{
    ::CopyMemory( Card, "HISTORY ", 8 );  // columns 1 -> 8

    int nHistory = ::strlen( History );

    if ( nHistory > 72 ) nHistory = 72;

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