/* -*- mode: C++ -*- */
//
// 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__TEST__FR_SAMPLE_6_TCC
#define FRAME_CPP__TEST__FR_SAMPLE_6_TCC

#include <boost/shared_array.hpp>

#include "framecpp/Common/FrameSpec.hh"

#include "framecpp/Universal/FrameH.hh"

#include "framecpp/Universal/FrAdcData.hh"
#include "framecpp/Universal/FrHistory.hh"
#include "framecpp/Universal/FrMsg.hh"
#include "framecpp/Universal/FrProcData.hh"
#include "framecpp/Universal/FrRawData.hh"
#include "framecpp/Universal/FrSerData.hh"
#include "framecpp/Universal/FrSimData.hh"
#include "framecpp/Universal/FrSimEvent.hh"
#include "framecpp/Universal/FrStatData.hh"
#include "framecpp/Universal/FrSummary.hh"
#include "framecpp/Universal/FrTable.hh"
#include "framecpp/Universal/FrVect.hh"

using FrameCPP::Common::FrameSpec;

#define LM_AT( )

namespace Testing
{
    namespace Version_6
    {
        using FrameCPP::Universal::GPSTime;

        using FrameCPP::Universal::FrAdcData;
        using FrameCPP::Universal::FrameH;
        using FrameCPP::Universal::FrEvent;
        using FrameCPP::Universal::FrProcData;
        using FrameCPP::Universal::FrRawData;
        using FrameCPP::Universal::FrSerData;
        using FrameCPP::Universal::FrSimData;
        using FrameCPP::Universal::FrSimEvent;
        using FrameCPP::Universal::FrStatData;
        using FrameCPP::Universal::FrSummary;
        using FrameCPP::Universal::FrTable;
        using FrameCPP::Universal::FrVect;

        inline INT_4U
        ChannelNumber( )
        {
            static INT_4U cn( 0 );
            return ++cn;
        }

        template < class T >
        std::string
        data_type( )
        {
            return "";
        }

#define DATA_TYPE( A )                                                         \
    template <>                                                                \
    std::string data_type< A >( )                                              \
    {                                                                          \
        return #A;                                                             \
    }

        DATA_TYPE( CHAR_U )
        DATA_TYPE( CHAR )
        DATA_TYPE( INT_2U )
        DATA_TYPE( INT_2S )
        DATA_TYPE( INT_4U )
        DATA_TYPE( INT_4S )
        DATA_TYPE( INT_8U )
        DATA_TYPE( INT_8S )
        DATA_TYPE( REAL_4 )
        DATA_TYPE( REAL_8 )
        DATA_TYPE( COMPLEX_8 )
        DATA_TYPE( COMPLEX_16 )

#undef DATA_TYPE

        std::string
        method( const FrVect::compression_scheme_type Method )
        {
            switch ( Method )
            {
#define METHOD( A )                                                            \
    case FrVect::A:                                                            \
        return #A
                METHOD( RAW );
                METHOD( GZIP );
                METHOD( DIFF_GZIP );
                METHOD( ZERO_SUPPRESS );
                METHOD( ZERO_SUPPRESS_OTHERWISE_GZIP );

#undef METHOD

            default:
            {
                std::ostringstream msg;

                msg << "Unknown compression method: " << Method;
                throw std::runtime_error( msg.str( ).c_str( ) );
            }
            break;
            }
        }

        template < class T >
        void
        compress( FrameH&                         Frame,
                  FrVect::compression_scheme_type Method,
                  INT_2U                          Level,
                  const T                         Start,
                  const T                         End,
                  const T                         Inc,
                  INT_4U                          Samples )
        {
            using FrameCPP::Universal::FrAdcData;

            std::ostringstream adc_name;

            adc_name << "TesT_" << method( Method ) << "_" << Level << "_"
                     << data_type< T >( );

            boost::shared_array< T > data( new T[ Samples ] );
            FrVect::dimension_type   dim( Samples );

            data[ 0 ] = Start;
            for ( INT_4U x = 1; x < Samples; x++ )
            {
                data[ x ] = data[ x - 1 ] + Inc;
            }

            FrVect adc_data( adc_name.str( ), 1, &dim, data.get( ) );

            adc_data.Compress( Method, Level );

            // FrAdcData
            LM_AT( );
            FrAdcData adc( adc_name.str( ),
                           0, // Channel grouping
                           ChannelNumber( ), // Channel number
                           I4U, // nBits
                           Samples, // sampleRate
                           R8, // bias
                           R4, // slope
                           S, // units
                           0.0, // fShift
                           0.0, // timeOffset
                           0, // dataValid
                           0.0 // phase
            );
            adc.RefData( ).append( adc_data );
            LM_AT( );
            Frame.GetRawData( )->RefFirstAdc( ).append( adc );
        }

