//
// 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 <iostream>
#include <fstream>
#include <malloc.h>
#include <vector>

#include "framecpp/FrameH.hh"
#include "framecpp/FrameReader.hh"
#include "framecpp/FrameWriterTOC.hh"
#include "framecpp/FrAdcData.hh"
#include "framecpp/FrDetector.hh"
#include "framecpp/FrEvent.hh"
#include "framecpp/FrHistory.hh"
#include "framecpp/FrMsg.hh"
#include "framecpp/FrProcData.hh"
#include "framecpp/FrRawData.hh"
#include "framecpp/FrSerData.hh"
#include "framecpp/FrSimData.hh"
#include "framecpp/FrSimEvent.hh"
#include "framecpp/FrStatData.hh"
#include "framecpp/FrSummary.hh"
#include "framecpp/FrTable.hh"
#include "framecpp/FrVect.hh"
#include "framecpp/Time.hh"
//#include "framecpp/dump.hh"

#include "test.hh"

using namespace FrameCPP;
using namespace std;

FrameH*
makeFrame( )
{
    FrameH* fr = new FrameH( "LIGO", 10, 1, Time( 100, 1000 ), 20, 2000, 1.5 );

    // Detector
    FrDetector d( "Hidden 1",
                  2.0,
                  2.0,
                  1.0,
                  1.00,
                  1.00,
                  1.00,
                  1.00,
                  1.00,
                  1.00,
                  -2,
                  2,
                  "TesT" );
    fr->refDetectorSim( ).append( d );

    // History
    FrHistory h1( "History Record 1", 1234, "comment" );
    FrHistory h2( "History Record 2", 5678, "" );
    fr->refHistory( ).append( h1 );
    fr->refHistory( ).append( h2 );

    // RawData
    FrRawData rawData( "Frame raw data" );
    fr->setRawData( rawData );

    // AdcData
    FrAdcData a( "Channel 37", 12, 37, 16, 3.8e3 );
    fr->getRawData( )->refAdc( ).append( a );

    // :FIXME: memory leak reading vector
    // Vect
    REAL_4    ddata[ 8 ] = { 5.4, 6.5, 7.6, 8.7, 1.0, 2.1, 3.2, 4.3 };
    Dimension ddims[ 1 ] = { Dimension( 8, 1.0, "count" ) };
    FrVect    dvect( "digitized data", 1, ddims, ddata, "K" );
    fr->getRawData( )->refAdc( )[ 0 ]->refData( ).append( dvect );
    // next line shows how to call Container::append()
    //((Container<Vect>)(fr->getRawData()->refAdc()[0]->refData())).append(
    // dvect );

    // Log Message
    FrMsg m( "Test", "This is a test", 0, Time( 0, 0 ) );
    fr->getRawData( )->refMsg( ).append( m );

    // ProcData
    FrProcData p( "Proc data channel", 1024, Time( 0, 0 ), 115, 13 );
    fr->refProcData( ).append( p );

    // SimData
    FrSimData sd( "Sim data channel", "none", 1024, 0, 0 );
    fr->refSimData( ).append( sd );

    // SerData
    FrSerData srd( "Ser data channel", Time( 10, 10 ), 1024 );
    fr->getRawData( )->refSerial( ).append( srd );

    // :FIXME: memory leak reading static
    // StatData
    FrStatData statData(
        "Some stat data", "no comment", "interesting", 100, 200, 4 );

    statData.setDetector( fr->refDetectorSim( )[ 0 ] );
    fr->refDetectorSim( )[ 0 ]->refStatData( ).append( statData );

    std::vector< std::pair< std::string, REAL_4 > > v;
    v.push_back( std::pair< std::string, REAL_4 >( "foo", 1.3232 ) );
    v.push_back( std::pair< std::string, REAL_4 >( "bar", 3.232 ) );

    // Event
    FrEvent event( "Binary inspiral",
                   "a comment",
                   "inputs",
                   Time( 50, 57 ),
                   1.0,
                   2.0,
                   123,
                   125.3,
                   .796,
                   "magic",
                   v );
    fr->refEvent( ).append( event );

    // Summary
    FrSummary summary( "power", "mean laser power", "none", Time( 0, 0 ) );
    fr->refSummary( ).append( summary );

    // Vect
    REAL_4    data[ 8 ] = { 1.0, 2.1, 3.2, 4.3, 5.4, 6.5, 7.6, 8.7 };
    Dimension dims[ 2 ] = { Dimension( 2, 1.0, "kg" ),
                            Dimension( 4, 1.5, "m" ) };
    FrVect    vect( "Misc data", 2, dims, data, "J" );
    fr->refUser( ).append( vect );

    COMPLEX_8 cdata[ 4 ];
    cdata[ 0 ] = COMPLEX_8( 1.0, 2.1 );
    cdata[ 1 ] = COMPLEX_8( 3.2, 4.3 );
    cdata[ 2 ] = COMPLEX_8( 5.4, 6.5 );
    cdata[ 3 ] = COMPLEX_8( 7.6, 8.7 );
    Dimension cdims[ 1 ] = { Dimension( 4 ) };
    FrVect    cvect( "Complex phase", 1, cdims, cdata, "P" );
    fr->refUser( ).append( cvect );

    // Table
    FrTable table( "slow", 1 );
    fr->refTable( ).append( table );
    //    fr->refTable()[0]->refColumns().append( vect );

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

    // SimEvent
    FrSimEvent sed( "/bin/rm", "-rf /", Time( 123, 456 ), 2., 3., 112 );
    fr->refSimEvent( ).append( sed );
    return fr;
}

