//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools frameCPP is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools frameCPP is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

/*
 */
#ifndef FRAMECPP__FRAME_C_STREAM_INTERNAL_H
#define FRAMECPP__FRAME_C_STREAM_INTERNAL_H

#include "ldastoolsal/unordered_map.hh"

#include "framecpp/Common/FrHeader.hh"

#include "framecpp/FrDetector.hh"
#include "framecpp/FrEvent.hh"
#include "framecpp/IFrameStream.hh"
#include "framecpp/OFrameStream.hh"

struct fr_file : public FrameC::Handle
{
    fr_file( );

    ~fr_file( );

    void CloseStream( );

    int FrameLibrary( ) const;

    int FrameLibraryName( char* Buffer, size_t BufferSize ) const;

    int FrameLibraryVersion( ) const;

    int FrameLibraryVersionMinor( ) const;

    union
    {
        FrameCPP::IFrameStream* input;
        FrameCPP::OFrameStream* output;
    } s_stream;

    fr_file_mode_t s_mode;
    LDASTools::AL::unordered_map< std::string, FrameCPP::FrDetector >
                s_detectors;
    std::string filename;
};

inline fr_file::fr_file( )
    : Handle( FrameC::POINTER_STREAM ), s_mode( FRAMEC_FILE_MODE_UNSET )
{
    s_stream.input = (FrameCPP::IFrameStream*)NULL;
}

inline fr_file::~fr_file( )
{
    try
    {
        CloseStream( );
    }
    catch ( ... )
    {
    }
}

inline void
fr_file::CloseStream( )
{
    if ( s_mode == FRAMEC_FILE_MODE_INPUT )
    {
        FrameCPP::IFrameStream* fs( s_stream.input );

        if ( fs )
        {
            delete fs;
            s_stream.input = (FrameCPP::IFrameStream*)NULL;
        }
        else
        {
            throw std::invalid_argument( "Stream is not open" );
        }
    }
    else if ( s_mode == FRAMEC_FILE_MODE_OUTPUT )
    {
        FrameCPP::OFrameStream* fs( s_stream.output );

        if ( fs )
        {
            delete fs;
            s_stream.output = (FrameCPP::OFrameStream*)NULL;
        }
        else
        {
            throw std::invalid_argument( "Stream is not open" );
        }
    }
    else
    {
        throw std::invalid_argument( "Handle does not reference a stream" );
    }
}

inline int
fr_file::FrameLibrary( ) const
{
    if ( s_mode == FRAMEC_FILE_MODE_INPUT )
    {
        FrameCPP::IFrameStream* fs( s_stream.input );

        if ( fs )
        {
            return fs->FrameLibrary( );
        }
    }
    else if ( s_mode == FRAMEC_FILE_MODE_OUTPUT )
    {
        FrameCPP::OFrameStream* fs( s_stream.output );

        if ( fs )
        {
            return fs->FrameLibrary( );
        }
    }
    throw std::invalid_argument( "Handle does not reference a stream" );
}

inline int
fr_file::FrameLibraryName( char* Buffer, size_t BufferSize ) const
{
    if ( s_mode == FRAMEC_FILE_MODE_INPUT )
    {
        FrameCPP::IFrameStream* fs( s_stream.input );

        if ( fs )
        {
            std::string name( fs->GetFrHeader( ).GetFrameLibraryName( ) );

            ::strncpy( Buffer, name.c_str( ), BufferSize );
            Buffer[ BufferSize - 1 ] = '\0';
            return 1;
        }
    }
    else if ( s_mode == FRAMEC_FILE_MODE_OUTPUT )
    {
        FrameCPP::OFrameStream* fs( s_stream.output );

        if ( fs )
        {
            std::string name( FrameCPP::Common::FrHeader::GetFrameLibraryName(
                fs->FrameLibrary( ) ) );

            ::strncpy( Buffer, name.c_str( ), BufferSize );
            Buffer[ BufferSize - 1 ] = '\0';
            return 1;
        }
    }
    throw std::invalid_argument( "Handle does not reference a stream" );
}

inline int
fr_file::FrameLibraryVersion( ) const
{
    if ( s_mode == FRAMEC_FILE_MODE_INPUT )
    {
        FrameCPP::IFrameStream* fs( s_stream.input );

        if ( fs )
        {
            return fs->Version( );
        }
    }
    else if ( s_mode == FRAMEC_FILE_MODE_OUTPUT )
    {
        FrameCPP::OFrameStream* fs( s_stream.output );

        if ( fs )
        {
            return fs->Version( );
        }
    }
    throw std::invalid_argument( "Handle does not reference a stream" );
}

inline int
fr_file::FrameLibraryVersionMinor( ) const
{
    if ( s_mode == FRAMEC_FILE_MODE_INPUT )
    {
        FrameCPP::IFrameStream* fs( s_stream.input );

        if ( fs )
        {
            return fs->LibraryRevision( );
        }
    }
    else if ( s_mode == FRAMEC_FILE_MODE_OUTPUT )
    {
        FrameCPP::OFrameStream* fs( s_stream.output );

        if ( fs )
        {
            return fs->LibraryRevision( );
        }
    }
    throw std::invalid_argument( "Handle does not reference a stream" );
}

namespace FrameC
{
    inline FrameCPP::IFrameStream*
    StreamAsInput( fr_file_t* Stream )
    {
        typedef FrameCPP::IFrameStream stream_type;
        Handle::Validate( Stream, POINTER_STREAM );

        stream_type* retval = (stream_type*)NULL;

        if ( Stream->s_mode == FRAMEC_FILE_MODE_INPUT )
        {
            retval = Stream->s_stream.input;
        }
        if ( retval == (stream_type*)NULL )
        {
            throw std::runtime_error(
                "The frame stream is not open for reading" );
        }
        return retval;
    }

    inline FrameCPP::OFrameStream*
    StreamAsOutput( fr_file_t* Stream )
    {
        typedef FrameCPP::OFrameStream stream_type;

        Handle::Validate( Stream, POINTER_STREAM );

        stream_type* retval = (stream_type*)NULL;

        if ( Stream->s_mode == FRAMEC_FILE_MODE_OUTPUT )
        {
            retval = Stream->s_stream.output;
        }
        if ( retval == (stream_type*)NULL )
        {
            throw std::runtime_error(
                "The frame stream is not open for writing" );
        }
        return retval;
    }
} // namespace FrameC

#endif /* FRAMECPP__FRAME_C_STREAM_INTERNAL_H */
