//
// 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 <fcntl.h>
#include <unistd.h>

#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <cstring>

#include <boost/pointer_cast.hpp>
#include <boost/scoped_array.hpp>
#include <boost/shared_ptr.hpp>

#include "ldastoolsal/MemChecker.hh"

#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/CheckSum.hh"
#include "framecpp/Common/IOStream.hh"
#include "framecpp/Common/FrameStream.hh"
#include "framecpp/Common/FrameBuffer.hh"

#include "framecpp/FrameCPP.hh"

#include "framecpp/FrameH.hh"
#include "framecpp/FrAdcData.hh"
#include "framecpp/FrHistory.hh"
#include "framecpp/FrRawData.hh"
#include "framecpp/FrVect.hh"
#include "framecpp/GPSTime.hh"

using namespace std;

using FrameCPP::GPSTime;
using LDASTools::AL::MemChecker;

int num_channels = 1;

boost::shared_ptr< FrameCPP::FrRawData > rawData;

boost::shared_ptr< FrameCPP::FrameH >
full_frame( int foo, long bar, int frame_length_seconds )
{
    rawData =
        boost::shared_ptr< FrameCPP::FrRawData >( new FrameCPP::FrRawData );
    boost::shared_ptr< FrameCPP::FrameH > frame;
    FrameCPP::FrHistory                   history(
        "", 0, "framebuilder, framecpp-" + string( FRAMECPP_VERSION ) );
    frame = boost::shared_ptr< FrameCPP::FrameH >( new FrameCPP::FrameH(
        "LIGO",
        0, // run number ??? buffpt -r> block_prop (nb) -> prop.run;
        1, // frame number
        GPSTime( 0, 0 ),
        0, // leap seconds
        frame_length_seconds // dt
        ) );

    frame->SetRawData( rawData );

    for ( int i = 0; i < num_channels; i++ )
    {
        char buf[ 128 ];
        snprintf( buf, sizeof(buf), "%d", i );
        auto adc = boost::make_shared< FrameCPP::FrAdcData >(
            buf,
            1,
            i, // channel ???
            8 * 32,
            1024,
            0,
            0,
            std::string( "" ),
            0,
            0,
            0,
            .0 ); /* heterodyning phase in radians */
        adc->SetDataValid( 0 );

        int DATA_SET_SIZE = 16 * frame_length_seconds;
        //-------------------------------------------------------------------
        // Append ADC AUX vector to store 16 status words per second
        //-------------------------------------------------------------------
        FrameCPP::FrVect::dimension_type aux_dims[ 1 ] = {
            FrameCPP::FrVect::dimension_type(
                16 * frame_length_seconds, 1. / 16, "" )
        };
        FrameCPP::FrVect::data_type data8(
            new FrameCPP::FrVect::data_type::element_type[ DATA_SET_SIZE *
                                                           sizeof( REAL_8 ) ] );
        std::fill( reinterpret_cast< REAL_8* >( &( data8[ 0 ] ) ),
                   reinterpret_cast< REAL_8* >(
                       &( data8[ DATA_SET_SIZE * sizeof( REAL_4 ) ] ) ),
                   REAL_8( 3.1415 ) );

        auto aux_vect = boost::make_shared< FrameCPP::FrVect >(
            "dataValid",
            FrameCPP::FrVect::RAW, /* Compress */
            FrameCPP::FrVect::FR_VECT_8R, /* Type */
            1,
            aux_dims,
            DATA_SET_SIZE, /* NData */
            DATA_SET_SIZE * sizeof( REAL_8 ), /* NBytes */
            data8,
            "" );
        auto aux = adc->RefAux( );
        aux.append( aux_vect );

        //-------------------------------------------------------------------
        // Append Data vector
        //-------------------------------------------------------------------
        DATA_SET_SIZE = 65536 * frame_length_seconds;
        FrameCPP::FrVect::data_type data4(
            new FrameCPP::FrVect::data_type::element_type[ DATA_SET_SIZE *
                                                           sizeof( REAL_4 ) ] );
        std::fill( reinterpret_cast< REAL_4* >( &( data4[ 0 ] ) ),
                   reinterpret_cast< REAL_4* >(
                       &( data4[ DATA_SET_SIZE * sizeof( REAL_4 ) ] ) ),
                   REAL_4( 3.1415 ) );

        FrameCPP::FrVect::dimension_type dims[ 1 ] = {
            FrameCPP::FrVect::dimension_type(
                65536 * frame_length_seconds, 1. / 65536, "time" )
        };

        auto vect = boost::make_shared< FrameCPP::FrVect >(
            "junk4",
            FrameCPP::FrVect::RAW, /* Compress */
            FrameCPP::FrVect::FR_VECT_4R, /* Type */
            1,
            dims,
            DATA_SET_SIZE, /* NData */
            DATA_SET_SIZE * sizeof( REAL_4 ), /* NBytes */
            data4,
            "" );
        auto data = adc->RefData( );
        data.append( vect );

        frame->GetRawData( )->RefFirstAdc( ).append( adc );
    }
    return frame;
}

int frames_per_file = 1;
int blocks_per_frame = 16;

int
main( )
{
    MemChecker::Trigger gc_trigger( true );

    FrameCPP::Initialize( );

    long                                  frame_cntr;
    boost::shared_ptr< FrameCPP::FrameH > frame;

    if ( frames_per_file != 1 )
    {
        printf( "Not supported frames_per_file=%d\n", frames_per_file );
        abort( );
    }

    frame = full_frame( 0, 0, blocks_per_frame );

    if ( !frame )
    {
        return -1;
    }

    for ( frame_cntr = 0; frame_cntr == 0; frame_cntr++ )
    {
        int       eof_flag = 0;
        time_t    frame_start;
        time_t    gps = 0, gps_n = 0;
        GPSTime   gps_frame_start( gps, gps_n );
        struct tm tms;

        if ( eof_flag )
            break;

        frame_start = gps_frame_start.GetSeconds( GPSTime::UTC );
        gmtime_r( &frame_start, &tms );
        tms.tm_mon++;

        frame->SetGTime( GPSTime( gps, gps_n ) );
        int fd = creat( "foo", 0644 );
        if ( fd < 0 )
        {
            // system_log(1, "Couldn't open full frame file `%s' for writing;
            // errno %d", _tmpf, errno); fsd.report_lost_frame (); set_fault ();
            exit( 1 );
        }
        else
        {
            close( fd );
            {
                FrameCPP::Common::FrameBuffer< filebuf >* obuf =
                    new FrameCPP::Common::FrameBuffer< std::filebuf >(
                        std::ios::out );
                obuf->open( "foo", std::ios::out | std::ios::binary );
                FrameCPP::Common::OFrameStream ofs( obuf );
                ofs.SetCheckSumFile( FrameCPP::Common::CheckSum::CRC );
                time_t t = time( 0 );
                ofs.WriteFrame(
                    frame,
                    // FrameCPP::FrVect::GZIP, 1,
                    // FrameCPP::FrVect::RAW, 1,
                    FrameCPP::FrVect::ZERO_SUPPRESS_OTHERWISE_GZIP,
                    1,
                    // FrameCPP::Compression::MODE_ZERO_SUPPRESS_SHORT,
                    // FrameCPP::FrVect::DEFAULT_GZIP_LEVEL, /* 6 */
                    FrameCPP::Common::CheckSum::CRC );
                t = time( 0 ) - t;
                ofs.Close( );
                obuf->close( );
            }
        }
    }
    return 0;
}