        template < class T >
        inline void
        compress( FrameH&                         Frame,
                  FrVect::compression_scheme_type Method,
                  INT_2U                          Level,
                  const T                         Start,
                  const T                         End,
                  const T                         Inc )
        {
            compress( Frame,
                      Method,
                      Level,
                      Start,
                      End,
                      Inc,
                      INT_4U( ( End - Start ) / Inc ) );
        }

        void
        compress( FrameH&                         Frame,
                  FrVect::compression_scheme_type Method,
                  INT_2U                          Level )
        {
            compress< CHAR_U >( Frame, Method, Level, 0, 255, 1, 256 );
            compress< CHAR >( Frame, Method, Level, -128, 127, 1, 256 );
            compress< INT_2U >( Frame, Method, Level, 0, 65535, 1, 65536 );
            compress< INT_2S >( Frame, Method, Level, -32768, 32767, 1, 65536 );
            compress< INT_4U >( Frame, Method, Level, 0, 65536, 1 );
            compress< INT_4S >( Frame, Method, Level, -32768, 32768, 1 );
            compress< INT_8U >( Frame, Method, Level, 0, 65536, 1 );
            compress< INT_8S >( Frame, Method, Level, -32768, 32768, 1 );
            compress< REAL_4 >( Frame, Method, Level, -32768.0, 32768.0, 1.0 );
            compress< REAL_8 >( Frame, Method, Level, -32768.0, 32768.0, 1.0 );
            compress< COMPLEX_8 >( Frame,
                                   Method,
                                   Level,
                                   COMPLEX_8( -32768.0, 0.0 ),
                                   COMPLEX_8( 32768.0, 0.0 ),
                                   COMPLEX_8( 1.0, 0.0 ),
                                   65636 );
            compress< COMPLEX_16 >( Frame,
                                    Method,
                                    Level,
                                    COMPLEX_8( -32768.0, 0.0 ),
                                    COMPLEX_8( 32768.0, 0.0 ),
                                    COMPLEX_8( 1.0, 0.0 ),
                                    65636 );
        }
    } // namespace Version_6
} // namespace Testing

