//
// 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>

#define NOTGDS
//
//    DMT base class methods.
//

//-------------------------------------  Header files.
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
#include <streambuf.h>
#ifndef NOTGDS
#include "iSMbuf.hh"
#include "SigFlag.hh"
#endif
//
//    Base class for the Data Monitor processing routines!
//

#define MaxID 256

struct IGWhdr
{
    int   len; // note that the signed int is a cheat.
    short id;
    short inst;
};

/**  Frame processor base class for data monitors.
 */
class FrTest
{
public:
    FrTest( int argc, const char* argv[] );
    virtual ~FrTest( );
    void ProcessFrame( istream& In );
    void MainLoop( void );
    void
    finish( void )
    {
        mActive = false;
    }
    int
    Debug( void ) const
    {
        return mDebug;
    }
    int
    getNFrames( void ) const
    {
        return mNFrames;
    }
    void Dump( const IGWhdr& h, istream& in );
    bool procSH( const IGWhdr& h, istream& in );

private:
    bool mActive;
#ifndef NOTGDS
    SigFlag mTerm;
#else
    bool mTerm;
#endif
    streambuf* mBuffer;
    istream*   mStream;
    int        mDebug;
    int        mNFrames;
    bool       mDumpID[ MaxID ];
    bool       mDefID[ MaxID ];
    int        mEOFrID;
    int        mEOFID;
};

//-------------------------------------  FrTest main function.
int
main( int argc, const char* argv[] )
{
    FrTest MyObject( argc, argv );
    MyObject.MainLoop( );
    return 0;
}

//-------------------------------------  FrTest constructor.
FrTest::FrTest( int argc, const char* argv[] )
    : mActive( false ), mBuffer( 0 ), mStream( 0 ), mDebug( 0 ), mNFrames( 0 )
{
#ifndef NOTGDS
    const char* partition = getenv( "LIGOSMPART" );
    if ( !partition )
        partition = "LIGO_Online";
#endif
    const char* file = (const char*)0;
    for ( int i = 0; i < MaxID; i++ )
        mDumpID[ i ] = false;

    //---------------------------------  Parse the arguments
    for ( int i = 1; i < argc && argv; i++ )
    {
        if ( !strcmp( "-infile", argv[ i ] ) )
        {
            file = argv[ ++i ];
#ifndef NOTGDS
        }
        else if ( !strcmp( "-partition", argv[ i ] ) )
        {
            partition = argv[ ++i ];
#endif
        }
        else if ( !strcmp( "-debug", argv[ i ] ) )
        {
            if ( ++i == argc )
                mDebug = 1;
            else
                mDebug = strtol( argv[ i ], 0, 0 );
        }
        else if ( !strcmp( "-dumpid", argv[ i ] ) )
        {
            mDumpID[ strtol( argv[ ++i ], 0, 0 ) ] = true;
        }
        else
        {
            cerr << "Invalid argument: " << argv[ i ] << endl;
            cerr << "Syntax:" << endl;
            cerr << "FrTest [-infile <file>] [-debug <debug-level>]"
                 << " [-dumpid <struct-id>]" << endl;
#ifndef NOTGDS
            cerr << "       [-partition <part>]" << endl;
#endif
            return;
        }
    }

#ifndef NOTGDS
    //---------------------------------  Handle signals
    mTerm.add( SIGTERM );
    mTerm.add( SIGHUP );
    mTerm.add( SIGINT );
#else
    mTerm = false;
#endif

    //---------------------------------  Open a frame stream (FrameCPP)
    if ( file )
    {
        filebuf* fbuf = new filebuf;
        if ( fbuf->open( (char*)file, ios::in ) )
        {
            cout << "FrTest: Opened file " << file << " for frame input."
                 << endl;
        }
        else
        {
            cerr << "FrTest: Unable to open file " << file
                 << " for frame input." << endl;
            return;
        }
        mBuffer = fbuf;
    }
    else
    {
#ifdef NOTGDS
        cerr << "FrTest: No file specified with -infile " << endl;
#else
        iSMbuf* sbuf = new iSMbuf;
        if ( sbuf->open( (char*)partition, ios::in ) )
        {
            cout << "FrTest: Opened partition " << partition
                 << " for frame input." << endl;
        }
        else
        {
            cerr << "FrTest: Unable to access partiton " << partition
                 << " for frame input." << endl;
            return;
        }
        mBuffer = sbuf;
#endif
    }
    mStream = new istream( mBuffer );
    mActive = true;
}

//-------------------------------------  FrTest object destructor.
FrTest::~FrTest( void )
{
    //---------------------------------  Close the Input file/partition.
    if ( mStream )
        delete mStream;
    if ( mBuffer )
        delete mBuffer;

    //---------------------------------  Print statistics
    if ( mDebug != 999 )
    {
        cout << "Frame reading terminated after " << mNFrames << " frames."
             << endl;
    }
}

//-------------------------------------  Main processing loop function.
void
FrTest::MainLoop( void )
{
    //---------------------------------  Loop until terminated.
    while ( mActive && !mTerm )
    {
        //-----------------------------  Process a frame.
        mNFrames++;
        ProcessFrame( *mStream );
    }
}

