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

#include <cfloat>

#include <iomanip>
#include <iostream>
#include <list>
#include <memory>

#include <boost/shared_ptr.hpp>

#include "ldastoolsal/MemChecker.hh"
#include "ldastoolsal/CommandLineOptions.hh"
#include "ldastoolsal/types.hh"

#include "framecpp/IFrameStreamPlan.hh"
#include "framecpp/FrameCPP.hh"
#include "framecpp/FrAdcData.hh"
#include "framecpp/FrEvent.hh"
#include "framecpp/FrProcData.hh"
#include "framecpp/FrSerData.hh"
#include "framecpp/FrSimData.hh"
#include "framecpp/FrSimEvent.hh"
#include "framecpp/FrVect.hh"

#include "../src/Utilities/StandardOptions.hh"

using LDASTools::AL::MemChecker;

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

typedef boost::shared_ptr< ::FrameCPP::FrVect > vector_type;

typedef FrameCPP::FrVect::data_type data_type;

typedef FrameCPP::FrVect::data_const_pointer_type data_const_pointer_type;

using std::filebuf;
using namespace FrameCPP;

using FrameCPP::Common::FrameBuffer;

class CommandLine;

enum
{
    EXIT_CODE_OK = 0,
    EXIT_CODE_UNCAUGHT_EXCEPTION,
    EXIT_CODE_UNKNOWN_CHANNEL,
    EXIT_CODE_VECTOR_EXPANSION
};

class channel_name_constructor : public FrameCPP::Common::FrTOC::FunctionString,
                                 public std::list< std::string >
{
public:
    virtual ~channel_name_constructor( );

    virtual void operator( )( const std::string& ChannelName );
};

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

int exit_code = EXIT_CODE_OK;

void dump_vect( vector_type Vect, const CommandLine& Options );

template < class T >
void dump( const CHAR_U* Source, INT_4U NData );

template < class T >
void
dump( const CHAR_U* Source, INT_4U NData, INT_2S Precision )
{
    const T* data( reinterpret_cast< const T* >( Source ) );

    std::streamsize restore = std::cout.precision( );

    if ( Precision > 0 )
    {
        std::cout.precision( (std::streamsize)Precision );
    }
    for ( INT_4U x = 0; x < NData; ++x, ++data )
    {
        if ( x )
        {
            std::cout << ", ";
        }
        std::cout << *data;
    }
    std::cout.precision( restore );
}

template <>
void
dump< CHAR >( const CHAR_U* Source, INT_4U NData )
{
    const CHAR* data( reinterpret_cast< const CHAR* >( Source ) );

    for ( INT_4U x = 0; x < NData; ++x, ++data )
    {
        if ( x )
        {
            std::cout << ", ";
        }
        std::cout << ( INT_2S )( *data );
    }
}

template <>
void
dump< CHAR_U >( const CHAR_U* Source, INT_4U NData )
{
    const CHAR_U* data( reinterpret_cast< const CHAR_U* >( Source ) );

    for ( INT_4U x = 0; x < NData; ++x, ++data )
    {
        if ( x )
        {
            std::cout << ", ";
        }
        std::cout << ( INT_2U )( *data );
    }
}

template <>
void
dump< REAL_4 >( const CHAR_U* Source, INT_4U NData )
{
    dump< REAL_4 >( Source, NData, FLT_DIG );
}

template <>
void
dump< REAL_8 >( const CHAR_U* Source, INT_4U NData )
{
    dump< REAL_8 >( Source, NData, DBL_DIG );
}

template < class T >
void
dump( const CHAR_U* Source, INT_4U NData )
{
    const T* data( reinterpret_cast< const T* >( Source ) );

    for ( INT_4U x = 0; x < NData; ++x, ++data )
    {
        if ( x )
        {
            std::cout << ", ";
        }
        std::cout << *data;
    }
}

