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

#if HAVE_CONFIG_H
#include <framecpp_config.h>
#endif /* HAVE_CONFIG_H */

#include <stdlib.h>

#include <complex>
#include <fstream>
#include <sstream>

#include "framecpp/FrameCPP.hh"
#include "framecpp/FrameH.hh"
#include "framecpp/Version6/OFrameStream.hh"

using namespace FrameCPP::Version;

char* Program;

void
usage( )
{
    std::cerr << "Usage: " << Program << " frame_file [ frame_file ... ]"
              << std::endl;
}

template < class T >
std::string
ramp_message( INT_4U Size, T Offset, T Denominator )
{
    std::ostringstream oss;
    oss << "This channel ramps from " << Offset << " to "
        << ( ( ( Size - 1 ) / Denominator ) + Offset ) << " by steps of "
        << ( 1 / Denominator );
    return oss.str( );
}

template <>
std::string
ramp_message( INT_4U Size, CHAR Offset, CHAR Denominator )
{
    std::ostringstream oss;
    oss << "This channel ramps from " << (INT_2S)Offset << " to "
        << ( INT_2S )( ( ( Size - 1 ) / Denominator ) + Offset )
        << " by steps of " << ( INT_2S )( 1 / Denominator );
    return oss.str( );
}

template <>
std::string
ramp_message( INT_4U Size, CHAR_U Offset, CHAR_U Denominator )
{
    std::ostringstream oss;
    oss << "This channel ramps from " << (INT_2U)Offset << " to "
        << ( INT_2U )( ( ( Size - 1 ) / Denominator ) + Offset )
        << " by steps of " << ( INT_2U )( 1 / Denominator );
    return oss.str( );
}

template < class T >
void
ramp_adc( const std::string&        Name,
          INT_4U                    Size,
          FrRawData::firstAdc_type& Adc,
          T                         Offset = 0,
          T                         Denominator = 1 )
{
    T*        data( new T[ Size ] );
    Dimension dim( Size );

    for ( INT_4U x = 0; x < Size; x++ )
    {
        data[ x ] = ( x / Denominator ) + Offset;
    }

    FrAdcData* channel( new FrAdcData( Name,
                                       0,
                                       0,
                                       0,
                                       Size,
                                       FR_ADC_DATA_DEFAULT_BIAS,
                                       FR_ADC_DATA_DEFAULT_SLOPE,
                                       FR_ADC_DATA_DEFAULT_UNITS( ),
                                       FR_ADC_DATA_DEFAULT_FSHIFT,
                                       FR_ADC_DATA_DEFAULT_TIME_OFFSET,
                                       FR_ADC_DATA_DEFAULT_DATA_VALID,
                                       FR_ADC_DATA_DEFAULT_PHASE ) );

    channel->AppendComment( ramp_message( Size, Offset, Denominator ) );

    Adc.append( channel, false /* allocate */, true /* own */ );
    channel->RefData( ).append(
        new FrVect(
            Name, 1, &dim, data, "NONE", false /* allocate */, true /* own */ ),
        false, /* allocate */
        true /* own */ );
}

template < class T >
std::string
ramp_message( INT_4U Size,
              T      RealOffset,
              T      RealDenominator,
              T      ImagOffset,
              T      ImagDenominator )
{
    std::ostringstream oss;
    oss << "This channel ramps from " << RealOffset << "+i" << ImagOffset
        << " to " << ( ( ( Size - 1 ) / RealDenominator ) + RealOffset ) << "+i"
        << ( ( ( Size - 1 ) / ImagDenominator ) + ImagOffset )
        << " by steps of " << ( 1.0 / RealDenominator ) << "+i"
        << ( 1.0 / ImagDenominator );
    return oss.str( );
}

template < class T >
void
ramp_adc( const std::string&        Name,
          INT_4U                    Size,
          FrRawData::firstAdc_type& Adc,
          T                         RealOffset,
          T                         RealDenominator,
          T                         ImagOffset,
          T                         ImagDenominator )
{
    std::complex< T >* data( new std::complex< T >[ Size ] );
    Dimension          dim( Size );

    for ( INT_4U x = 0; x < Size; x++ )
    {
        data[ x ] = std::complex< T >( ( x / RealDenominator ) + RealOffset,
                                       ( x / ImagDenominator ) + ImagOffset );
    }

    FrAdcData* channel( new FrAdcData( Name,
                                       0,
                                       0,
                                       0,
                                       Size,
                                       FR_ADC_DATA_DEFAULT_BIAS,
                                       FR_ADC_DATA_DEFAULT_SLOPE,
                                       FR_ADC_DATA_DEFAULT_UNITS( ),
                                       FR_ADC_DATA_DEFAULT_FSHIFT,
                                       FR_ADC_DATA_DEFAULT_TIME_OFFSET,
                                       FR_ADC_DATA_DEFAULT_DATA_VALID,
                                       FR_ADC_DATA_DEFAULT_PHASE ) );

    channel->AppendComment( ramp_message(
        Size, RealOffset, RealDenominator, ImagOffset, ImagDenominator ) );

    Adc.append( channel, false /* allocate */, true /* own */ );
    channel->RefData( ).append(
        new FrVect(
            Name, 1, &dim, data, "NONE", false /* allocate */, true /* own */ ),
        false, /* allocate */
        true /* own */ );
}

