//
// 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
//

#include <framecpp_config.h>

#include <algorithm>

#include <boost/shared_ptr.hpp>

#include "framecpp/Common/IOStream.hh"
#include "framecpp/Common/Description.hh"
#include "framecpp/Common/SearchContainer.hh"
#include "framecpp/Common/FrameSpec.tcc"

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

#include "framecpp/Universal/FrAdcData.hh"
#include "framecpp/Universal/FrSE.hh"
#include "framecpp/Universal/FrSH.hh"
#include "framecpp/Universal/PTR_STRUCT.hh"


#if HAVE_TEMPLATE_MOVE
#define MOVE_RET( a ) ( std::move( a ) )
#else /* HAVE_TEMPLATE_MOVE */
#define MOVE_RET( a ) ( a )
#endif /* HAVE_TEMPLATE_MOVE */

using FrameCPP::Common::Description;
using FrameCPP::Common::FrameSpec;

using FrameCPP::Universal::FrVect;

using std::ceil;
using std::floor;

//-----------------------------------------------------------------------
// Local functions and variables
//-----------------------------------------------------------------------

FR_OBJECT_META_DATA_DEFINE_UNIVERSAL( FrAdcDataImpl,
                                      FSI_FR_ADC_DATA,
                                      "FrAdcData",
                                      "ADC Data Structure" )

namespace FrameCPP
{
    namespace Universal
    {
        //-----------------------------------------------------------------------
        // FrAdcDataImpl namespace implementations
        //-----------------------------------------------------------------------
        namespace FrAdcDataImpl
        {
            const Data::units_type&
            Data::DEFAULT_UNITS( )
            {
                static units_type retval;
                return ( retval );
            }
        } // namespace FrAdcDataImpl

        //-----------------------------------------------------------------------
        // FrAdcData class implementation
        //-----------------------------------------------------------------------

        FrAdcData::FrAdcData( )
        {
        }

        FrAdcData::FrAdcData( const name_type&   Name,
                              channelGroup_type  ChannelGroup,
                              channelNumber_type ChannelNumber,
                              nBits_type         NBits,
                              sampleRate_type    SampleRate,
                              bias_type          Bias,
                              slope_type         Slope,
                              const units_type&  Units,
                              fShift_type        FShift,
                              timeOffset_type    TimeOffset,
                              dataValid_type     DataValid,
                              phase_type         Phase )
        {
            Data::name = Name;
            Data::channelGroup = ChannelGroup;
            Data::channelNumber = ChannelNumber;
            Data::nBits = NBits;
            Data::sampleRate = SampleRate;
            Data::bias = Bias;
            Data::slope = Slope;
            Data::units = Units;
            Data::fShift = FShift;
            Data::timeOffset = TimeOffset;
            Data::dataValid = DataValid;
            Data::phase = Phase;
        }

        const FrAdcData::name_type&
        FrAdcData::GetNameSlow( ) const
        {
            return Data::GetName( );
        }

        FrAdcData&
        FrAdcData::Merge( const FrAdcData& RHS )
        {
            /// \todo
            ///   Need to implement Merge routine
            std::string msg( "Merge currently not implemented for " );
            msg += StructName( );

            throw std::domain_error( msg );
            return *this;
        }

        bool
        FrAdcData::operator==( const Common::FrameSpec::Object& Obj ) const
        {
            return FrameCPP::compare( *this, Obj );
        }

