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

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <string>

#include "ldastoolsal/fstream.hh"
#include "ldastoolsal/util.hh"

#include "framecpp/Version6/FrCommon.hh"
#include "framecpp/Version6/FrameH.hh"
#include "framecpp/Version6/FrDetector.hh"
#include "framecpp/Version6/FrEndOfFile.hh"
#include "framecpp/Version6/FrEndOfFrame.hh"
#include "framecpp/Version6/FrTOC.hh"
#include "framecpp/Version6/Functions.hh"
#include "framecpp/Version6/IFrameStream.hh"
#include "framecpp/Version6/Util.hh"

using namespace FrameCPP::Version_6;

#include "DumpObjects6.cc"

namespace
{
    enum object_type
    {
        ADC,
        HEADER,
        LIST,
        PROC,
        ANY,
        SIM,
        SER,
        EVENT,
        SIM_EVENT
    };

    struct request_typearget_type
    {
        enum
        {
            NAME,
            INDEX
        } type;
        std::string name;
        INT_4U      index;

        request_typearget_type( const std::string& Target )
            : type( NAME ), name( Target )
        {
            if ( Target.length( ) > 0 )
            {
                std::istringstream iss( Target );
                std::string        junk;
                iss >> index;
                if ( iss.good( ) )
                {
                    iss >> junk;
                    if ( junk.length( ) == 0 )
                    {
                        type = INDEX;
                    }
                }
            }
        }
    };

    template < class T >
    void
    list_names( const T& DataSet, bool Alphabetical )
    {
        typename T::const_iterator begin( DataSet.begin( ) );
        typename T::const_iterator end( DataSet.end( ) );

        std::list< std::string > names;

        for ( typename T::const_iterator o( begin ); o != end; o++ )
        {
            names.push_back( ( *o ).first );
        }
        if ( Alphabetical )
        {
            names.sort( );
        }
        for ( std::list< std::string >::const_iterator i( names.begin( ) );
              i != names.end( );
              i++ )
        {
            std::cout << ( *i ) << std::endl;
        }
    }

    class DumpCB_type : public Version6::DumpFrStruct,
                        public Dictionary::Callback
    {
    public:
        DumpCB_type( )
            : Version6::DumpFrStruct( true, // Verbose mode
                                      false, // Scientific output
                                      Version6::DumpFrStruct::OUTPUT_MODE_DUMP )
        {
        }

        virtual void inline
        operator( )( FrBase& Base )
        {
            Dump( Base );
        }
    };

} // namespace

using namespace FrameCPP::Version_6;

char* Program;

void
usage( )
{
    std::cerr
        << "Usage: " << Program << " ["
        << " [-H ]"
        << " [-a <adc name>]"
        << " [-e]"
        << " [-f <frame index>]"
        << " [-l (adc|proc|ser|sim|all|any)]"
        << " [-p <proc name>]"
        << " [-d [<type>%]<name>]"
        << " [-s]"
        << " ... ]"
        << " frame_file [ frame_file ... ]" << std::endl
        << "\t-H - Dump the Header block" << std::endl
        << "\t-a - Dump the named adc" << std::endl
        << "\t-e - Print real numbers in scientific notation" << std::endl
        << "\t-f - Dump a range of frames" << std::endl
        << "\t-l - Dump a list of adc, procs, serial, or simulation data"
        << std::endl
        << "\t-d - Dump the specified named object called <name> of type <type>"
        << std::endl
        << "\t-p - Dump the named FrProc structure" << std::endl
        << "\t-s - Do not dump the data element of FrVect structures"
        << std::endl;
}

object_type
convert_to_type( const std::string& Type )
{
    object_type retval = ANY;

    if ( LDASTools::AL::cmp_nocase( Type, "adc" ) == 0 )
    {
        retval = ADC;
    }
    else if ( LDASTools::AL::cmp_nocase( Type, "event" ) == 0 )
    {
        retval = EVENT;
    }
    else if ( LDASTools::AL::cmp_nocase( Type, "proc" ) == 0 )
    {
        retval = PROC;
    }
    else if ( LDASTools::AL::cmp_nocase( Type, "ser" ) == 0 )
    {
        retval = SER;
    }
    else if ( LDASTools::AL::cmp_nocase( Type, "sim" ) == 0 )
    {
        retval = SIM;
    }
    else if ( LDASTools::AL::cmp_nocase( Type, "simevent" ) == 0 )
    {
        retval = SIM_EVENT;
    }
    return retval;
}

