//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2019 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_UNIVERSAL__IMPL__FrAdcDataClassicIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrAdcDataClassicIO_HH

#include "framecpp/Universal/impl/FrAdcDataData.hh"

#if defined( __cplusplus )
#if !defined( SWIG )

#include <cmath>

#include <chrono>

#include "framecpp/Universal/impl/GPSTime.hh"

#include "framecpp/Universal/impl/FrAdcDataData.hh"

#include "framecpp/Common/Description.hh"
#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/FrameSpec.tcc"
#include "framecpp/Common/TOCInfo.hh"

#include "framecpp/Universal/impl/FrameSpec.hh"

namespace FrameCPP
{
    namespace Universal
    {
        class FrAdcData;

        namespace FrAdcDataImpl
        {
            template < typename T >
            class ClassicIO
                : public virtual Data,
                  public Common::TOCInfo,
                  public ObjectWithChecksum< Common::FrameSpec::Object >
            {
            public:
                // Bring base class GetDescription methods into scope
                using FrameCPP::Common::FrameSpec::Object::GetDescription;

                //-------------------------------------------------------
                //
                //-------------------------------------------------------
                ClassicIO( )
                    : ObjectWithChecksum< Common::FrameSpec::Object >(
                          FrameSpecId( ), nullptr )
                {
                }

                ClassicIO( const ClassicIO& ) = default;
                ClassicIO( ClassicIO&& ) = default;
                ClassicIO& operator=( const ClassicIO& ) = delete;
                ClassicIO& operator=( ClassicIO&& ) = delete;

                /**
                 * \brief The name structure name of this object.
                 *
                 * \return
                 *     The name of the structure as specified by the frame
                 *     specification.
                 */
                virtual const char*
                ObjectStructName( ) const
                {
                    return StructName( );
                }

                //-----------------------------------------------------------------
                /// @brief The data structure description
                ///
                /// @return
                ///     The description of the data structure used to represent
                ///     this object when written to a GWF stream
                //-----------------------------------------------------------------
                virtual const Common::Description*
                GetDescription( unsigned int version ) const;

                /**
                 * \brief Return the name associate with the FrAdcData
                 * structure.
                 *
                 * \return
                 *     The name associated with the FrAdcData structure
                 */
                const name_type& GetNameSlow( ) const;

            protected:
                typedef STRING io_name_type;
                typedef STRING io_comment_type;
                typedef STRING io_units_type;
                /**
                 * \brief Number of bytes needed to write this structure
                 *
                 * \param[in] Stream
                 *     The stream from which to the object is being read or
                 *     written.
                 *
                 * \return
                 *     The number of bytes need to read or write this object.
                 */
                cmn_streamsize_type
                pBytes( const Common::StreamBase& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );

                    return (
                        io_name_type::Bytes( name ) // name
                        + io_comment_type::Bytes( comment ) // comment
                        +
                        ( ( frame_stream_version >= 4 )
                              ? sizeof( channelGroup_type )
                              : sizeof( v3::crate_type ) ) // channelGroup/crate
                        +
                        ( ( frame_stream_version >= 4 )
                              ? sizeof( channelNumber_type )
                              : sizeof(
                                    v3::channel_type ) ) // channelNumber/channel
                        + sizeof( nBits_type ) // nBits
                        + sizeof( bias_type ) // bias
                        + sizeof( slope_type ) // slope
                        + io_units_type::Bytes( units ) // units
                        + sizeof( sampleRate_type ) // sampleRate
                        + ( ( frame_stream_version >= 5 )
                                ? sizeof( timeOffset_type )
                                : ( ( ( frame_stream_version >= 4 )
                                          ? sizeof( v4::timeOffsetS_type )
                                          : sizeof( v3::timeOffsetS_type ) ) //
                                    + sizeof( v3::timeOffsetN_type ) ) ) //
                        + sizeof( fShift_type ) //
                        + ( ( frame_stream_version >= 5 ) // phase
                                ? sizeof( phase_type )
                                : 0 ) //
                        + ( ( frame_stream_version >= 4 )
                                ? sizeof( dataValid_type )
                                : sizeof( v3::overRange_type ) ) +
                        Stream.PtrStructBytes( ) // data
                        + Stream.PtrStructBytes( ) // aux
                        + Stream.PtrStructBytes( ) // next
                    );
                }