template <>
make_frame_ret_type
makeFrame< 6 >( stat_data_container_type& StatData )
{
    using namespace Testing::Version_6;

    using FrameCPP::Universal::FrAdcData;
    using FrameCPP::Universal::FrameH;
    using FrameCPP::Universal::FrDetector;
    using FrameCPP::Universal::FrHistory;
    using FrameCPP::Universal::FrRawData;
    using FrameCPP::Universal::FrSerData;
    using FrameCPP::Universal::FrSummary;
    using FrameCPP::Universal::FrVect;

    boost::shared_ptr< FrameH > fr(
        new FrameH( S, I4S, I4U, GPSTime( I4U, I4U ), R8, I4U ) );

    // FrDetector (both detectors are the same)
    //
    FrDetector d( S, // name
                  "aa", // prefix
                  R8, // longitude
                  R8, // latitude
                  R4, // elevation
                  R4, // armXazimuth
                  R4, // armYazimuth
                  R4, // armXaltitude
                  R4, // armYaltitude
                  R4, // armXmidpoint
                  R4 ); // armYmidpoint

    LM_AT( );
    fr->RefDetectSim( ).append( d );
    LM_AT( );
    fr->RefDetectProc( ).append( d );

    // FrHistory (append same record twice)
    LM_AT( );
    FrHistory h1( S, I4U, S );
    LM_AT( );
    fr->RefHistory( ).append( h1 );

    LM_AT( );
    {
        // FrRawData
        FrameH::rawData_type rawData(
            new FrameH::rawData_type::element_type( S ) );
        LM_AT( );
        fr->SetRawData( rawData );
    }

    // FrAdcData
    LM_AT( );
    FrAdcData a( S, I4U, I4U, I4U, R8, R4, R4, S, R8, R8, I2U, R4 );
    LM_AT( );
    a.AppendComment( S );
    LM_AT( );
    fr->GetRawData( )->RefFirstAdc( ).append( a );

    // character vector
    {
        CHAR                   d[ 4 ] = { C, C, C, C };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // unsigned character vector
    {
        CHAR_U                 d[ 4 ] = { UC, UC, UC, UC };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // short vector
    {
        INT_2S                 d[ 4 ] = { I2S, I2S, I2S, I2S };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // unsigned short vector
    {
        INT_2U                 d[ 4 ] = { I2U, I2U, I2U, I2U };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // int vector
    {
        INT_4S                 d[ 4 ] = { I4S, I4S, I4S, I4S };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // unsigned int vector
    {
        INT_4U                 d[ 4 ] = { I4U, I4U, I4U, I4U };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // int vector
    {
        INT_8S                 d[ 4 ] = { I8S, I8S, I8S, I8S };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // unsigned int vector
    {
        INT_8U                 d[ 4 ] = { I8U, I8U, I8U, I8U };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // real vector
    {
        REAL_4                 d[ 4 ] = { R4, R4, R4, R4 };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    // real vector
    {
        REAL_8                 d[ 4 ] = { R8, R8, R8, R8 };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( S, I2U, ddims, d, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( dvect );
    }

    {
        COMPLEX_8 cdata[ 4 ];
        cdata[ 0 ] = C8;
        cdata[ 1 ] = C8;
        FrVect::dimension_type cdims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 cvect( S, I2U, cdims, cdata, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( cvect );
    }

    {
        COMPLEX_16 cdata[ 4 ];
        cdata[ 0 ] = C16;
        cdata[ 1 ] = C16;
        FrVect::dimension_type cdims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 cvect( S, 1, cdims, cdata, S );
        fr->GetRawData( )->RefFirstAdc( )[ 0 ]->RefData( ).append( cvect );
    }

    {
        using FrameCPP::Universal::FrMsg;

        // Log Message (a couple of them)
        LM_AT( );
        FrMsg m( "Mesg1", S, I4U, GPSTime( I4U, I4U ) );
        LM_AT( );
        fr->GetRawData( )->RefLogMsg( ).append( m );
    }

    // FrProcData
    LM_AT( );
    FrProcData p( S, // name
                  S, // comment
                  I2U, // type
                  I2U, // subType
                  R8, // timeOffset
                  R8, // tRange
                  R8, // fShift
                  R4, // phase
                  R8, // fRange
                  R8 ); // BW
    {
        using FrameCPP::Universal::FrHistory;

        INT_2U                 d[ 512 ];
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            sizeof( d ) / sizeof( *d ), R8, S, R8 ) };

        for ( INT_4U x = 0; x < sizeof( d ) / sizeof( *d ); x++ )
        {
            d[ x ] = x;
        }
        FrVect data( S, 1, ddims, d, "Test of Ramp data" );
        p.RefData( ).append( data );
        FrProcData::AuxParamList_type& param( p.GetAuxParam( ) );
        append( param, FrProcData::AuxParam_type( S, R8 ), I2U );
        //-----------------------------------------------------------------
        // History
        //-----------------------------------------------------------------
        LM_AT( );
        FrHistory h1( S, I4U, S );
        LM_AT( );
        p.RefHistory( ).append( h1 );
        //-----------------------------------------------------------------
        // FrTable
        //-----------------------------------------------------------------
        FrTable table( S, I2U );
        table.AppendComment( S );
        // character vector
        {
            CHAR                   d[ 4 ] = { C, C, C, C };
            FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
                I4U, R8, S, R8 ) };
            FrVect                 dvect( S, I2U, ddims, d, S );
            table.RefColumn( ).append( dvect );
        }
        p.RefTable( ).append( table );
    }

    LM_AT( );
    fr->RefProcData( ).append( p );

    // FrSimData
    LM_AT( );
    fr->RefSimData( ).append( FrSimData( S, S, R4, R8, R4 ) );
    LM_AT( );
    fr->RefSimData( ).append( FrSimData( "SimData2", S, R4, R8, R4 ) );

    // FrSerData
    LM_AT( );
    FrSerData srd( S, GPSTime( I4U, I4U ), R4 );
    LM_AT( );
    srd.SetData( S );
    LM_AT( );
    FrSerData srd2( "SerData2", GPSTime( I4U, I4U ), R4 );
    LM_AT( );
    fr->GetRawData( )->RefFirstSer( ).append( srd );
    LM_AT( );
    fr->GetRawData( )->RefFirstSer( ).append( srd2 );

    // FrStatData
    LM_AT( );
    FrameCPP::Universal::FrStatData statData(
        "StatData1", S, S, I4U, I4U, I4U );
    // real vector
    {
        REAL_8                 d[ 4 ] = { R8, R8, R8, R8 };
        FrVect::dimension_type ddims[ 1 ] = { FrVect::dimension_type(
            I4U, R8, S, R8 ) };
        FrVect                 dvect( "FrVect1", I2U, ddims, d, S );
        statData.RefData( ).append( dvect );
    }

    LM_AT( );
    statData.SetDetector( fr->RefDetectSim( )[ 0 ] );

    boost::shared_ptr< FrameCPP::Universal::FrStatData > sdp;

    sdp.reset( statData.Clone( ) );
    StatData.push_back( sdp );
    LM_AT( );
    statData.SetDetector( fr->RefDetectProc( )[ 0 ] );
    sdp.reset( statData.Clone( ) );
    StatData.push_back( sdp );

    // FrEvent
    // LM_AT( );
    FrEvent::ParamList_type event_params;
    event_params.push_back( FrEvent::Param_type( S, R4 ) );
    FrEvent event(
        S, S, S, GPSTime( I4U, I4U ), R4, R4, I4U, R4, R4, S, event_params );
    {
        FrEvent::ParamList_type& param( event.GetParam( ) );
        append( param, FrEvent::Param_type( S, R4 ), I2U );
    }
    // LM_AT( );
    fr->RefEvent( ).append( event );

    // FrSummary
    ;
    LM_AT( );
    fr->RefSummaryData( ).append( FrSummary( S, S, S, GPSTime( I4U, I4U ) ) );
    LM_AT( );
    fr->RefSummaryData( ).append(
        FrSummary( "FrSummary2", S, S, GPSTime( I4U, I4U ) ) );

    // FrSimEvent
    LM_AT( );
    FrSimEvent::ParamList_type sed_param;
    sed_param.resize( 1 );
    sed_param[ 0 ] = FrSimEvent::Param_type( S, R8 );
    FrSimEvent sed( S, S, S, GPSTime( I4U, I4U ), R4, R4, R4, sed_param );
    LM_AT( );
    {
        FrSimEvent::ParamList_type& param( sed.GetParam( ) );
        append( param, FrSimEvent::Param_type( S, R4 ), I2U );
    }
    LM_AT( );
    fr->RefSimEvent( ).append( sed );

    // FrTable
    FrTable table( S, I2U );
    table.AppendComment( S );
    fr->RefAuxTable( ).append( table );
    fr->RefSimEvent( )[ 0 ]->RefTable( ).append( table );

    // ???

    //     fr->RefAuxTable()[0]->RefColumns().append( vect );

    // :TODO: :FIXME: new Table interface
    // Create table with 8 rows
    //     Table table( "slow", 8 );
    //     fr->RefAuxTable().append( table );
    // Insert first column: verified to be 8 rows deep; vector name is taken
    // first column name
    //     fr->RefAuxTable()[0]->append( vect );
    // Insert first column
    //     fr->RefAuxTable()[0]->insert( 0, vect );
    // Remove indexed column
    //     fr->RefAuxTable()[0]->erase( 0 );
    // Remove range of columns
    //     fr->RefAuxTable()[0]->erase( 0, 1 );

    //-------------------------------------------------------------------
    // RAW
    //-------------------------------------------------------------------
    compress( *fr, FrVect::RAW, 0 );
    //-------------------------------------------------------------------
    // Test all compression levels for gzip.
    //-------------------------------------------------------------------
    for ( INT_2U level = 1; level <= 9; level++ )
    {
        compress( *fr, FrVect::GZIP, level );
    }
    //-------------------------------------------------------------------
    // Test all compression levels for gzip_diff.
    //-------------------------------------------------------------------
    for ( INT_2U level = 1; level <= 9; level++ )
    {
        compress( *fr, FrVect::DIFF_GZIP, level );
    }
    //-------------------------------------------------------------------
    // ZERO_SUPPRESS_SHORT
    //-------------------------------------------------------------------
    compress( *fr, FrVect::ZERO_SUPPRESS, 0 );
    //-------------------------------------------------------------------
    // ZERO_SUPPRESS_INT_FLOAT
    //-------------------------------------------------------------------
    // compress( *fr, FrVect::ZERO_SUPPRESS, 0 );

    return fr;
}

#endif /* FRAME_CPP__TEST__FR_SAMPLE_6_TCC */