void
dump_raw( data_type Data, INT_8U Size )
{
    std::ostringstream line;

    INT_8U                  cur( 0 );
    data_const_pointer_type d( Data.get( ) );
    bool                    need_nl( false );

    while ( cur != Size )
    {
        ++cur;
        if ( ( cur % 32 ) == 0 )
        {
            if ( need_nl )
            {
                std::cout << std::endl;
            }
            std::cout << line.str( );
            line.str( "" );
            need_nl = true;
        }
        if ( ( cur % 4 ) == 0 )
        {
            line << " ";
        }
        line << std::setfill( '0' ) << std::setw( 2 ) << std::hex
             << ( INT_2U )( *d );
        ++d;
    }
    if ( line.str( ).empty( ) == false )
    {
        if ( need_nl )
        {
            std::cout << std::endl;
        }
        std::cout << line.str( );
    }
}

//-----------------------------------------------------------------------
/// \brief Class to handle command line options for this application
//-----------------------------------------------------------------------
class CommandLine : protected CommandLineOptions
{
public:
    typedef std::list< std::string > channel_container_type;

    CommandLine( int ArgC, char** ArgV );

    inline bool
    BadOption( ) const
    {
        bool retval = false;

        for ( const_iterator cur = begin( ), last = end( ); cur != last; ++cur )
        {
            if ( ( *cur )[ 0 ] == '-' )
            {
                std::cerr << "ABORT: Bad option: " << *cur << std::endl;
                retval = true;
            }
        }
        return retval;
    }

    inline size_t
    BlockSize( ) const
    {
        return bs;
    }

    inline const channel_container_type&
    Channels( ) const
    {
        return m_channels;
    }

    inline bool
    ListChannels( ) const
    {
        return list_channels;
    }

    inline bool
    Raw( ) const
    {
        return m_raw;
    }

    inline bool
    SilentData( ) const
    {
        return silent_data;
    }

    inline bool
    UseMemoryMappedIO( ) const
    {
        return m_use_memory_mapped_io;
    }

    inline bool
    UseTOCOptimization( ) const
    {
        return toc_optimization;
    }

    inline void
    Usage( int ExitValue ) const
    {
        std::cout << "Usage: " << ProgramName( ) << m_options << std::endl;
        depart( ExitValue );
    }

    using CommandLineOptions::empty;
    using CommandLineOptions::Pop;
    using CommandLineOptions::size;

private:
    enum option_types
    {
        OPT_BLOCK_SIZE,
        OPT_CHANNEL,
        OPT_HELP,
        OPT_LIST_CHANNELS,
        OPT_RAW,
        OPT_SILENT_DATA,
        OPT_TOC_OPT,
        OPT_USE_MEMORY_MAPPED_IO
    };

    size_t                 bs;
    OptionSet              m_options;
    channel_container_type m_channels;
    bool                   list_channels;
    bool                   m_raw;
    bool                   m_use_memory_mapped_io;
    bool                   silent_data;
    bool                   toc_optimization;
};

