/* watcher.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2020, 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 <unistd.h>
#include <sys/resource.h>
#include <sys/time.h>

#include "source.h"
#include "watcher.h"

using namespace trafficmeter;

constexpr const int     PHY_ADDENDUM_ETHERNET_ = 20;

/**************************************************************************************************************/
Watcher::Watcher( Source* source ) :
    source_( source ),
    phyAddendum_( source->isEthernet()? PHY_ADDENDUM_ETHERNET_ : 0 ),
    pps_( 0 ),
    ppsMin_( 0 ),
    ppsMax_( 0 ),
    ppsSum_( 0 ),
    bps_( 0 ),
    bpsMin_( 0 ),
    bpsMax_( 0 ),
    bpsSum_( 0 ),
    nPackets2Previous_( 0 ),
    nBytes2Previous_( 0 ),
    nMeasurings_( 0 ),
    startUsec_( -1 ),
    previousUsec_( -1 ),
    measuringDuration_( 0 ),
    nPackets2_a_( 0 ),
    nBytes2_a_( 0 )
{
}

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

/**************************************************************************************************************/
void
Watcher::measure()
{
    waitForSecond_();

    uintmax_t   nPackets2 = nPackets2_a_;
    uintmax_t   nBytes2 = nBytes2_a_;

    pps_ = nPackets2 - nPackets2Previous_;
    bps_ = ( (nBytes2 - nBytes2Previous_) + pps_ * phyAddendum_ ) << 3;
    pps_ = pps_ * 1000000 / measuringDuration_;
    bps_ = bps_ * 1000000 / measuringDuration_;

    if ( nMeasurings_ == 0 )
    {
        ppsMin_ = ppsMax_ = ppsSum_ = pps_;
        bpsMin_ = bpsMax_ = bpsSum_ = bps_;
    }
    else
    {
        if ( ppsMin_ > pps_ )
        {
            ppsMin_ = pps_;
        }

        if ( ppsMax_ < pps_ )
        {
            ppsMax_ = pps_;
        }

        if ( bpsMin_ > bps_ )
        {
            bpsMin_ = bps_;
        }

        if ( bpsMax_ < bps_ )
        {
            bpsMax_ = bps_;
        }

        ppsSum_ += pps_;
        bpsSum_ += bps_;
    }

    nPackets2Previous_ = nPackets2;
    nBytes2Previous_ = nBytes2;
    ++nMeasurings_;
}

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

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

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

        if ( packet.isValid() )
        {
            nPackets2_a_++;
            nBytes2_a_ += packet.nBytes2;
        }
    }

    source_->close();
}

/**************************************************************************************************************/
void
Watcher::waitForSecond_()
{
    struct timeval  tv;

    gettimeofday( &tv, nullptr );

    if ( startUsec_ < 0 )
    {
        usage_.startMeasuring();
        startUsec_ = previousUsec_ = tv.tv_usec;
        nPackets2_a_ = 0;
        nBytes2_a_ = 0;
        usleep( 1000000 );
    }
    else
    {
        usleep( startUsec_ - tv.tv_usec + ((tv.tv_usec<startUsec_)? 0 : 1000000) );
    }

    gettimeofday( &tv, nullptr );
    usage_.continueMeasuring();
    measuringDuration_ = tv.tv_usec - previousUsec_ + 1000000;
    previousUsec_ = tv.tv_usec;

    if ( measuringDuration_ < 500000 )
    {
        measuringDuration_ += 1000000;
    }
    else if ( measuringDuration_ > 1500000 )
    {
        measuringDuration_ -= 1000000;
    }
}
