//
// 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 "ldastoolsal/fstream.hh"
#include "ldastoolsal/CommandLineOptions.hh"
#include "ldastoolsal/MemChecker.hh"

#include "framecpp/Common/FrameBuffer.hh"
#include "framecpp/FrameStreamPlan.hh"
#include "framecpp/OFrameStream.hh"

using LDASTools::AL::MemChecker;

typedef LDASTools::AL::CommandLineOptions CommandLineOptions;
typedef CommandLineOptions::Option        Option;
typedef CommandLineOptions::OptionSet     OptionSet;

typedef std::vector< std::string > filenames_type;
typedef std::vector< std::string > channels_type;

static inline std::vector< std::string >
split( std::string Source, std::string Sep )
{
    std::vector< std::string > retval;

    size_t cur_pos = 0;
    size_t end_pos;
    size_t len;

    do
    {
        end_pos = Source.find_first_of( Sep, cur_pos );
        len = end_pos;
        if ( std::string::npos != end_pos )
        {
            len -= cur_pos - 1;
        }
        retval.push_back( Source.substr( cur_pos, len ) );
        if ( std::string::npos != end_pos )
        {
            cur_pos = ++end_pos;
        }
    } while ( end_pos != std::string::npos );
    return retval;
}

static inline void
depart( int ExitCode )
{
    exit( ExitCode );
}

class CommandLine : protected CommandLineOptions
{
public:
    using CommandLineOptions::empty;
    using CommandLineOptions::Pop;
    typedef CommandLineOptions::option_type option_type;

    CommandLine( int ArgC, char** ArgV );

    const channels_type& Channels( ) const;

    const filenames_type& Filenames( ) const;

    const std::string& Output( ) const;

    void Usage( int ExitValue ) const;

    void operator( )( );

private:
    enum option_types
    {
        OPT_CHANNELS,
        OPT_OUTPUT,
        OPT_FILENAMES,
        OPT_HELP
    };

    OptionSet options;

    channels_type  channels;
    filenames_type filenames;
    std::string    output;
};

CommandLine::CommandLine( int ArgC, char** ArgV )
    : CommandLineOptions( ArgC, ArgV )
{
    //---------------------------------------------------------------------
    // Setup the options that will be recognized.
    //---------------------------------------------------------------------
    std::ostringstream desc;

    options.Synopsis( "[options] <file> [<file> ...]" );
    options.Summary( "This creates a new frame based on the contents"
                     " of one or more input frames."
                     " It uses an optimized frame reader." );

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

    options.Add(
        Option( OPT_HELP, "help", Option::ARG_NONE, "Display this message" ) );

    options.Add( Option(
        OPT_CHANNELS,
        "channels",
        Option::ARG_REQUIRED,
        "A comma separated list of channels to place in the output frame",
        "string" ) );

    options.Add( Option( OPT_OUTPUT,
                         "output",
                         Option::ARG_REQUIRED,
                         "Name of the output frame file",
                         "string" ) );
}

inline const channels_type&
CommandLine::Channels( ) const
{
    return channels;
}

inline const filenames_type&
CommandLine::Filenames( ) const
{
    return filenames;
}

inline const std::string&
CommandLine::Output( ) const
{
    return output;
}

inline void
CommandLine::Usage( int ExitValue ) const
{
    std::cerr << "Usage: " << ProgramName( ) << options << std::endl;
    depart( ExitValue );
}

inline void
CommandLine::operator( )( )
{
    //---------------------------------------------------------------------
    // Parse the options specified on the command line
    //---------------------------------------------------------------------
    try
    {
        std::string name;
        std::string value;
        bool        parsing( true );

        //-------------------------------------------------------------------
        // Parse the option flags specified on the command line
        //-------------------------------------------------------------------
        while ( parsing )
        {
            const int id( Parse( options, name, value ) );

            switch ( id )
            {
            case CommandLineOptions::OPT_END_OF_OPTIONS:
                parsing = false;
                break;
            case OPT_HELP:
            {
                Usage( 0 );
            }
            break;
            case OPT_CHANNELS:
            {
                channels = split( value, "," );
            }
            break;
            case OPT_OUTPUT:
            {
                output = value;
            }
            break;
            }
        }
    }
    catch ( const std::invalid_argument& Error )
    {
        throw;
    }
    catch ( ... )
    {
    }
    //---------------------------------------------------------------------
    // Save the list of files
    //---------------------------------------------------------------------
    while ( empty( ) == false )
    {
        filenames.push_back( Pop( ) );
    }
}