CommandLine::CommandLine( int ArgC, char** ArgV )
    : CommandLineOptions( ArgC, ArgV ),
      bs( IFrameFStreamPlan::BUFFER_SIZE / 1024 ), list_channels( false ),
      m_raw( false ), m_use_memory_mapped_io( false ), silent_data( false ),
      toc_optimization( false )
{
    //---------------------------------------------------------------------
    // Setup the options that will be recognized.
    //---------------------------------------------------------------------
    std::ostringstream desc;

    m_options.Synopsis( "[options] <file> [<file> ...]" );

    m_options.Summary(
        "This command will dump the contents of the selected channels"
        " for each of the requested files." );

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    StandardOption< STANDARD_OPTION_BLOCK_SIZE >(
        m_options, OPT_BLOCK_SIZE, bs );
    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    desc.str( "" );
    desc << "Specify a channel.";

    m_options.Add( Option(
        OPT_CHANNEL, "channel", Option::ARG_REQUIRED, desc.str( ), "string" ) );

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    StandardOption< STANDARD_OPTION_HELP >( m_options, OPT_HELP );

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    m_options.Add( Option( OPT_LIST_CHANNELS,
                           "list-channels",
                           Option::ARG_NONE,
                           "Display a list of channels." ) );

    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    desc.str( "" );
    desc << "Dumps the data without decompressing."
            " (Default: "
         << ( ( m_raw ) ? "enabled" : "disabled" ) << " )";

    m_options.Add( Option( OPT_RAW, "raw", Option::ARG_NONE, desc.str( ) ) );

    StandardOption< STANDARD_OPTION_SILENT_DATA >(
        m_options, OPT_SILENT_DATA, false );

    StandardOption< STANDARD_OPTION_TOC_OPT >( m_options, OPT_TOC_OPT, false );

    StandardOption< STANDARD_OPTION_USE_MMAP >(
        m_options, OPT_USE_MEMORY_MAPPED_IO, false );
    //---------------------------------------------------------------------
    // Parse the options specified on the command line
    //---------------------------------------------------------------------

    try
    {
        std::string arg_name;
        std::string arg_value;
        bool        parsing = true;

        while ( parsing )
        {
            const int cmd_id( Parse( m_options, arg_name, arg_value ) );

            switch ( cmd_id )
            {
            case CommandLineOptions::OPT_END_OF_OPTIONS:
                parsing = false;
                break;
            case OPT_HELP:
            {
                Usage( 0 );
            }
            break;
            case OPT_BLOCK_SIZE:
            {
                std::istringstream bs_buffer( arg_value );

                bs_buffer >> bs;
            }
            break;
            case OPT_CHANNEL:
                m_channels.push_back( arg_value );
                break;
            case OPT_LIST_CHANNELS:
                list_channels = true;
                break;
            case OPT_RAW:
                m_raw = true;
                break;
            case OPT_SILENT_DATA:
                silent_data = true;
                break;
            case OPT_TOC_OPT:
                toc_optimization = true;
                break;
            case OPT_USE_MEMORY_MAPPED_IO:
                m_use_memory_mapped_io = true;
                break;
            }
        }
    }
    catch ( ... )
    {
    }
}