int
main( int ArgC, char** ArgV )
try
{
    typedef std::pair< object_type, request_typearget_type > request_type;
    typedef std::pair< INT_4U, INT_4U >                      range_info_type;
    typedef std::vector< range_info_type >                   range_type;

    int        exit_status = 0;
    range_type range;
    bool       scientific = false;
    bool       verbose = true;

    Program = ArgV[ 0 ];

    int opt;

    std::list< request_type > request;

    while ( ( opt = getopt( ArgC, ArgV, "a:d:f:l:p:eHs" ) ) != -1 )
    {
        switch ( opt )
        {
        case 'H':
            request.push_back(
                request_type( HEADER, request_typearget_type( "" ) ) );
            break;
        case 'a':
            request.push_back(
                request_type( ADC, request_typearget_type( optarg ) ) );
            break;
        case 'd':
        {
            std::string            name( optarg );
            std::string            prefix;
            std::string::size_type pos = name.find_first_of( "%" );

            if ( pos != std::string::npos )
            {
                prefix = name.substr( 0, pos );
                ++pos;
                name.replace( 0, pos, "" );
            }
            request.push_back( request_type( convert_to_type( prefix ),
                                             request_typearget_type( name ) ) );
        }
        break;
        case 'e':
            scientific = true;
            break;
        case 'f':
        {
            std::istringstream iss( optarg );
            INT_4U             first, last;
            char               c;

            iss >> first;
            if ( iss.eof( ) )
            {
                last = first;
            }
            else
            {
                iss >> c >> last;
            }
            range.push_back( range_info_type( first, ++last ) );
        }
        break;
        case 'l':
            request.push_back(
                request_type( LIST, request_typearget_type( optarg ) ) );
            break;
        case 'p':
            request.push_back(
                request_type( PROC, request_typearget_type( optarg ) ) );
            break;
        case 's':
            verbose = false;
            break;
        default:
            break;
        }
    }

    ArgC -= optind;
    ArgV += optind;

    if ( ArgC < 1 )
    {
        usage( );
        exit( 1 );
    }
    for ( int x = 0; x < ArgC; x++ )
    {
        DumpCB_type cb;
        cb.SetVerbosity( verbose );
        cb.SetScientific( scientific );
        LDASTools::AL::ifstream ifile( ArgV[ x ] );
        if ( !ifile.good( ) )
        {
            std::cout << "Unable to open file: " << ArgV[ x ] << std::endl;
            exit_status = 1;
            continue;
        }
        IFrameStream frame_stream( ifile );
        std::cout << "Frame Version: " << frame_stream.GetVersion( )
                  << " Library Revision: " << frame_stream.GetLibraryRevision( )
                  << " Originator: " << frame_stream.GetOriginator( )
                  << std::endl;
        if ( request.size( ) == 0 )
        {
            try
            {
                if ( range.size( ) == 0 )
                {
                    //-------------------------------------------------------------
                    // Read
                    //-------------------------------------------------------------
                    std::unique_ptr< FrBase > frame_base(
                        frame_stream.ReadNextFrame( ) );

                    while ( frame_base.get( ) != (FrBase*)NULL )
                    {
                        FrameH* frame(
                            dynamic_cast< FrameH* >( frame_base.get( ) ) );
                        if ( frame )
                        {
                            cb( *frame );
                        }
                        frame_base.reset( frame_stream.ReadNextFrame( ) );
                    }
                }
                else
                {
                    //-------------------------------------------------------------
                    // Read specific frames from the frame file
                    //-------------------------------------------------------------
                    for ( range_type::const_iterator
                              current_range = range.begin( ),
                              end_range = range.end( );
                          current_range != end_range;
                          current_range++ )
                    {
                        for ( INT_4U current_frame( ( *current_range ).first ),
                              end_frame( ( *current_range ).second );
                              current_frame <= end_frame;
                              current_frame++ )
                        {
                            std::cout << "Frame Offset: " << current_frame
                                      << std::endl;

                            std::unique_ptr< FrBase > frame_base(
                                frame_stream.ReadFullFrame( current_frame ) );

                            FrameH* frame(
                                dynamic_cast< FrameH* >( frame_base.get( ) ) );
                            if ( frame )
                            {
                                cb( *frame );
                            }
                        }
                    }
                }
            }
            catch ( const std::exception& e )
            {
                std::cerr << "Caught exception while processing file: "
                          << e.what( ) << std::endl;
            }
        }
        else
        {
            bool patched_range( false );
            if ( range.size( ) == 0 )
            {
                range.push_back(
                    range_info_type( 0, frame_stream.GetNumberOfFrames( ) ) );
                patched_range = true;
            }
            std::unique_ptr< FrBase > object;
            int                       action;
            for ( range_type::const_iterator current_range = range.begin( ),
                                             end_range = range.end( );
                  current_range != end_range;
                  ++current_range )
            {
                for ( INT_4U current_frame( ( *current_range ).first ),
                      end_frame( ( *current_range ).second );
                      current_frame < end_frame;
                      ++current_frame )
                {
                    std::cout << "Frame Offset: " << current_frame << std::endl;
                    for ( std::list< request_type >::const_iterator r(
                              request.begin( ) );
                          r != request.end( );
                          r++ )
                    {
                        int                       outputs = 0;
                        std::list< request_type > dump_objects;

                        object.reset( (FrBase*)NULL );
                        action = ( *r ).first;
                        if ( action == ANY )
                        {
                            dump_objects.push_back(
                                request_type( ADC, ( *r ).second ) );
                            dump_objects.push_back(
                                request_type( EVENT, ( *r ).second ) );
                            dump_objects.push_back(
                                request_type( PROC, ( *r ).second ) );
                            dump_objects.push_back(
                                request_type( SER, ( *r ).second ) );
                            dump_objects.push_back(
                                request_type( SIM, ( *r ).second ) );
                            dump_objects.push_back(
                                request_type( SIM_EVENT, ( *r ).second ) );
                        }
                        else
                        {
                            dump_objects.push_back( *r );
                        }
                        for ( std::list< request_type >::const_iterator d(
                                  dump_objects.begin( ) );
                              d != dump_objects.end( );
                              d++ )
                        {
                            switch ( ( *d ).first )
                            {
#define READ_OBJECT_BY_NAME( FUNC )                                            \
    if ( ( *d ).second.type == request_typearget_type::NAME )                  \
    {                                                                          \
        object.reset(                                                          \
            frame_stream.FUNC( current_frame, ( *d ).second.name ) );          \
    }

#define READ_OBJECT( FUNC )                                                    \
    READ_OBJECT_BY_NAME( FUNC )                                                \
    else if ( ( *d ).second.type == request_typearget_type::INDEX )            \
    {                                                                          \
        object.reset(                                                          \
            frame_stream.FUNC( current_frame, ( *d ).second.index ) );         \
    }

                            case ADC:
                                READ_OBJECT( ReadFrAdcData );
                                break;
                            case EVENT:
                                READ_OBJECT_BY_NAME( ReadFrEvent );
                                break;
                            case PROC:
                                READ_OBJECT( ReadFrProcData );
                                break;
                            case SER:
                                READ_OBJECT_BY_NAME( ReadFrSerData );
                                break;
                            case SIM:
                                READ_OBJECT_BY_NAME( ReadFrSimData );
                                break;
                            case SIM_EVENT:
                                READ_OBJECT_BY_NAME( ReadFrSimEvent );
                                break;

#undef READ_OBJECT
#undef READ_OBJECT_BY_NAME

                            case HEADER:
                                object.reset( frame_stream.ReadFrameH(
                                    current_frame,
                                    FrameH::DETECT_SIM | FrameH::DETECT_PROC |
                                        FrameH::HISTORY ) );
                                break;
                            case LIST:
                            {
                                ++outputs;
                                const FrTOC* toc( frame_stream.GetTOC( ) );

                                object_type type =
                                    convert_to_type( ( *d ).second.name );

                                if ( ( type == ANY ) || ( type == ADC ) )
                                {
                                    list_names( toc->GetADC( ), true );
                                }
                                if ( ( type == ANY ) || ( type == EVENT ) )
                                {
                                    list_names( toc->GetEventType( ), true );
                                }
                                if ( ( type == ANY ) || ( type == PROC ) )
                                {
                                    list_names( toc->GetProc( ), true );
                                }
                                if ( ( type == ANY ) || ( type == SER ) )
                                {
                                    list_names( toc->GetSer( ), true );
                                }
                                if ( ( type == ANY ) || ( type == SIM ) )
                                {
                                    list_names( toc->GetSim( ), true );
                                }
                                if ( ( type == ANY ) || ( type == SIM_EVENT ) )
                                {
                                    list_names( toc->GetSimEventType( ), true );
                                }
                            }
                            break;
                            default:
                                break;
                            }
                            if ( object.get( ) )
                            {
                                cb( *object );
                                ++outputs;
                            }
                        }
                        if ( outputs == 0 )
                        {
                            std::cerr << "WARNING: Unknown object named: "
                                      << ( *r ).second.name << std::endl;
                        }
                    }
                }
            }
            if ( patched_range )
            {
                // Remove the element that was added
                range.pop_back( );
            }
        }
    }

    request.erase( request.begin( ), request.end( ) );

    exit( exit_status );
}
catch ( std::exception& e )
{
    std::cerr << "ABORT: Caught exception: " << e.what( ) << std::endl;
    exit( 1 );
}
catch ( ... )
{
    std::cerr << "ABORT: Caught unknown exception: " << std::endl;
    exit( 1 );
}
