//
// 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 FRAME_CPP_INTERFACE__FR_CHAN_INTERNAL_HH
#define FRAME_CPP_INTERFACE__FR_CHAN_INTERNAL_HH

#include <boost/shared_ptr.hpp>

#include "framecpp/FrEvent.hh"
#include "framecpp/FrSimData.hh"
#include "framecpp/FrSimEvent.hh"
#include "framecpp/FrVect.hh"

#include "framecppc/FrVect.h"

namespace FrameC
{
    struct FrChannel : Handle
    {
        typedef boost::shared_ptr< ::FrameCPP::FrVect >       vector_type;
        typedef boost::shared_ptr< ::FrameCPP::FrVect > const const_vector_type;

        FrChannel( );

        FrChannel( const std::string& Name,
                   fr_chan_type,
                   fr_vect_data_types_t DataType,
                   fr_vect_ndata_t      NData );

        FrChannel( const std::string&   Name,
                   fr_proc_type         Type,
                   fr_proc_sub_type     SubType,
                   fr_vect_data_types_t DataType,
                   fr_vect_ndata_t      NData );

        virtual ~FrChannel( );

        void Read( ::fr_file_t*           Stream,
                   const ::fr_chan_name_t Name,
                   ::fr_chan_offset_t     Offset );

        const_vector_type Vector( ) const;

        vector_type Vector( );

        vector_type Vector( fr_vect_data_types_t DataType,
                            fr_vect_ndata_t      NData );

        static ::FrameCPP::FrVect::compression_scheme_type
        CompressionScheme( fr_vect_compression_schemes_t Scheme );

        static fr_vect_compression_schemes_t
        CompressionScheme( ::FrameCPP::FrVect::compress_type Scheme );

        template < class T >
        inline boost::shared_ptr< T > Channel( );

        enum fr_chan_type Type( ) const;

    private:
        friend void ::FrameCFrChanQuery( FrameCError**    Error,
                                         const fr_chan_t* Channel,
                                         int              Options,
                                         ... );
        friend void ::FrameCFrChanSet( FrameCError**    Error,
                                       const fr_chan_t* Channel,
                                       int              Options,
                                       ... );

        struct channel_t
        {
        public:
            typedef boost::shared_ptr< ::FrameCPP::FrAdcData >  fr_adc_data;
            typedef boost::shared_ptr< ::FrameCPP::FrEvent >    fr_event;
            typedef boost::shared_ptr< ::FrameCPP::FrProcData > fr_proc_data;
            typedef boost::shared_ptr< ::FrameCPP::FrSimData >  fr_sim_data;
            typedef boost::shared_ptr< ::FrameCPP::FrSimEvent > fr_sim_event;

            enum fr_chan_type type;

            fr_adc_data  adc;
            fr_event     event;
            fr_proc_data proc;
            fr_sim_data  sim;
            fr_sim_event sim_event;
        } m_channel;
    };

    template <>
    inline FrChannel::channel_t::fr_adc_data
    FrChannel::Channel< FrameCPP::FrAdcData >( )
    {
        if ( m_channel.type == FR_ADC_CHAN_TYPE )
        {
            return m_channel.adc;
        }
        return channel_t::fr_adc_data( );
    }

    template <>
    inline FrChannel::channel_t::fr_event
    FrChannel::Channel< FrameCPP::FrEvent >( )
    {
        if ( m_channel.type == FR_EVENT_CHAN_TYPE )
        {
            return m_channel.event;
        }
        return channel_t::fr_event( );
    }

    template <>
    inline FrChannel::channel_t::fr_proc_data
    FrChannel::Channel< FrameCPP::FrProcData >( )
    {
        if ( m_channel.type == FR_PROC_CHAN_TYPE )
        {
            return m_channel.proc;
        }
        return channel_t::fr_proc_data( );
    }

    template <>
    inline FrChannel::channel_t::fr_sim_data
    FrChannel::Channel< FrameCPP::FrSimData >( )
    {
        if ( m_channel.type == FR_SIM_CHAN_TYPE )
        {
            return m_channel.sim;
        }
        return channel_t::fr_sim_data( );
    }

    template <>
    inline FrChannel::channel_t::fr_sim_event
    FrChannel::Channel< FrameCPP::FrSimEvent >( )
    {
        if ( m_channel.type == FR_SIM_EVENT_CHAN_TYPE )
        {
            return m_channel.sim_event;
        }
        return channel_t::fr_sim_event( );
    }