        FrAdcData::subset_ret_type
        FrAdcData::Subset( REAL_8 Offset, REAL_8 Dt ) const
        {
            //---------------------------------------------------------------------
            // Sanity checks
            //---------------------------------------------------------------------
            // Verify the existance of only a single FrVect component
            //---------------------------------------------------------------------
            if ( data.size( ) != 1 )
            {
                std::ostringstream msg;

                msg << "METHOD: frameAPI::FrAdcData::SubFrAdcData: "
                    << "data vectors of length " << data.size( )
                    << " currently are not supported";
                throw std::logic_error( msg.str( ) );
            }
            INT_4U num_elements = data[ 0 ]->GetDim( 0 ).GetNx( );
            //---------------------------------------------------------------------
            /// \todo
            ///   Ensure the dimension of the FrVect is 1
            //---------------------------------------------------------------------
            //---------------------------------------------------------------------
            /// \todo
            ///   Ensure the requested range is valid
            //---------------------------------------------------------------------
            if ( Offset >
                 ( GetTimeOffset( ) + ( num_elements / GetSampleRate( ) ) ) )
            {
                std::ostringstream msg;
                msg << "METHOD: frameAPI::FrAdcData::SubFrAdcData: "
                    << "The value for the parameter Offset (" << Offset
                    << ") is out of range ([0-"
                    << ( GetTimeOffset( ) +
                         ( num_elements / GetSampleRate( ) ) )
                    << "))";
                throw std::range_error( msg.str( ) );
            }
            //---------------------------------------------------------------------
            // Start moving of data
            //---------------------------------------------------------------------
            subset_ret_type retval( new FrAdcData );

            // Copy in the header information
            retval->name = name;
            retval->comment = comment;
            retval->channelGroup = channelGroup;
            retval->channelNumber = channelNumber;
            retval->nBits = nBits;
            retval->sampleRate = sampleRate;
            retval->bias = bias;
            retval->slope = slope;
            retval->units = units;
            retval->fShift = fShift;
            retval->timeOffset = timeOffset;
            retval->dataValid = dataValid;
            retval->phase = phase;

            /// \todo
            ///   Get the slice of FrVect
            REAL_8 startSampleReal = ( Offset * retval->GetSampleRate( ) ) -
                ( retval->GetTimeOffset( ) * retval->GetSampleRate( ) );

            INT_4U endSample = INT_4U(
                ceil( startSampleReal + ( retval->GetSampleRate( ) * Dt ) ) );
            INT_4U startSample = ( startSampleReal > REAL_8( 0 ) )
                ? INT_4U( floor( startSampleReal ) )
                : INT_4U( 0 );

            {
                boost::shared_ptr< FrVect > subvect(
                    this->data[ 0 ]
                        ->SubFrVect( startSample, endSample )
                        .release( ) );

                retval->data.append( subvect );
            }

            //---------------------------------------------------------------------
            // Shift the offset
            //---------------------------------------------------------------------
            if ( startSampleReal > 0 )
            {
                // There is no gap
                retval->SetTimeOffset( 0 );
            }
            else
            {
                retval->incrementTimeOffset( Offset );
            }
            //---------------------------------------------------------------------
            // Return the new FrAdcData
            //---------------------------------------------------------------------
#if __clang__ &&                                                               \
    ( ( __clang_major__ > 7 ) ||                                               \
      ( ( __clang_major__ == 7 ) && ( __clang_minor__ >= 3 ) ) )
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpessimizing-move"
#endif /* __clang__ */
            return MOVE_RET( retval );
#if __clang__ &&                                                               \
    ( ( __clang_major__ > 7 ) ||                                               \
      ( ( __clang_major__ == 7 ) && ( __clang_minor__ >= 3 ) ) )
#pragma clang diagnostic pop
#endif /* __clang__ */
        }

    } // namespace Universal
} // namespace FrameCPP

//=======================================================================
//               C L A S S I C   I O
//=======================================================================
namespace FrameCPP
{
    namespace Universal
    {
        namespace FrAdcDataImpl
        {