                /**
                 * \brief Virtual constructor
                 *
                 * \param[in] Stream
                 *     The input stream from where the object is being read.
                 *
                 * \return
                 *     The newly allocated object.
                 */
                virtual self_type*
                pCreate( istream_type& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );

                    T* obj( new T( ) );

                    io_name_type    io_name;
                    io_comment_type io_comment;
                    io_units_type   io_units;

                    Stream >> io_name //
                        >> io_comment //
                        >> obj->channelGroup //
                        >> obj->channelNumber //
                        >> obj->nBits //
                        >> obj->bias //
                        >> obj->slope //
                        >> io_units //
                        >> obj->sampleRate //
                        ;
                    if ( frame_stream_version >= 5 )
                    {
                        Stream >> obj->timeOffset // timeOffset
                            ;
                    }
                    else if ( frame_stream_version >= 3 )
                    {
                        using namespace std::chrono;

                        INT_4U seconds;
                        // Convert seconds and nanoseconds to duration<double>
                        duration< double > total_time;

                        if ( frame_stream_version >= 4 )
                        {
                            v4::timeOffsetS_type timeOffsetS;

                            Stream >> timeOffsetS;
                            seconds = timeOffsetS;
                        }
                        else if ( frame_stream_version >= 3 )
                        {
                            v3::timeOffsetS_type timeOffsetS;

                            Stream >> timeOffsetS;
                            seconds = timeOffsetS;
                        }
                        v3::timeOffsetN_type timeOffsetN;

                        Stream >> timeOffsetN;

                        total_time = duration< double >( seconds );
                        total_time += duration< double >( timeOffsetN * 1ns );

                        obj->timeOffset = total_time.count( );
                    }

                    Stream >> obj->fShift //
                        ;
                    if ( frame_stream_version >= 5 )
                    {
                        Stream >> obj->phase //
                            ;
                    }
                    Stream >> obj->dataValid;

                    Stream >> obj->data;

                    Stream >> obj->aux;

                    Stream.Next( obj );

                    obj->name = io_name;
                    obj->comment = io_comment;
                    obj->units = io_units;

                    return ( obj );
                }

                /**
                 * \brief Write the structure to the stream
                 *
                 * \param[in] Stream
                 *     The output stream where the object is to be written.
                 */
                virtual void
                pWrite( ostream_type& Stream ) const
                {
                    try
                    {
                        auto frame_stream_version = StreamVersion( Stream );

                        Stream << io_name_type( name )
                               << io_comment_type( comment );
                        if ( frame_stream_version >= 4 )
                        {
                            Stream << channelGroup << channelNumber;
                        }
                        else
                        {
                            Stream << v3::crate_type( channelGroup )
                                   << v3::channel_type( channelNumber );
                        }
                        Stream << nBits << bias << slope
                               << io_units_type( units ) << sampleRate;
                        if ( frame_stream_version >= 5 )
                        {
                            Stream << timeOffset;
                        }
                        else
                        {
                            if ( frame_stream_version > 4 )
                            {
                                INT_4U tempS, tempN;
                                ConvertToSecAndNsec( timeOffset, tempS, tempN );
                                v4::timeOffsetS_type timeOffsetS{
                                    static_cast< v4::timeOffsetS_type >( tempS )
                                };
                                v4::timeOffsetN_type timeOffsetN{
                                    static_cast< v4::timeOffsetN_type >( tempN )
                                };
                                Stream << timeOffsetS << timeOffsetN;
                            }
                            else
                            {
                                INT_4U tempS, tempN;
                                ConvertToSecAndNsec( timeOffset, tempS, tempN );
                                timeOffsetS_type timeOffsetS{
                                    static_cast< v4::timeOffsetS_type >( tempS )
                                };
                                v4::timeOffsetN_type timeOffsetN{
                                    static_cast< v4::timeOffsetN_type >( tempN )
                                };
                                Stream << timeOffsetS << timeOffsetN;
                            }
                        }
                        Stream << fShift;
                        if ( frame_stream_version >= 5 )
                        {
                            Stream << phase;
                        }
                        Stream << dataValid;
                        Stream << data << aux;
                        WriteNext( Stream );
                    }
                    catch ( const std::exception& Exception )
                    {
                        std::cerr
                            << "DEBUG: Error Writing FrAdcData: " << GetName( )
                            << " exception: " << Exception.what( ) << std::endl;
                        throw; // rethrow
                    }
                }

#if WORKING_VIRTUAL_TOCQUERY
            public:
                //-----------------------------------------------------------------
                /// \brief Answer requests for information as needed by TOC
                ///
                /// \param[in] InfoClass
                ///     Class of information being requested.
                //-----------------------------------------------------------------
                void TOCQuery( int InfoClass, ... ) const;
#else /* WORKING_VIRTUAL_TOCQUERY */
            protected:
                //-----------------------------------------------------------------
                /// \brief Answer requests for information as needed by TOC
                ///
                /// \param[in] InfoClass
                ///     Class of information being requested.
                /// \param[in] vl
                ///     Variable argument list of requested data.
                //-----------------------------------------------------------------
                virtual void vTOCQuery( int InfoClass, va_list vl ) const;
#endif /* WORKING_VIRTUAL_TOCQUERY */
#if 0
                // using Data::Data;