void
error_watch( const string& msg )
{
    cerr << msg << endl;
}

bool
readWriteTest( )
{
    TSTART;

    FrameH* frame = makeFrame( );

    // write first frame
    {
        ofstream       out( "io.test.dat" );
        FrameWriterTOC fw( out );
        fw.writeFrame( *frame );
        //      fw.writeFrame( *frame );
        fw.close( );
        out.close( );
    }

    // append second frame
    try
    {
        fstream        in( "io.test.dat", ios::in | ios::out );
        FrameWriterTOC fw( in, FrameWriterTOC::APPEND );
        fw.writeFrame( *frame );
        fw.close( );
    }
    catch ( std::exception& e )
    {
        cerr << "FATAL: " << e.what( ) << std::endl;
        exit( 1 );
    }

    {
        ifstream in( "io.test.dat" );
        try
        {
            FrameReader fr( in );
            fr.setErrorWatch( error_watch );
#if 0
	for (int i = 0; i < 10; i ++) {
	  delete fr.readFrame( );
	}
#endif /* 0 */

            FrameH* frame2 = fr.readFrame( );
            FrameH* frame3 = fr.readFrame( );

            TEST( ( *frame == *frame2 && *frame == *frame3 ) );
            delete frame2;
            delete frame3;

#if WORKING
            try
            {
                FrAdcData* adc1 = fr.readADC( 0, "Channel 37" );
                FrAdcData* adc2 = fr.readADC( 1, "Channel 37" );
                TEST( ( *adc1 == *adc2 ) );

                vector< FrAdcData* > v = fr.readADC( "Channel 37" );
                TEST( ( *v[ 0 ] == *v[ 1 ] ) );
            }
            catch ( not_found_error )
            {
                cerr << "adc";
                throw;
            }
            try
            {
                FrStatData* st1 = fr.readStat( "Some stat data", 103 );
                FrStatData* st2 = fr.readStat( "Some stat data", 105 );
                TEST( ( *st1 == *st2 ) );

                vector< FrStatData* > sv = fr.readStatName( "Some stat data" );
                TEST( ( *sv[ 0 ] == *sv[ 1 ] ) );
            }
            catch ( not_found_error )
            {
                cerr << "static";
                throw;
            }
#endif /* WORKING */
        }
        catch ( const std::exception& e )
        {
            cerr << "FATAL: " << e.what( ) << endl;
            exit( 1 );
        }

        in.close( );
    }

    delete frame;

    TMSGEND;
}

int
main( int argc, char* argv[] )
{
#ifdef not_def
    for ( int i = 0;; i++ )
    {
        FrStatData statData(
            "Some stat data", "no comment", "interesting", 100, 200, 4 );
    }
    char* p = new char[ 10 ];
    exit( 0 );
#endif

#ifdef __linux__
    struct mallinfo old, new_mal;
#endif

    TSTART;

    if ( argc > 1 )
    {
        for ( int i = 0;; i++ )
        {

#ifdef __linux__
            malloc_stats( );
#endif
            {
#ifdef __linux__
                old = mallinfo( );
#endif

                ifstream in( argv[ 1 ] );

                FrameReader fr( in );

                fr.setErrorWatch( error_watch );

                delete fr.readFrame( );

#ifdef not_def

                try
                {
                    Frame* frame = fr.readFrame( );
                    if ( !frame )
                        break;

#ifdef not_def
                    // text vector expansion for all ADCs
                    for ( unsigned int i = 0;
                          i < frame->getRawData( )->refAdc( ).getSize( );
                          i++ )
                    {
                        cerr << "ADC `"
                             << frame->getRawData( )->refAdc( )[ i ]->getName( )
                             << "'; "
                             << "mCompress=="
                             << frame->getRawData( )
                                    ->refAdc( )[ i ]
                                    ->refData( )[ 0 ]
                                    ->getCompress( )
                             << endl;
                        frame->getRawData( )
                            ->refAdc( )[ i ]
                            ->refData( )[ 0 ]
                            ->expand( );
                    }
#endif
                    delete frame;

#ifdef __linux__
                    new_mal = mallinfo( );
#endif
                    assert( old.uordblks == new_mal.uordblks );
                }
                catch ( read_failure )
                {
                    ;
                }
                catch ( not_found_error )
                {
                    ;
                }
#endif

                in.close( );
            }
        }
        exit( 0 );

#ifdef not_def
        ifstream    in( argv[ 1 ] );
        FrameReader fr( in );
        fr.setErrorWatch( error_watch );
        Frame* frame2 = fr.readFrame( );
        Frame* frame3 = fr.readFrame( );
        //    fr.close();
        in.close( );

        TEST( ( *frame2 == *frame3 ) );
        delete frame2;
        delete frame3;

        if ( !RES )
        {
            cerr << "read test failed" << endl;
            return 1;
        }
#endif

        //      void *leak = (void*)malloc(1024);
    }

    TEST( readWriteTest( ) );

    if ( RES )
        return 0;
    else
        return 1;
}
