//
// 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 <thread>
#include <vector>

#include "../src/frame.hh"
#include "../src/history.hh"
#include "../src/rawdata.hh"
#include "../src/adcdata.hh"
#include "../src/detector.hh"
#include "../src/framewritertoc.hh"
#include "../src/tocreader.hh"
#include "../src/framereader.hh"
#include "test.hh"
#include "../src/dump.hh"

using FrameCPP::Frame;
using FrameCPP::FrameReader;
using FrameCPP::FrameWriterTOC;
using FrameCPP::Location;
using FrameCPP::Time;
using FrameCPP::TOCReader;

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

    // Detector
    FrameCPP::Detector d(
        "Hidden 1", Location( 1, 2, 3.44, 5, 6, 7.8 ), 15.0, 1.75, 3.56 );
    fr->setDetectSim( d );

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

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

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

    // Vect
    REAL_4              ddata[ 8 ] = { 5.4, 6.5, 7.6, 8.7, 1.0, 2.1, 3.2, 4.3 };
    FrameCPP::Dimension ddims[ 1 ] = { FrameCPP::Dimension( 8, 1.0, "count" ) };
    FrameCPP::Vect      dvect( "digitized data", 1, ddims, ddata, "K" );
    fr->getRawData( )->refAdc( )[ 0 ]->refData( ).append( dvect );

    // Log Message
    FrameCPP::Msg m( "Test", "This is a test", 1 );
    fr->getRawData( )->refMsg( ).append( m );

    // ProcData
    FrameCPP::ProcData p( "Proc data channel", 1024, Time( 0, 0 ), 115 );
    fr->refStrain( ).append( p );

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

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

    // StatData
    FrameCPP::StatData statData(
        "Some stat data", "no comment", "interesting", 100, 200, 4 );
    statData.setDetector( fr->getDetectSim( ) );
    fr->getDetectSim( )->refStatData( ).append( statData );

    // TrigData
    FrameCPP::TrigData trigData( "Binary inspiral",
                                 "a comment",
                                 "inputs",
                                 Time( 50, 57 ),
                                 1.0,
                                 2.0,
                                 123,
                                 125.3,
                                 .796,
                                 "magic" );
    fr->refTrigData( ).append( trigData );

    // Summary
    FrameCPP::Summary summary( "power", "mean laser power", "none" );
    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 };
    FrameCPP::Dimension dims[ 2 ] = { FrameCPP::Dimension( 2, 1.0, "kg" ),
                                      FrameCPP::Dimension( 4, 1.5, "m" ) };
    FrameCPP::Vect      vect( "Misc data", 2, dims, data, "J" );
    fr->refUser( ).append( vect );

    // Table
    FrameCPP::Table 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
    FrameCPP::SimEvent 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;
    //    cout << "Testing write/read... ";

    Frame* frame = makeFrame( );

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

    {
        ifstream in( "io.test.dat" );
        try
        {
            TOCReader fr( in );
            fr.setErrorWatch( error_watch );
            for ( int i = 0; i < 10; i++ )
            {
                delete fr.readFrame(
                    (int)( *( (unsigned char*)frame + i ) > 0xff / 2 ) );
            }

            for ( int i = 0; i < 10; i++ )
            {
                delete fr.readFrameHeader(
                    (int)( *( (unsigned char*)frame + i ) > 0xff / 2 ) );
            }

            Frame* frame2 = fr.readFrame( 1 );
            Frame* frame3 = fr.readFrame( 0 );

            TEST( ( *frame == *frame2 && *frame == *frame3 ) );
            cerr << frame3->getGTime( ).getNSec( ) << endl;
            delete frame2;
            delete frame3;

            try
            {
                FrameCPP::AdcData* adc1 = fr.readADC( 0, "Channel 37" );
                FrameCPP::AdcData* adc2 = fr.readADC( 1, "Channel 37" );
                TEST( ( *adc1 == *adc2 ) );

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

                vector< FrameCPP::StatData* > sv =
                    fr.readStatName( "Some stat data" );
                TEST( ( *sv[ 0 ] == *sv[ 1 ] ) );
            }
            catch ( not_found_error )
            {
                cerr << "static";
                throw;
            }
        }
        catch ( not_found_error )
        {
            cerr << " not found" << endl;
            exit( 1 );
        }
        catch ( read_failure )
        {
            cerr << "toc read failure" << endl;
            exit( 1 );
        }

        in.close( );
    }

    delete frame;

    TMSGEND;
}

void
read_test( )
{
    {
        ifstream    in( "io.test.dat" );
        FrameReader fr( in );
        //  fr.setErrorWatch( error_watch );

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

            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();
            }

            if ( frame )
            {
                delete frame;
            }
        }
        catch ( read_failure )
        {
            ;
        }

        in.close( );
    }

    cerr << "read test successful" << endl;
}

int
main( int argc, char* argv[] )
{
    TSTART;
    // 20 reader threads, all joinable. Pre-port code used
    // PTHREAD_CREATE_DETACHED here and then attempted to pthread_join the
    // detached threads (UB), and also looped the join 100 times while only
    // creating 20 threads (UB again). Both bugs are removed by switching
    // to std::thread with explicit join — the threads are joinable by
    // default and the loop bound matches the spawn count.
    const std::size_t       num_threads = 20;
    std::vector< std::thread > consumers;
    consumers.reserve( num_threads );

    for ( std::size_t i = 0; i < num_threads; ++i )
    {
        consumers.emplace_back( read_test );
        cerr << "tid=" << consumers.back( ).get_id( ) << endl;
    }

    for ( auto& t : consumers )
    {
        t.join( );
    }
    exit( 0 );

    TEST( readWriteTest( ) );

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