template < class T >
void
ramp_proc( const std::string&     Name,
           INT_4U                 Size,
           FrameH::procData_type& Proc,
           T                      Offset = 0,
           T                      Denominator = 1 )
{
    T*        data( new T[ Size ] );
    Dimension dim( Size );

    for ( INT_4U x = 0; x < Size; x++ )
    {
        data[ x ] = ( x / Denominator ) + Offset;
    }

    FrProcData* channel(
        new FrProcData( Name,
                        ramp_message( Size, Offset, Denominator ),
                        0,
                        0,
                        0, /* timeOffset */
                        1, /* tRange */
                        0, /* fShift */
                        0, /* phase */
                        0, /* fRange */
                        0 /* BW */ ) );

    Proc.append( channel, false /* allocate */, true /* own */ );
    channel->RefData( ).append(
        new FrVect(
            Name, 1, &dim, data, "NONE", false /* allocate */, true /* own */ ),
        false, /* allocate */
        true /* own */ );
}

int
main( int ArgC, char** ArgV ) try
{
    Program = ArgV[ 0 ];

    std::ostringstream filename;
    filename << "RampFrame" << FRAME_SPEC_CURRENT << ".gwf";

    //---------------------------------------------------------------------
    // Establish a working frame header
    //---------------------------------------------------------------------
    FrameH frameh( "Ramp Frame", 0, 0, GPSTime( 20, 0 ), 0, 1 );
    //---------------------------------------------------------------------
    // Add the raw data structure
    //---------------------------------------------------------------------
    FrRawData raw_data;

    frameh.SetRawData( &raw_data, false /* allocate */, false /* own */ );
    //---------------------------------------------------------------------
    // Add an adc for each numeric data type
    //---------------------------------------------------------------------

    FrRawData::firstAdc_type& adcRef( raw_data.RefFirstAdc( ) );

    ramp_adc< CHAR_U >( "adc_char_u", 256, adcRef );
    ramp_adc< CHAR >( "adc_char", 256, adcRef, -128 );
    ramp_adc< INT_2S >( "adc_int_2s", 65536, adcRef, -32768 );
    ramp_adc< INT_2U >( "adc_int_2u", 65536, adcRef );
    ramp_adc< INT_4S >( "adc_int_4s", 65536, adcRef, -32768 );
    ramp_adc< INT_4U >( "adc_int_4u", 65536, adcRef );
    ramp_adc< INT_8S >( "adc_int_8s", 65536, adcRef, -32768 );
    ramp_adc< INT_8U >( "adc_int_8u", 65536, adcRef );
    ramp_adc< REAL_4 >( "adc_real_4", 65536, adcRef, -1927, 17 );
    ramp_adc< REAL_8 >( "adc_real_8", 65536, adcRef, -1927, 17 );

    //---------------------------------------------------------------------
    // Ramp complex data types
    //---------------------------------------------------------------------

    ramp_adc< REAL_4 >( "adc_complex_8", 65536, adcRef, -1927, 17, -2520, 13 );
    ramp_adc< REAL_8 >( "adc_complex_16", 65536, adcRef, -1927, 17, -2520, 13 );

    //---------------------------------------------------------------------
    // Ramp process data
    //---------------------------------------------------------------------

    FrameH::procData_type& procRef( frameh.RefProcData( ) );

    ramp_proc< REAL_8 >( "proc_real_8", 65536, procRef, -1927, 17 );

    //---------------------------------------------------------------------
    // Have finished creating the frame, now dump it out.
    //---------------------------------------------------------------------

    std::ofstream ofile( filename.str( ).c_str( ) );
    OFrameStream  stream( ofile );
    stream.WriteFrame( frameh );
    stream.close( );
    ofile.close( );
}
catch ( std::exception& e )
{
    std::cerr << "FATAL: Caught exception: " << e.what( ) << std::endl;
    exit( 1 );
}
catch ( ... )
{
    std::cerr << "FATAL: Caught unknown exception: " << std::endl;
    exit( 1 );
}