    inline ::FrameCPP::FrVect::compression_scheme_type
    FrChannel::CompressionScheme( fr_vect_compression_schemes_t Scheme )
    {
        ::FrameCPP::FrVect::compression_scheme_type retval;

        switch ( Scheme & ~FR_VECT_COMPRESS_LITTLEENDIAN )
        {
        case FR_VECT_COMPRESS_RAW:
            retval = ::FrameCPP::FrVect::RAW;
            break;
        case FR_VECT_COMPRESS_GZIP:
            retval = ::FrameCPP::FrVect::GZIP;
            break;
        case FR_VECT_COMPRESS_DIFF_GZIP:
            retval = ::FrameCPP::FrVect::DIFF_GZIP;
            break;
        case FR_VECT_COMPRESS_ZERO_SUPPRESS:
            retval = ::FrameCPP::FrVect::ZERO_SUPPRESS;
            break;
        default:
            throw std::runtime_error( "Unknown compression mode" );
            break;
        }
        return retval;
    }

    inline fr_vect_compression_schemes_t
    FrChannel::CompressionScheme( ::FrameCPP::FrVect::compress_type Scheme )
    {
        fr_vect_compression_schemes_t retval = FR_VECT_COMPRESS_UNKNOWN;

        switch ( Scheme & 0xFF )
        {
        case ( ::FrameCPP::FrVect::RAW & 0xFF ):
            retval = FR_VECT_COMPRESS_RAW;
            break;
        case ( ::FrameCPP::FrVect::GZIP & 0xFF ):
            retval = FR_VECT_COMPRESS_GZIP;
            break;
        case ( ::FrameCPP::FrVect::DIFF_GZIP & 0xFF ):
            retval = FR_VECT_COMPRESS_DIFF_GZIP;
            break;
        case ( ::FrameCPP::FrVect::ZERO_SUPPRESS & 0xFF ):
            retval = FR_VECT_COMPRESS_ZERO_SUPPRESS;
            break;
        default:
            break;
        }

        if ( Scheme & 0x100 )
        {
            retval = ( fr_vect_compression_schemes_t )(
                retval | FR_VECT_COMPRESS_LITTLEENDIAN );
        }

        return retval;
    }

    inline void
    FrChannel::Read( ::fr_file_t*           Stream,
                     const ::fr_chan_name_t Name,
                     ::fr_chan_offset_t     Offset )
    {
        using FrameCPP::IFrameStream;

        IFrameStream::frame_offset_type offset(
            (IFrameStream::frame_offset_type)Offset );
        IFrameStream* fs( StreamAsInput( Stream ) );

        /*------------------------------------------------------------------*
          Try to see if it is an ADC channel
         *------------------------------------------------------------------*/
        try
        {
            m_channel.adc = fs->ReadFrAdcData( offset, Name );
            m_channel.type = FR_ADC_CHAN_TYPE;

            return;
        }
        catch ( ... )
        {
        }
        /*------------------------------------------------------------------*
          Try to see if it is an Proc channel
         *------------------------------------------------------------------*/
        try
        {
            m_channel.proc = fs->ReadFrProcData( offset, Name );
            m_channel.type = FR_PROC_CHAN_TYPE;

            return;
        }
        catch ( ... )
        {
        }
        /*------------------------------------------------------------------*
          Try to see if it is an event channel
          *------------------------------------------------------------------*/
        try
        {
            m_channel.event = fs->ReadFrEvent( offset, Name );
            m_channel.type = FR_EVENT_CHAN_TYPE;

            return;
        }
        catch ( ... )
        {
        }
        /*------------------------------------------------------------------*
          Try to see if it is an Sim channel
          *------------------------------------------------------------------*/
        try
        {
            m_channel.sim = fs->ReadFrSimData( offset, Name );
            m_channel.type = FR_SIM_CHAN_TYPE;

            return;
        }
        catch ( ... )
        {
        }
        /*------------------------------------------------------------------*
          Try to see if it is an SimEvent channel
          *------------------------------------------------------------------*/
        try
        {
            m_channel.sim_event = fs->ReadFrSimEvent( offset, Name );
            m_channel.type = FR_SIM_EVENT_CHAN_TYPE;

            return;
        }
        catch ( ... )
        {
        }
        std::ostringstream msg;

        msg << "Failed to read channel " << Name << " at offset " << Offset
            << " from the stream as the read operator is unimplemented.";
        throw std::runtime_error( msg.str( ) );
    }

    inline enum fr_chan_type
    FrChannel::Type( ) const
    {
        return m_channel.type;
    }
} // namespace FrameC

#endif /* FRAME_CPP_INTERFACE__FR_CHAN_INTERNAL_HH */