void
FrTest::ProcessFrame( istream& In )
{
    char   igwd[ 4 ];
    IGWhdr h;

    //---------------------------------  Check the IGWD specifier
    if ( In.read( igwd, sizeof( igwd ) ).gcount( ) < sizeof( igwd ) )
    {
        finish( );
        return;
    }

    if ( strncmp( "IGWD", igwd, 4 ) )
    {
        cout << "Data isn't IGWD format" << endl;
        finish( );
        return;
    }
    In.ignore( 36 );

    //---------------------------------  Initialize the instance counts.
    int strinst[ MaxID ];
    for ( int i = 0; i < MaxID; i++ )
    {
        strinst[ i ] = 0;
        mDefID[ i ] = false;
    }
    mDefID[ 1 ] = true;
    mDefID[ 2 ] = true;

    //---------------------------------  Loop over structures
    while ( !mTerm )
    {
        In.read( &h, sizeof( h ) );
        if ( h.len < 8 )
        {
            cout << "Illegal structure length " << h.len << endl;
            finish( );
            return;
        }

        if ( h.id <= 0 || h.id >= MaxID || !mDefID[ h.id ] )
        {
            cout << "Illegal or undefined structure ID " << h.id << endl;
            finish( );
            return;
        }

        if ( h.inst != strinst[ h.id ]++ )
        {
            if ( h.inst == 1 && strinst[ h.id ] == 1 )
            {
                strinst[ h.id ]++;
            }
            else
            {
                cout << "Illegal structure instance " << h.inst << " of ID "
                     << h.id << ", expected " << strinst[ h.id ] - 1 << endl;
            }
        }

        if ( Debug( ) > 1 )
            cout << "Length, id, instance =" << h.len << " " << h.id << " "
                 << h.inst << endl;
        if ( h.id == 1 )
        {
            if ( procSH( h, In ) )
                return;
        }
        else if ( mDumpID[ h.id ] )
        {
            Dump( h, In );
        }
        else
        {
            In.ignore( h.len - sizeof( h ) );
        }
        if ( h.id == mEOFrID )
        {
            for ( int i = 0; i < MaxID; i++ )
            {
                strinst[ i ] = 0;
            }
        }
        if ( h.id == mEOFID )
            break;
    }
    if ( mDebug )
        cout << "End of Frame " << getNFrames( ) << endl;
}

#define LSTRUCTNAME 32
bool
FrTest::procSH( const IGWhdr& h, istream& In )
{
    char           name[ LSTRUCTNAME ];
    char*          comment;
    unsigned short lname, ID, lcomment;
    In.read( &lname, sizeof( short ) );
    if ( ( lname < 2 ) || ( lname > h.len - sizeof( IGWhdr ) - 6 ) )
    {
        cout << "Inconsistent name length in SH, l=" << lname << endl;
        In.ignore( h.len - sizeof( IGWhdr ) - 2 );
        return true;
    }
    In.read( name, lname );

    In.read( &ID, sizeof( ID ) );

    In.read( &lcomment, sizeof( short ) );
    if ( lcomment != h.len - sizeof( IGWhdr ) - lname - 6 )
    {
        cout << "Inconsistent comment length in SH, l=" << lname << endl;
        In.ignore( h.len - sizeof( IGWhdr ) - 6 - lname );
        return true;
    }
    else if ( mDumpID[ h.id ] )
    {
        cout.form(
            "Structure type SH, Instance %i, Length %i\n", h.inst, h.len );
        comment = new char[ lcomment ];
        In.read( comment, lcomment );
        cout.form( "  Name:%-20s  ID: %4i Comment: %s\n", name, ID, comment );
        delete[] comment;
    }
    else
    {
        In.ignore( lcomment );
    }
    if ( !ID || ID > MaxID )
    {
        cout << "Illegal structure ID defined" << endl;
        return true;
    }
    mDefID[ ID ] = true;
    if ( !strcmp( name, "FrEndOfFile" ) )
        mEOFID = ID;
    if ( !strcmp( name, "FrEndOfFrame" ) )
        mEOFrID = ID;
    return false;
}

#define TEXTLEN 16
void
FrTest::Dump( const IGWhdr& h, istream& In )
{
    char string[ TEXTLEN + 1 ];

    //-----------------------------  Read in a frame
    int  nbyt = h.len - sizeof( h );
    int  nwd = ( nbyt + sizeof( int ) - 1 ) / sizeof( int );
    int* buffer = new int[ nwd ];
    if ( !buffer )
        return;
    buffer[ nwd - 1 ] = 0;
    In.read( buffer, nbyt );

    //-----------------------------  Dump the frame to stdout.
    cout.form(
        "Structure type %i, instance %i, Length %i\n", h.id, h.inst, h.len );
    for ( int i = 0; i < nwd; i += 4 )
    {
        for ( int j = 0; j < TEXTLEN; j++ )
        {
            char tchar = *( (char*)( buffer + i ) + j );
            if ( tchar >= 'a' && tchar <= 'z' )
                string[ j ] = tchar;
            else if ( tchar >= 'A' && tchar <= 'Z' )
                string[ j ] = tchar;
            else if ( tchar >= '0' && tchar <= '9' )
                string[ j ] = tchar;
            else if ( tchar == ' ' || tchar == '_' )
                string[ j ] = tchar;
            else if ( tchar == ':' || tchar == '-' )
                string[ j ] = tchar;
            else
                string[ j ] = '.';
        }
        string[ TEXTLEN ] = 0;
        cout.form( "%8x  %08x %08x %08x %08x |%s|\n",
                   i * sizeof( int ),
                   buffer[ i ],
                   buffer[ i + 1 ],
                   buffer[ i + 2 ],
                   buffer[ i + 3 ],
                   string );
    }
    delete[] buffer;
}