                /**
                 * \brief Constructor
                 */
                ClassicIO( const name_type&   Name,
                           channelGroup_type  Group,
                           channelNumber_type Channel,
                           nBits_type         NBits,
                           sampleRate_type    SampleRate,
                           bias_type          Bias = DEFAULT_BIAS,
                           slope_type         Slope = DEFAULT_SLOPE,
                           const units_type&  Units = DEFAULT_UNITS( ),
                           fShift_type        FShift = DEFAULT_FSHIFT,
                           timeOffset_type    TimeOffset = DEFAULT_TIME_OFFSET,
                           dataValid_type     DataValid = DEFAULT_DATA_VALID,
                           phase_type         Phase = DEFAULT_PHASE )
                    : Data( Name,
                            Group,
                            Channel,
                            NBits,
                            SampleRate,
                            Bias,
                            Slope,
                            Units,
                            FShift,
                            TimeOffset,
                            DataValid,
                            Phase ),
                      ObjectWithChecksum< Common::FrameSpec::Object >(
                          FrameSpecId( ), StructDescription( ) )
                {
                }

                virtual ~ClassicIO( )
               { 
                }

            protected:
#endif /* 0 */

            }; // namespace FrAdcDataImpl

            template < typename T >
            void
            ClassicIO< T >::
#if WORKING_VIRTUAL_TOCQUERY
                TOCQuery( int InfoClass, ... ) const
#else /*  WORKING_VIRTUAL_TOCQUERY */
                vTOCQuery( int InfoClass, va_list vl ) const
#endif /*  WORKING_VIRTUAL_TOCQUERY */
            {
                using Common::TOCInfo;

#if WORKING_VIRTUAL_TOCQUERY
                va_list vl;
                va_start( vl, InfoClass );
#endif /*  WORKING_VIRTUAL_TOCQUERY */

                while ( InfoClass != TOCInfo::IC_EOQ )
                {
                    int data_type = va_arg( vl, int );
                    switch ( data_type )
                    {
                    case TOCInfo::DT_STRING_2:
                    {
                        STRING* data = va_arg( vl, STRING* );
                        switch ( InfoClass )
                        {
                        case TOCInfo::IC_NAME:
                            *data = GetName( );
                            break;
                        default:
                            goto cleanup;
                            break;
                        }
                    }
                    break;
                    case TOCInfo::DT_INT_2U:
                    {
                        INT_2U* data = va_arg( vl, INT_2U* );
                        switch ( InfoClass )
                        {
                        case TOCInfo::IC_DATA_VALID:
                            *data = GetDataValid( );
                            break;
                        default:
                            goto cleanup;
                            break;
                        }
                    }
                    break;
                    case TOCInfo::DT_INT_4U:
                    {
                        INT_4U* data = va_arg( vl, INT_4U* );
                        switch ( InfoClass )
                        {
                        case TOCInfo::IC_CHANNEL_ID:
                            *data = GetChannelNumber( );
                            break;
                        case TOCInfo::IC_GROUP_ID:
                            *data = GetChannelGroup( );
                            break;
                        default:
                            goto cleanup;
                            break;
                        }
                    }
                    break;
                    default:
                        // Stop processing
                        goto cleanup;
                    }
                    InfoClass = va_arg( vl, int );
                }
            cleanup:
#if WORKING_VIRTUAL_TOCQUERY
                va_end( vl )
#endif /*  WORKING_VIRTUAL_TOCQUERY */
                    ;
            }
        } // namespace FrAdcDataImpl
    } // namespace Universal
} // namespace FrameCPP

#endif /* ! defined(SWIG) */
#endif /* defined( __cplusplus ) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrAdcDataClassicIO_HH */
