#if !defined( _IOADOUBLEFITSFILEBUFFER_H )
#define _IOADOUBLEFITSFILEBUFFER_H

#include "FITSFileBuffer.h"
#include "FrameBuffer.h"
#include "Folder.h"

class IoADoubleFITSFileBuffer
{
public:
    IoADoubleFITSFileBuffer( )
    {
        m_FrameCount  = -1;
        m_BufferIndex = -1;

        ClearRootFolder( );

        ClearMaxFrameCount( );

        SetFramesPerFile( 1 );  // default = single frame per file

        SetMaxFrameCount( 0 );  // default = no limit
    }

    ~IoADoubleFITSFileBuffer( )
    {
        Close( );  // make sure all I/O operations are finished ...

        DestroyThreads( );  // before destroying threads ...
        
        Free( );  // ... and freeing buffer memory
    }

    int SetFramesPerFile( int FramesPerFile )
    {
        if ( IsOpen( ) ) return FALSE;

        if ( FramesPerFile < 1 ) return FALSE;

        m_FramesPerFile = FramesPerFile;

        return TRUE;
    }

    int FramesPerFile( ) const { return m_FramesPerFile; }

    int SetMaxFrameCount( int MaxFrameCount )
    {
        if ( IsOpen( ) ) return FALSE;

        if ( MaxFrameCount < 0 ) return FALSE;

        m_MaxFrameCount = MaxFrameCount;

        return TRUE;
    }

    void ClearMaxFrameCount( ) { m_MaxFrameCount = -1; }

    int MaxFrameCount( ) const { return m_MaxFrameCount; }

    int IsMaxFrameCountSet( ) const { return m_MaxFrameCount > 0; }

    int Allocate( int BufferSize );
    void Free( );
    int IsAllocated( ) const;

    int CreateThreads( );
    void DestroyThreads( );
    int AreThreadsRunning( ) const;

    int SetRootFolder( const char *RootFolder )
    {
        ClearRootFolder( );

        if ( RootFolder == NULL ) return FALSE;

        int s = ::strlen( RootFolder );

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

        ::strcpy( m_RootFolder, RootFolder );

        return TRUE;
    }

    void ClearRootFolder( ) { m_RootFolder[ 0 ] = '\0'; }

    const char *RootFolder( ) const { return m_RootFolder; }

    int IsRootFolderSet( ) const { return m_RootFolder[ 0 ] != '\0'; }

    int SetSequenceName( const char *SequenceName )
    {
        ClearSequenceName( );

        if ( SequenceName == NULL ) return FALSE;

        int s = ::strlen( SequenceName );

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

        ::strcpy( m_SequenceName, SequenceName );

        return TRUE;
    }

    void ClearSequenceName( ) { m_SequenceName[ 0 ] = '\0'; }

    const char *SequenceName( ) const { return m_SequenceName; }

    int IsSequenceNameSet( ) const { return m_SequenceName[ 0 ] != '\0'; }

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

        if ( FileExtension == NULL ) return FALSE;

        for ( int i = 0; i < 2; i++ )
        {
            if ( ! m_FITSFileBuffers[ i ].SetFileExtension( FileExtension ) ) return FALSE;
        }

