/* flowcollection.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 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 <algorithm>
#include <cassert>
#include <arpa/inet.h>

#include "flowrecord.h"
#include "flowcollection.h"
#include "tmdefs.h"

using namespace iclass;
using namespace trafficmeter;

/**************************************************************************************************************/
FlowCollection::FlowCollection( int threadNo, int nThreads ) :
    threadNo_( threadNo ),
    nSlots_( ((1U<<SUFFIX_HASH_LENGTH) - threadNo_ - 1)/nThreads + 1 ),
    hasActivity_c_( false )
{
    slots_c_ = new SlotVector( nSlots_ );
    nextSlots_c_ = new SlotVector( nSlots_ );
    oldestGroup_c_ = moduloRoundDown( time(nullptr), GROUP_FACTOR );
    newestGroup_c_ = oldestGroup_c_;
}

/**************************************************************************************************************/
FlowCollection::~FlowCollection()
{
    constexpr auto  hasRows = []( auto s ){ return !s.empty(); };

    this->lock();
    assert( find_if(slots_c_->begin(), slots_c_->end(), hasRows) == slots_c_->end() );
    assert( find_if(nextSlots_c_->begin(), nextSlots_c_->end(), hasRows) == nextSlots_c_->end() );
    delete slots_c_;
    delete nextSlots_c_;
}

/**************************************************************************************************************/
void
FlowCollection::push( const SlotIndex& index, const Metadata& meta )
{
    int         slotNo = ( threadNo_ == index.sThreadNo )? index.sSlotNo : index.dSlotNo;
    bool        shouldDuplicate = ( index.dThreadNo == index.sThreadNo && index.dSlotNo != index.sSlotNo );
    Summary     summary( 1, meta.nBytes );
    time_t      group = moduloRoundDown( meta.timestamp.sec(), GROUP_FACTOR );
    BeFlow      beFlow;

    beFlow.sip = htonl( meta.sip );
    beFlow.dip = htonl( meta.dip );
    beFlow.vlan = htons( meta.vlan );
    beFlow.proto =  htons( meta.proto );
    beFlow.sport = htons( meta.sport );
    beFlow.dport = htons( meta.dport );
    this->lock();
    hasActivity_c_ |= ( group <= oldestGroup_c_ );

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

    SlotVector*     slots = ( group <= oldestGroup_c_ )? slots_c_ : nextSlots_c_;
    auto&&          [ row, wasInserted ] = ( *slots )[ slotNo ].try_emplace( beFlow, summary );

    if ( wasInserted )
    {
        if ( shouldDuplicate )
        {
            ( *slots )[ index.dSlotNo ].emplace( beFlow, summary );
        }
    }
    else
    {
        row->second += summary;

        if ( shouldDuplicate )
        {
            ( *slots )[ index.dSlotNo ].find( beFlow )->second += summary;
        }
    }

    this->unlock();
}

/**************************************************************************************************************/
FlowCollection::SlotVector*
FlowCollection::pull()
{
    SlotVector*     slots;

    this->lock();
    slots = slots_c_;
    slots_c_ = nextSlots_c_;
    nextSlots_c_ = new SlotVector( nSlots_ );
    oldestGroup_c_ += GROUP_FACTOR;
    this->unlock();
    return slots;
}

/**************************************************************************************************************/
time_t
FlowCollection::oldestGroup() const
{
    this->lock();

    time_t  oldestGroup = oldestGroup_c_;

    this->unlock();
    return oldestGroup;
}

/**************************************************************************************************************/
bool
FlowCollection::isBusy() const
{
    this->lock();

    time_t  timeBound = oldestGroup_c_ + GROUP_FACTOR + 1;   // 1 second more to collect from unsorted queue
    bool    hasNextTime = newestGroup_c_ > oldestGroup_c_;
    bool    hasActivity =  hasActivity_c_;

    hasActivity_c_ = false;
    this->unlock();
    return time( nullptr ) < timeBound || ( !hasNextTime && hasActivity );
}

/**************************************************************************************************************/
long
FlowCollection::nRows() const
{
    long    nRows = 0;

    this->lock();

    for ( const auto& s : *slots_c_ )
    {
        nRows += s.size();
    }

    for ( const auto& s : *nextSlots_c_ )
    {
        nRows += s.size();
    }

    this->unlock();
    return nRows;
}
