/* packetcollection.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2015, 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 <cassert>
#include <limits>

#include "packetcollection.h"
#include "packetrecord.h"

using namespace iclass;
using namespace trafficmeter;

using std::numeric_limits;

constexpr const unsigned    QUEUE_RESERVE_ = 500;   // number of buckets

/**************************************************************************************************************/
PacketCollection::PacketCollection( int nChunks ) :
    nSlots_( 1U << SUFFIX_HASH_LENGTH ),
    queueSize_a_( 0 ),
    nBuckets_a_( 0 ),
    slots_c_( nSlots_, nullptr ),
    hasActivity_c_( false )
{
    ring_ = new RingAllocator( nChunks );
    queue_c_ = new Buckets;
    queue_c_->reserve( QUEUE_RESERVE_ );
    oldestGroup_c_ = moduloRoundDown( time(nullptr), PACKET_FACTOR );
    newestGroup_c_ = oldestGroup_c_;
}

/**************************************************************************************************************/
PacketCollection::~PacketCollection()
{
    this->lock();

    for ( Bucket* b : slots_c_ )
    {
        assert( b == nullptr );
    }

    assert( queue_c_->empty() );
    delete queue_c_;
    delete ring_;
}

/**************************************************************************************************************/
void
PacketCollection::push( const Metadata& meta, Packet packet )
{
    int     sSlotNo = meta.sip & ( nSlots_ - 1 );
    int     dSlotNo = meta.dip & ( nSlots_ - 1 );
    time_t  group = moduloRoundDown( packet.timestamp().sec(), PACKET_FACTOR );

    this->lock();
    hasActivity_c_ |= ( group <= oldestGroup_c_ );

    if ( group > newestGroup_c_ )
    {
        newestGroup_c_ = group;
    }

    getBucket_( sSlotNo, group, packet.nBytes2() )->pushBack( packet );

    if ( sSlotNo != dSlotNo )
    {
        getBucket_( dSlotNo, group, packet.nBytes2() )->pushBack( packet );
    }

    this->unlock();
}

/**************************************************************************************************************/
PacketCollection::Buckets*
PacketCollection::pull()
{
    Buckets*    queue = nullptr;

    this->lock();

    if ( queueSize_a_ <= 0 )
    {
        time_t  edge = oldestGroup_c_ + PACKET_FACTOR;
        bool    hasNextTime = newestGroup_c_ > oldestGroup_c_;

        if ( time(nullptr) >= edge && (hasNextTime || !hasActivity_c_) )
        {
            for ( Bucket*& b : slots_c_ )
            {
                if ( b != nullptr && b->oldest < edge )
                {
                    queue_c_->push_back( b );
                    ++queueSize_a_;
                    b = nullptr;
                }
            }

            oldestGroup_c_ += PACKET_FACTOR;
        }

        hasActivity_c_ = false;
    }

    if ( queueSize_a_ > 0 )
    {
        queue = queue_c_;
        queue_c_ = new Buckets;
        queue_c_->reserve( QUEUE_RESERVE_ );
        nBuckets_a_ -= queue->size();
        queueSize_a_ = 0;
    }

    this->unlock();
    return queue;
}

/**************************************************************************************************************/
void
PacketCollection::flush()
{
    this->lock();

    for ( Bucket*& b : slots_c_ )
    {
        if ( b != nullptr )
        {
            queue_c_->push_back( b );
            ++queueSize_a_;
            b = nullptr;
        }
    }

    this->unlock();
}

/**************************************************************************************************************/
long
PacketCollection::nBuckets() const
{
    return nBuckets_a_;
}

/**************************************************************************************************************/
long
PacketCollection::queueSize() const
{
    return queueSize_a_;
}

/**************************************************************************************************************/
PacketCollection::Bucket*
PacketCollection::getBucket_( int slotNo, time_t group, int nBytes2 )
{
    Bucket*     bucket = slots_c_[ slotNo ];

    if ( bucket == nullptr )
    {
        bucket = new( ring_ ) Bucket( slotNo );
        slots_c_[ slotNo ] = bucket;
        ++nBuckets_a_;
    }
    else if ( bucket->oldest < group || bucket->end >= bucket->packets + BUCKET_PACKETS )
    {
        queue_c_->push_back( bucket );
        ++queueSize_a_;
        bucket = new( ring_ ) Bucket( slotNo );
        slots_c_[ slotNo ] = bucket;
        ++nBuckets_a_;
    }

    return bucket;
}

/**************************************************************************************************************/
PacketCollection::Bucket::Bucket( int slotNo ) :
    end( packets ),
    oldest( numeric_limits<time_t>::max() ),
    slotNo( slotNo )
{
}

/**************************************************************************************************************/
void
PacketCollection::Bucket::pushBack( const Packet& packet )
{
    if ( packets == end )
    {
        oldest = packet.timestamp().sec();
    }

    *end++ = packet;
}

/**************************************************************************************************************/
void*
PacketCollection::Bucket::operator new( size_t size, RingAllocator* ring )
{
    return RingAllocator::allocate( ring, size );
}

/**************************************************************************************************************/
void
PacketCollection::Bucket::operator delete( void* bucket )
{
    RingAllocator::deallocate( bucket );
}