//=======================================================================
// Main entry point into the application
//=======================================================================
int
main( int ArgC, char* ArgV[] )
try
{
    MemChecker::Trigger gc_trigger( true );
    CommandLine         cl( ArgC, ArgV );

    if ( cl.empty( ) || cl.BadOption( ) )
    {
        cl.Usage( 1 );
    }

    FrameCPP::Initialize( );

    IFrameFStreamPlan::seed_plan_type seed_plan;
    IFrameFStreamPlan::seed_plan_type ifs;

    while ( cl.empty( ) == false )
    {
        const CommandLineOptions::option_type filename = cl.Pop( );
        //-------------------------------------------------------------------
        // Creation of the frame structure by reading of frame file
        //-------------------------------------------------------------------
        ifs.reset( new IFrameFStreamPlan::seed_plan_type::element_type(
            seed_plan,
            filename.c_str( ),
            cl.UseMemoryMappedIO( ),
            cl.BlockSize( ) * 1024 ) );

        std::cout << "Frame file: " << filename << std::endl;
        //-------------------------------------------------------------------
        //
        //-------------------------------------------------------------------
        if ( cl.ListChannels( ) )
        {
            //-----------------------------------------------------------------
            // Get the channels from the table of contents
            //-----------------------------------------------------------------
            const FrameCPP::FrTOC&   toc( ifs->GetTOC( ) );
            channel_name_constructor cn;

            toc.ForEach( FrameCPP::Common::FrTOC::TOC_CHANNEL_NAMES, cn );
            //-----------------------------------------------------------------
            // Make it easier on output by sorting the channel names
            //-----------------------------------------------------------------
            cn.sort( );
            //-----------------------------------------------------------------
            // List the channels
            //-----------------------------------------------------------------
            for ( channel_name_constructor::const_iterator cur = cn.begin( ),
                                                           last = cn.end( );
                  cur != last;
                  ++cur )
            {
                std::cout << "\t" << *cur << std::endl;
            }
        }
        else
        {
            for ( CommandLine::channel_container_type::const_iterator
                      cur = cl.Channels( ).begin( ),
                      last = cl.Channels( ).end( );
                  cur != last;
                  ++cur )
            {
                std::cout << "DEBUG: Processing channel: " << *cur << std::endl;
                //-----------------------------------------------------------------
                // FrAdcData
                //-----------------------------------------------------------------
                {
                    boost::shared_ptr< FrAdcData > channel;

                    try
                    {
                        std::cout << "DEBUG: Trying as ADC" << std::endl;
                        channel = ifs->ReadFrAdcData( 0, *cur );
                        std::cout << "DEBUG: Is a ADC" << std::endl;
                    }
                    catch ( const std::exception& E )
                    {
                        std::cerr << "DEBUG: FrAdcData exception: " << E.what( )
                                  << std::endl;
                    }
                    catch ( ... )
                    {
                    }
                    if ( channel )
                    {
                        dump_vect( channel->RefData( )[ 0 ], cl );
                        continue;
                    }
                }
                //-----------------------------------------------------------------
                // FrProcData
                //-----------------------------------------------------------------
                {
                    boost::shared_ptr< FrProcData > channel;

                    try
                    {
                        std::cout << "DEBUG: Trying as Proc" << std::endl;
                        channel = ifs->ReadFrProcData( 0, *cur );
                        std::cout << "DEBUG: Is a Proc" << std::endl;
                    }
                    catch ( const std::exception& E )
                    {
                        std::cerr
                            << "DEBUG: FrProcData exception: " << E.what( )
                            << std::endl;
                    }
                    catch ( ... )
                    {
                    }
                    if ( channel )
                    {
                        dump_vect( channel->RefData( )[ 0 ], cl );
                        continue;
                    }
                }
                //-----------------------------------------------------------------
                // FrEvent
                //-----------------------------------------------------------------
                {
                    boost::shared_ptr< FrEvent > channel;

                    try
                    {
                        channel = ifs->ReadFrEvent( 0, *cur );
                    }
                    catch ( ... )
                    {
                    }
                    if ( channel )
                    {
                        dump_vect( channel->RefData( )[ 0 ], cl );
                        continue;
                    }
                }
                //-----------------------------------------------------------------
                // FrSerData
                //-----------------------------------------------------------------
                {
                    boost::shared_ptr< FrSerData > channel;

                    try
                    {
                        channel = ifs->ReadFrSerData( 0, *cur );
                    }
                    catch ( ... )
                    {
                    }
                    if ( channel )
                    {
                        dump_vect( channel->RefSerial( )[ 0 ], cl );
                        continue;
                    }
                }
                //-----------------------------------------------------------------
                // FrSimData
                //-----------------------------------------------------------------
                {
                    boost::shared_ptr< FrSimData > channel;

                    try
                    {
                        channel = ifs->ReadFrSimData( 0, *cur );
                    }
                    catch ( ... )
                    {
                    }
                    if ( channel )
                    {
                        dump_vect( channel->RefData( )[ 0 ], cl );
                        continue;
                    }
                }
                //-----------------------------------------------------------------
                // FrSimEvent
                //-----------------------------------------------------------------
                {
                    boost::shared_ptr< FrSimEvent > channel;

                    try
                    {
                        channel = ifs->ReadFrSimEvent( 0, *cur );
                    }
                    catch ( ... )
                    {
                    }
                    if ( channel )
                    {
                        dump_vect( channel->RefData( )[ 0 ], cl );
                        continue;
                    }
                }
                //-----------------------------------------------------------------
                // Case where the channel was not found
                //-----------------------------------------------------------------
                std::cout << "DEBUG: Unknown channel: " << *cur << std::endl;
                if ( exit_code == EXIT_CODE_OK )
                {
                    exit_code = EXIT_CODE_UNKNOWN_CHANNEL;
                }
            }
            seed_plan = ifs;
        }
    }
    //---------------------------------------------------------------------
    // Exit
    //---------------------------------------------------------------------
    depart( exit_code );
}
catch ( const std::exception& Except )
{
    std::cerr << "ABORT: Exception: " << Except.what( ) << std::endl;
    depart( EXIT_CODE_UNCAUGHT_EXCEPTION );
}
catch ( ... )
{
    depart( EXIT_CODE_UNCAUGHT_EXCEPTION );
}