        return TRUE;
    }

    void ClearFileExtension( )
    {
        for ( int i = 0; i < 2; i++ )
        {
            m_FITSFileBuffers[ i ].ClearFileExtension( );
        }
    }

    const char *FileExtension( ) const { return m_FITSFileBuffers[ 0 ].FileExtension( ); }

    int IsFileExtensionSet( )
    {
        for ( int i = 0; i < 2; i++ )
        {
            if ( ! m_FITSFileBuffers[ i ].IsFileExtensionSet( ) ) return FALSE;
        }

        return TRUE;
    }

    int Open( )
    {
        Close( );

        if ( ! IsAllocated( ) ) return FALSE;

        if ( ! AreThreadsRunning( ) ) return FALSE;

        if ( ! IsRootFolderSet( ) ) return FALSE;

        if ( ! IsSequenceNameSet( ) ) return FALSE;

        if ( ! IsFileExtensionSet( ) ) return FALSE;

        // Make sure root folder exists
        if ( ! IoAFolder::IsExistingFolder( RootFolder( ) ) ) return FALSE;

        // Make sure file buffers are empty
        for ( int i = 0; i < 2; i++ )
        {
            m_FITSFileBuffers[ i ].Empty( );
        }

        m_FrameCount  =  0;
        m_BufferIndex =  0;
        m_FileIndex   = -1;

        return TRUE;
    }

    void Close( )
    {
        // Make sure any remaining file buffer contents are safely written to file
        Flush( );

        m_BufferIndex = -1;
    }

    int IsOpen( ) const { return m_BufferIndex >= 0; }

    void Wait( )
    {
        for ( int i = 0; i < 2; i++ )
        {
            m_FITSFileBuffers[ i ].WaitForIOThreadIdle( );
        }
    }

    void Wait( int MillisecondTimeOut )
    {
        for ( int i = 0; i < 2; i++ )
        {
            if ( ! m_FITSFileBuffers[ i ].IsEmpty( ) )
            {
                m_FITSFileBuffers[ i ].WaitForIOThreadIdle( MillisecondTimeOut );
            }
        }
    }

    int IsValidFrameIndex( int FrameIndex ) const
    {
        if ( FrameIndex < 0 ) return FALSE;  // off limit

        if ( ! IsMaxFrameCountSet( ) ) return TRUE;  // no upper limit

        if ( FrameIndex < m_MaxFrameCount ) return TRUE;  // within upper limit

        return FALSE;  // off limit
    }

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

        int FrameIndex = FrameBuffer.FrameIndex( );

        // See if the frame does not belong in the current file buffer
        int FileIndex = FrameIndex / m_FramesPerFile;

        if ( FileIndex != m_FileIndex )
        {
            // If current file buffer is not empty, output its contents (asynchronous write)
            if ( ! m_FITSFileBuffers[ m_BufferIndex ].IsEmpty( ) )
            {
                if ( ! m_FITSFileBuffers[ m_BufferIndex ].Write( RootFolder( ) ) ) return FALSE;
            }

            // Switch file buffers
            m_BufferIndex = ( m_BufferIndex + 1 ) & 0x1;

            // If other file buffer is still being written to file, abandon this frame
            if ( m_FITSFileBuffers[ m_BufferIndex ].IsAsynchronousIOBusy( ) ) return FALSE;

            // Make sure new file buffer is empty
            m_FITSFileBuffers[ m_BufferIndex ].Empty( );

            // Update file index value
            m_FileIndex = FileIndex;
        }

        // Set sequence name
        FrameBuffer.SetSequenceName( m_SequenceName );

        // Set frames per file
        FrameBuffer.SetFramesPerFile( m_FramesPerFile );

        // We only add frame if frame index is valid, i.e. 0 <= FrameIndex < MaxFrameCount (if set)
        if ( IsValidFrameIndex( FrameIndex ) )
        {
            // Set saved frame index and increment frame count
            FrameBuffer.SetSaveIndex( m_FrameCount++ );

            // Add frame buffer contents to file buffer in FITS format
            if ( ! m_FITSFileBuffers[ m_BufferIndex ].AddFrame( FrameBuffer ) ) return FALSE;
        }
        else
        {
            // Saved frame index stays same as before
            FrameBuffer.SetSaveIndex( m_FrameCount - 1 );
        }

        return TRUE;
    }

    int Flush( )
    {
        if ( ! IsOpen( ) ) return FALSE;

        // Write any current file buffer contents to file
        if ( ! m_FITSFileBuffers[ m_BufferIndex ].IsEmpty( ) )
        {
            m_FITSFileBuffers[ m_BufferIndex ].Write( RootFolder( ) );
        }

        // Wait until all I/O threads idle
        for ( int i = 0; i < 2; i++ )
        {
            if ( ! m_FITSFileBuffers[ i ].WaitForIOThreadIdle( ) ) return FALSE;
        }

        return TRUE;
    }

private:
    // General properties
    int m_FramesPerFile;  // FileIndex = FrameIndex / FramesPerFile
    int m_MaxFrameCount;  // Close( ) triggered when FrameIndex >= MaxFrameCount

    char m_RootFolder  [ 300 ];    // defined in call to Open( )
    char m_SequenceName[ 100 ];    // defined in call to Open( )

    // Double buffering
    IoAFITSFileBuffer m_FITSFileBuffers[ 2 ];  // size defined in call to Allocate( )

    int m_FileIndex;    // = FrameIndex / FramesPerFile
    int m_BufferIndex;  // = 0 or 1

    // Saved frame count (used to determine save index)
    int m_FrameCount;
};

#endif
