/* dumpprocessor.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2021 Askar Almatov

    This file is part of Trafficmeter.

    Trafficmeter is free software: you can redistribute it and/or modify it under the terms of the GNU General
    Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.

    Trafficmeter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License along with Trafficmeter. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <libiclass/time>

#include "dumpprocessor.h"
#include "endian64.h"
#include "source.h"

using namespace iclass;
using namespace trafficmeter;

using std::cout;
using std::dec;
using std::endl;
using std::hex;
using std::invalid_argument;
using std::setfill;
using std::setw;
using std::string;

struct DumpProcessor::Pattern_
{
    uint64_t    fragment;
    int         nBits;
    int         offset;
    bool        hasInversion;
};

/**************************************************************************************************************/
DumpProcessor::DumpProcessor( Source* source, int nPacketsPrint, int nBytesPrint ) :
    source_( source ),
    nPacketsPrint_( nPacketsPrint ),
    nBytesPrint_( nBytesPrint )
{
}

/**************************************************************************************************************/
DumpProcessor::~DumpProcessor()
{
}

/**************************************************************************************************************/
void
DumpProcessor::addPattern( const string& patternString )
{
    const char*     token = patternString.data();
    char*           tokenEnd;
    bool            hasInversion = ( *token == '^' );
    int             offset = strtol( token + (hasInversion?1:0), &tokenEnd, 10 );

    if ( offset <= 0 || *tokenEnd != 'x' )
    {
        throw invalid_argument( "invalid pattern format" );
    }

    token = tokenEnd + 1;

    uint64_t    fragment = strtoull( token, &tokenEnd, 16 );
    int         nBits = ( tokenEnd - token ) << 2;

    if ( *tokenEnd != '\0' )
    {
        throw invalid_argument( "invalid pattern fragment" );
    }

    if ( nBits > (sizeof(fragment) << 3) )
    {
        throw invalid_argument( "pattern fragment too large" );
    }

    patterns_.push_back( {fragment, nBits, offset, hasInversion} );
}

/**************************************************************************************************************/
bool
DumpProcessor::isContainsPatterns( const Source::Packet& packet ) const
{
    if ( !patterns_.empty() )
    {
        for ( const Pattern_& pattern : patterns_ )
        {
            if ( pattern.offset + ((pattern.nBits+7) >> 3) > packet.nBytes2 )
            {
                return false;
            }

            uint64_t   fragment = be64toh( *reinterpret_cast<const uint64_t*>(packet.data2 + pattern.offset) );

            fragment >>= ( sizeof(fragment) << 3 ) - pattern.nBits;

            if ( !((fragment==pattern.fragment) ^ pattern.hasInversion) )
            {
                return false;
            }
        }
    }

    return true;
}

/**************************************************************************************************************/
void
DumpProcessor::routine()
{
    source_->open();

    while ( !stopped() && nPacketsPrint_ > 0 )
    {
        if ( !source_->checkForRecords() )
        {
            continue;
        }

        auto&&  packet = source_->getPacket();

        if ( packet.isValid() && isContainsPatterns(packet) )
        {
            cout <<
                setw(4) << setfill(' ') << packet.nBytes2 << "  " <<
                ltimet(packet.timestamp.sec()).str().substr(11, 8) << "." <<
                setw(6) << setfill('0') << packet.timestamp.usec() << "  ";

            for ( int i = 0; i < packet.nBytes2 && i < nBytesPrint_; ++i )
            {
                cout << hex << setw(2) << setfill('0') << static_cast<unsigned>( packet.data2[i] );
            }

            cout << dec << endl;
            --nPacketsPrint_;
        }
    }

    source_->close();
}