void
dump_vect( vector_type Vect, const CommandLine& Options )
{
    if ( !Vect )
    {
        return;
    }
    std::cout << "Name:     " << Vect->GetName( ) << std::endl
              << "Compress: " << Vect->GetCompress( ) << " (Endianness: "
              << ( ( Vect->GetCompress( ) & 0x100 ) ? "Little" : "Big" )
              << " Scheme: " << ( Vect->GetCompress( ) & 0xFF ) << ")"
              << std::endl
              << "Type:     " << Vect->GetType( ) << std::endl
              << "NData:    " << Vect->GetNData( ) << std::endl
              << "NBytes:   " << Vect->GetNBytes( ) << std::endl;
    std::cout << "Data: ";
    if ( Options.SilentData( ) )
    {
        std::cout << "..." << std::endl;
    }
    else if ( Options.Raw( ) )
    {
        dump_raw( Vect->GetDataRaw( ), Vect->GetNBytes( ) );
    }
    else
    {
        try
        {
            FrVect::data_type expanded;

            switch ( Vect->GetType( ) )
            {
            case FrVect::FR_VECT_C:
                dump< CHAR >( Vect->GetDataUncompressed( expanded ),
                              Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_1U:
                dump< CHAR_U >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_2U:
                dump< INT_2U >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_2S:
                dump< INT_2S >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_4U:
                dump< INT_4U >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_4S:
                dump< INT_4S >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_8U:
                dump< INT_8U >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_8S:
                dump< INT_8S >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_4R:
                dump< REAL_4 >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_8R:
                dump< REAL_8 >( Vect->GetDataUncompressed( expanded ),
                                Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_8C:
                dump< COMPLEX_8 >( Vect->GetDataUncompressed( expanded ),
                                   Vect->GetNData( ) );
                break;
            case FrVect::FR_VECT_16C:
                dump< COMPLEX_16 >( Vect->GetDataUncompressed( expanded ),
                                    Vect->GetNData( ) );
                break;
            }
        }
        catch ( const std::exception& Exception )
        {
            std::cerr << "ERROR: " << Exception.what( ) << std::endl;
            exit_code = EXIT_CODE_VECTOR_EXPANSION;
        }
    }
    std::cout << std::endl;
    // ------------------------------------------------------------------
    // Dump the dataValid information
    // ------------------------------------------------------------------
    std::cout << "dataValid Compress: " << Vect->GetDataValidCompress( )
              << " (Endianness: "
              << ( ( Vect->GetDataValidCompress( ) & 0x100 ) ? "Little"
                                                             : "Big" )
              << " Scheme: " << ( Vect->GetDataValidCompress( ) & 0xFF ) << ")"
              << std::endl
              << "NDataValid:    " << Vect->GetNDataValid( ) << std::endl
              << "NDataValidBytes:   " << Vect->GetNDataValidBytes( )
              << std::endl;
    std::cout << "dataValid Data: ";
    if ( Options.SilentData( ) )
    {
        std::cout << "..." << std::endl;
    }
    else if ( Options.Raw( ) )
    {
        dump_raw( Vect->GetDataValidRaw( ), Vect->GetNDataValidBytes( ) );
    }
    else
    {
        try
        {
            FrameCPP::FrVect::dataValid_type expanded;

            dump< CHAR_U >( Vect->GetDataUncompressed( expanded ),
                            Vect->GetNDataValid( ) );
        }
        catch ( const std::exception& Exception )
        {
            std::cerr << "ERROR: " << Exception.what( ) << std::endl;
            exit_code = EXIT_CODE_VECTOR_EXPANSION;
        }
    }
    std::cout << std::endl;
}

channel_name_constructor::~channel_name_constructor( )
{
}

void
channel_name_constructor::operator( )( const std::string& ChannelName )
{
    push_back( ChannelName );
}