//-----------------------------------------------------------------------

template < int ID, typename FrameStreamPlan >
void
FraCfg( const CommandLine& Options )
{
    typedef FrameStreamPlan                               plan_type;
    typedef typename plan_type::fr_raw_data_type          fr_raw_data_type;
    typedef typename plan_type::frame_h_type              frame_h_type;
    typedef typename plan_type::fr_adc_data_type          fr_adc_data_type;
    typedef FrameCPP::Common::FrameBuffer< std::filebuf > frame_buffer_type;
    typedef
        typename fr_raw_data_type::element_type::firstAdc_type firstAdc_type;

    //---------------------------------------------------------------------
    // Initialize some storage for results and state information
    //---------------------------------------------------------------------
    fr_raw_data_type rawData( new typename fr_raw_data_type::element_type( ) );
    frame_h_type     output_frame;
    std::unique_ptr< plan_type > first_reader;

    FrameCPP::Initialize( );

    //---------------------------------------------------------------------
    // Process each frame
    //---------------------------------------------------------------------
    for ( filenames_type::const_iterator
              filename_first = Options.Filenames( ).begin( ),
              filename_cur = Options.Filenames( ).begin( ),
              filename_last = Options.Filenames( ).end( );
          filename_cur != filename_last;
          ++filename_cur )
    {
        auto ibuf = std::make_unique< frame_buffer_type >( std::ios::in );

        ibuf->open( *filename_cur, std::ios::in | std::ios::binary );

        try
        {
            auto reader = std::make_unique< plan_type >( ibuf.release( ), first_reader.get( ) );

            plan_type* active_reader = reader.get( );

            if ( filename_cur == filename_first )
            {
                output_frame = reader->ReadFrameH( 0, 0 );
                output_frame->SetRawData( rawData );
                first_reader = std::move( reader );
                active_reader = first_reader.get( );
            }

            for ( filenames_type::const_iterator
                      name_cur = Options.Channels( ).begin( ),
                      name_last = Options.Channels( ).end( );
                  name_cur != name_last;
                  ++name_cur )
            {
                typename firstAdc_type::const_iterator base_iter(
                    output_frame->GetRawData( )->RefFirstAdc( ).find(
                        *name_cur ) );
                fr_adc_data_type adc = active_reader->ReadFrAdcData( 0, *name_cur );

                if ( base_iter ==
                     output_frame->GetRawData( )->RefFirstAdc( ).end( ) )
                {
                    output_frame->GetRawData( )->RefFirstAdc( ).append( adc );
                }
                else
                {
                    // *((*base_iter)->RefData( )[0]) += *(adc->RefData()[ 0 ]);
                }
            }
        }
        catch ( std::exception& e )
        {
            std::cerr << "DEBUG: Failed to read file: " << e.what( )
                      << std::endl;
        }
    }
    //---------------------------------------------------------------------
    // Write the frame file
    //---------------------------------------------------------------------
    auto oframebuf = std::make_unique< frame_buffer_type >( std::ios::out );

    oframebuf->open( Options.Output( ), std::ios::out | std::ios::binary );

    FrameCPP::OFrameStream ofs( oframebuf.release( ) );

    ofs.WriteFrame( output_frame );
    ofs.Close( );
}

int
main( int ArgC, char* ArgV[] )
{
    //---------------------------------------------------------------------
    // Setup
    //---------------------------------------------------------------------
    MemChecker::Trigger gc_trigger( true );
    int                 version = FRAME_SPEC_CURRENT;
    CommandLine         cl( ArgC, ArgV );
    filenames_type      filenames;
    channels_type       channels;

    //---------------------------------------------------------------------
    // Process the command line options
    //---------------------------------------------------------------------

    cl( );

    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    switch ( version )
    {
    case 9:
        FraCfg< 9, FrameCPP::IFrameStreamPlan >( cl );
        break;
    case 8:
        FraCfg< 8, FrameCPP::IFrameStreamPlan >( cl );
        break;
    default:
        //-------------------------------------------------------------------
        // This is the case where it cannot handle the version of
        //  the frame specification.
        //-------------------------------------------------------------------
        std::cerr << "Unsupported frame version: " << version << std::endl;
        exit( 1 );
    }

    //---------------------------------------------------------------------
    //---------------------------------------------------------------------
    exit( 0 );
}