            template <>
            const Common::Description*
            ClassicIO< FrAdcData >::GetDescription( unsigned int frame_stream_version ) const
            {
                using Common::Description;
                using Universal::PTR_STRUCT;

                Description* description{ nullptr };


                if ( frame_stream_version >= 8 )
                {
                    static Description v8_description;

                    if ( v8_description.size( ) == 0 )
                    {
                        //-----------------------------------------------
                        v8_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v8_description( FrSE(
                            "name",
                            Common::Description::Ascii< name_type >( ),
                            "Channel name -- must be unique with the frame" ) );
                        v8_description(
                            FrSE( "comment",
                                  Common::Description::Ascii< comment_type >( ),
                                  "Comment" ) );
                        v8_description( FrSE(
                            "channelGroup",
                            Common::Description::Ascii< channelGroup_type >( ),
                            "Channel grouping number containing ADC" ) );
                        v8_description( FrSE(
                            "channelNumber",
                            Common::Description::Ascii< channelNumber_type >( ),
                            "Channel number" ) );
                        v8_description(
                            FrSE( "nBits",
                                  Common::Description::Ascii< nBits_type >( ),
                                  "Number of bits in A/D output" ) );
                        v8_description( FrSE(
                            "bias",
                            Common::Description::Ascii< bias_type >( ),
                            "DC bias on channel (Units @ ADC counts = 0)" ) );
                        v8_description(
                            FrSE( "slope",
                                  Common::Description::Ascii< slope_type >( ),
                                  "ADC calibration: input units/ct" ) );
                        v8_description(
                            FrSE( "units",
                                  Common::Description::Ascii< units_type >( ),
                                  "ADC calibration: input units for slope. If "
                                  "dimensionless, then units == <NONE>, in "
                                  "CAPITALS (without<...>)." ) );
                        v8_description( FrSE(
                            "sampleRate",
                            Common::Description::Ascii< sampleRate_type >( ),
                            "Data Acquisition rate, samples/s" ) );
                        v8_description( FrSE(
                            "timeOffset",
                            Common::Description::Ascii< timeOffset_type >( ),
                            "Offset of 1st sample relative to the frame start "
                            "time (seconds). Must be positive and smaller than "
                            "the frame length" ) );
                        v8_description(
                            FrSE( "fShift",
                                  Common::Description::Ascii< fShift_type >( ),
                                  "fShift is the frequency (in Hz) in the "
                                  "original data that corresponds to 0 Hz in "
                                  "the heterodyned series" ) );
                        v8_description(
                            FrSE( "phase",
                                  Common::Description::Ascii< phase_type >( ),
                                  "Phase (in radiant) of heterodyning signal "
                                  "at start of dataset" ) );
                        v8_description( FrSE(
                            "dataValid",
                            Common::Description::Ascii< dataValid_type >( ),
                            "Data valid flag: dataValid = 0 -> ADC data valid; "
                            "dataValid != 0 -> ADC data suspect/not valid" ) );
                        //-----------------------------------------------
                        v8_description(
                            FrSE( "data",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector of sampled data." ) );
                        v8_description(
                            FrSE( "aux",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector for user-provided "
                                  "information; use is generic" ) );
                        //-----------------------------------------------
                        v8_description(
                            FrSE( "next",
                                  PTR_STRUCT::Desc( Data::StructName( ) ),
                                  "Identifier for next ADC structure in the "
                                  "linked list." ) );
                        //-----------------------------------------------
                        v8_description( FrSE( "chkSum",
                                              CheckSumDataClass( ),
                                              CheckSumDataComment( ) ) );
                    }

                    description = &v8_description;
                }
                else if ( frame_stream_version >= 5 )
                {
                    static Description v5_description;

                    if ( v5_description.size( ) == 0 )
                    {
                        v5_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v5_description( FrSE(
                            "name",
                            Common::Description::Ascii< io_name_type >( ),
                            "Channel name -- must be unique with the frame" ) );
                        v5_description( FrSE(
                            "comment",
                            Common::Description::Ascii< io_comment_type >( ),
                            "Comment" ) );
                        v5_description(
                            FrSE( "channelGroup",
                                  Common::Description::Ascii<
                                      v5::channelGroup_type >( ),
                                  "Channel grouping number containing ADC" ) );
                        v5_description( FrSE( "channelNumber",
                                              Common::Description::Ascii<
                                                  v5::channelNumber_type >( ),
                                              "Channel number" ) );
                        v5_description( FrSE(
                            "nBits",
                            Common::Description::Ascii< v5::nBits_type >( ),
                            "Number of bits in A/D output" ) );
                        v5_description( FrSE(
                            "bias",
                            Common::Description::Ascii< v5::bias_type >( ),
                            "DC bias on channl (Units @ ADC_counts = 0)" ) );
                        v5_description( FrSE(
                            "slope",
                            Common::Description::Ascii< v5::slope_type >( ),
                            "ADC calibration: input units/ct" ) );
                        v5_description( FrSE(
                            "units",
                            Common::Description::Ascii< io_units_type >( ),
                            "ADC calibration: input units for slope." ) );
                        v5_description(
                            FrSE( "sampleRate",
                                  Common::Description::Ascii<
                                      v5::sampleRate_type >( ),
                                  "Data acquisition rate, samples/s." ) );
                        v5_description(
                            FrSE( "timeOffset",
                                  Common::Description::Ascii<
                                      v5::timeOffset_type >( ),
                                  "Offset of 1st sample relative to the frame "
                                  "start time (seconds)" ) );
                        v5_description( FrSE(
                            "fShift",
                            Common::Description::Ascii< v5::fShift_type >( ),
                            "fShift is the frequency (in Hz) in teh original "
                            "data that corresponds to 0 Hz in the hetrodyned "
                            "series." ) );
                        v5_description( FrSE(
                            "phase",
                            Common::Description::Ascii< v5::phase_type >( ),
                            "Phase (in radian) of heterodyning signal at start "
                            "of dataset" ) );
                        v5_description( FrSE(
                            "dataValid",
                            Common::Description::Ascii< v5::dataValid_type >( ),
                            "Data valid flag: dataValid = 0 -> ADC data valid; "
                            "data Valid != 0 ADC data suspect/not valid" ) );
                        v5_description(
                            FrSE( "data",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector of sampled data." ) );
                        v5_description(
                            FrSE( "aux",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector for user-provided "
                                  "information; use is generic." ) );

                        v5_description(
                            FrSE( "next",
                                  PTR_STRUCT::Desc( FrAdcData::StructName( ) ),
                                  "Identifier for next ADC structure in the "
                                  "linked list." ) );
                    }
                    description = &v5_description;
                }
                else if ( frame_stream_version >= 4 )
                {
                    static Description v4_description;

                    if ( v4_description.size( ) == 0 )
                    {
                        v4_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v4_description( FrSE(
                            "name",
                            Common::Description::Ascii< io_name_type >( ),
                            "Channel name -- must be unique with the frame" ) );
                        v4_description( FrSE(
                            "comment",
                            Common::Description::Ascii< io_comment_type >( ),
                            "Comment" ) );
                        v4_description(
                            FrSE( "channelGroup",
                                  Common::Description::Ascii<
                                      v4::channelGroup_type >( ),
                                  "Channel grouping number containing ADC" ) );
                        v4_description( FrSE( "channelNumber",
                                              Common::Description::Ascii<
                                                  v4::channelNumber_type >( ),
                                              "Channel number" ) );
                        v4_description( FrSE(
                            "nBits",
                            Common::Description::Ascii< v4::nBits_type >( ),
                            "Number of bits inn A/D output" ) );
                        v4_description( FrSE(
                            "bias",
                            Common::Description::Ascii< v4::bias_type >( ),
                            "DC bias on channel (Units @ ADC_counts = 0)" ) );
                        v4_description( FrSE(
                            "slope",
                            Common::Description::Ascii< v4::slope_type >( ),
                            "ADC calibrarion: input units/ct" ) );
                        v4_description( FrSE(
                            "units",
                            Common::Description::Ascii< io_units_type >( ),
                            "ADC calibration: input units for slope." ) );
                        v4_description(
                            FrSE( "sampleRate",
                                  Common::Description::Ascii<
                                      v4::sampleRate_type >( ),
                                  "Data acquisition rate, samples/s." ) );
                        v4_description(
                            FrSE( "timeOffsetS",
                                  Common::Description::Ascii<
                                      v4::timeOffsetS_type >( ),
                                  "Offset of 1st sample relative to the "
                                  "integer seconds frame start time" ) );
                        v4_description( FrSE(
                            "timeOffsetN",
                            Common::Description::Ascii<
                                v4::timeOffsetN_type >( ),
                            "Offset of 1st sample relative to timeOffsetS in "
                            "integer residual nanoseconds" ) );
                        v4_description( FrSE(
                            "fShift",
                            Common::Description::Ascii< v4::fShift_type >( ),
                            "Frequency shift if signal has been heterodyned "
                            "before ADC" ) );
                        v4_description( FrSE(
                            "dataValid",
                            Common::Description::Ascii< v4::dataValid_type >( ),
                            "Data valid flag: dataValid = 0 -> ADC data valid; "
                            "dataValid!=0 -> data suspect/not valid" ) );
                        v4_description(
                            FrSE( "data",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector of sampled data." ) );
                        v4_description(
                            FrSE( "aux",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector for user-provided "
                                  "information; use is generic." ) );

                        v4_description(
                            FrSE( "next",
                                  PTR_STRUCT::Desc( FrAdcData::StructName( ) ),
                                  "Identifier for next ADC structure in the "
                                  "linked list." ) );
                    }
                    description = &v4_description;
                }
                else if ( frame_stream_version >= 3 )
                {
                    static Description v3_description;

                    if ( v3_description.size( ) == 0 )
                    {
                        v3_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v3_description(
                            FrSE( "name",
                                  Common::Description::Ascii< io_name_type >( ),
                                  "Channel name" ) );
                        v3_description( FrSE(
                            "comment",
                            Common::Description::Ascii< io_comment_type >( ),
                            "Comment" ) );
                        v3_description( FrSE(
                            "crate",
                            Common::Description::Ascii< v3::crate_type >( ),
                            "Crate number containing ADC" ) );
                        v3_description( FrSE(
                            "channel",
                            Common::Description::Ascii< v3::channel_type >( ),
                            "Channel number" ) );
                        v3_description( FrSE(
                            "nBits",
                            Common::Description::Ascii< v3::nBits_type >( ),
                            "Number of bits in A/D output" ) );
                        v3_description( FrSE(
                            "bias",
                            Common::Description::Ascii< v3::bias_type >( ),
                            "DC bias on channel (Volts @ ADC_counts = 0)" ) );
                        v3_description( FrSE(
                            "slope",
                            Common::Description::Ascii< v3::slope_type >( ),
                            "ADC calibration: input units/count" ) );
                        v3_description( FrSE(
                            "units",
                            Common::Description::Ascii< io_units_type >( ),
                            "ADC calibration: input units for slope" ) );
                        v3_description(
                            FrSE( "sampleRate",
                                  Common::Description::Ascii<
                                      v3::sampleRate_type >( ),
                                  "Data acquisition rate, samples/s" ) );
                        v3_description(
                            FrSE( "timeOffsetS",
                                  Common::Description::Ascii<
                                      v3::timeOffsetS_type >( ),
                                  "For triggered data lasting less than one "
                                  "frame, integer seconds start time relative "
                                  "to frame start" ) );
                        v3_description(
                            FrSE( "timeOffsetN",
                                  Common::Description::Ascii<
                                      v3::timeOffsetN_type >( ),
                                  "For triggered data lasting less than one "
                                  "frame, integer residual nanoseconds start "
                                  "time relative to frame start" ) );
                        v3_description( FrSE(
                            "fShift",
                            Common::Description::Ascii< v3::fShift_type >( ),
                            "Frequency shift if signal has been heterodyned "
                            "before ADC" ) );
                        v3_description( FrSE(
                            "overRange",
                            Common::Description::Ascii< v3::overRange_type >( ),
                            "Data valid flag: overRange = 0 -> ADC data valid; "
                            "overRange!=0 -> ADC data suspect/not valid" ) );
                        v3_description(
                            FrSE( "data",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector of sampled data." ) );
                        v3_description(
                            FrSE( "aux",
                                  PTR_STRUCT::Desc( "FrVect" ),
                                  "Identifier for vector for user-provided "
                                  "information; use is generic." ) );

                        v3_description(
                            FrSE( "next",
                                  PTR_STRUCT::Desc( FrAdcData::StructName( ) ),
                                  "Identifier for next ADC structure in the "
                                  "linked list." ) );
                    }
                    description = &v3_description;
                }
                return ( description );
            }

            template <>
            const ClassicIO< FrAdcData >::name_type&
            ClassicIO< FrAdcData >::GetNameSlow( ) const
            {
                return GetName( );
            }

            template <>
            void
            ClassicIO< FrAdcData >::
#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
