/* flowwriter.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2020, 2021, 2026 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 <cstdlib>
#include <iostream>
#include <libiclass/time>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/mman.h>

#include "flowwriter.h"
#include "now.h"

using namespace iclass;
using namespace trafficmeter;

using std::cerr;
using std::cout;
using std::endl;
using std::map;

constexpr const int     IDLE_DURATION_ = 50000;     // microseconds

/**************************************************************************************************************/
FlowWriter::FlowWriter( Directory* directory, int threadNo, int nThreads ) :
    threadNo_( threadNo ),
    nThreads_( nThreads ),
    nSlots_( ((1U<<SUFFIX_HASH_LENGTH) - threadNo_ - 1)/nThreads_ + 1 ),
    directory_( directory ),
    nWritten_a_( 0 )
{
    headers_ = new FileHeader*[ nSlots_ ];

    for ( int slotNo = 0; slotNo < nSlots_; ++slotNo )
    {
        headers_[ slotNo ] = nullptr;
    }
}

/**************************************************************************************************************/
FlowWriter::~FlowWriter()
{
    for ( FlowCollection* c : collections_ )
    {
        delete c;
    }

    for ( int slotNo = 0; slotNo < nSlots_; ++slotNo )
    {
        if ( headers_[slotNo] )
        {
            delete headers_[ slotNo ];
        }
    }

    delete[] headers_;
}

/**************************************************************************************************************/
void
FlowWriter::attach( StatWriter* writer )
{
    statCollections_.push_back( writer->createCollection() );
}

/**************************************************************************************************************/
FlowCollection*
FlowWriter::createCollection()
{
    FlowCollection*    collection = new FlowCollection( threadNo_, nThreads_ );

    collections_.push_back( collection );
    return collection;
}

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

    for ( const FlowCollection* c : collections_ )
    {
        nRows += c->nRows();
    }

    return nRows;
}

/**************************************************************************************************************/
uintmax_t
FlowWriter::nWritten() const
{
    return nWritten_a_;
}

/**************************************************************************************************************/
void
FlowWriter::routine()
{
    constexpr auto  lessGroup = []( auto lhs, auto rhs ){ return lhs->oldestGroup() < rhs->oldestGroup(); };

    group_ = ( *min_element(collections_.begin(), collections_.end(), lessGroup) )->oldestGroup();
    assert( !statCollections_.empty() );
    cout << Now() << "tid=" << tid() << " flowwriter started" << endl;

    while ( !stopped() || nRows() > 0 )
    {
        wait_();
        pull_();

        if ( !fwrite_() )
        {
            cerr << "\twriter error: no space left on the storage" << endl;
        }

        group_ += GROUP_FACTOR;
    }

    cout
        << Now() << "tid=" << tid() << " flowwriter finished "
        << "(" << nRows() << "," << nWritten() << ")" << endl;
}

/**************************************************************************************************************/
void
FlowWriter::wait_() const
{
    bool    isBusy = true;

    while ( !stopped() && isBusy )
    {
        isBusy = false;

        for ( const FlowCollection* c : collections_ )
        {
            if ( c->oldestGroup() == group_ && c->isBusy() )
            {
                isBusy = true;
                usleep( IDLE_DURATION_ );
                break;
            }
        }
    }
}

/**************************************************************************************************************/
void
FlowWriter::pull_()
{
    bool    isEmpty = true;

    for ( FlowCollection* c : collections_ )
    {
        if ( c->oldestGroup() == group_ )
        {
            if ( isEmpty )
            {
                isEmpty = false;
                combineSlots_ = c->pull();
            }
            else
            {
                FlowCollection::SlotVector*     movingSlots = c->pull();

                for ( int slotNo = 0; slotNo < nSlots_; ++slotNo )
                {
                    map<BeFlow,Summary>&    slot = ( *combineSlots_ )[ slotNo ];

                    for ( auto p : (*movingSlots)[slotNo] )
                    {
                        auto&&  [ row, wasInserted ] = slot.insert( p );

                        if ( !wasInserted )
                        {
                            row->second += p.second;
                        }
                    }
                }

                delete movingSlots;
            }
        }
    }

    assert( !isEmpty );

    for ( int slotNo = 0; slotNo < nSlots_; ++slotNo )
    {
        uint32_t    beHash = ntohl( nThreads_ * slotNo + threadNo_ );
        uint32_t    beMask = ntohl( (1U<<SUFFIX_HASH_LENGTH) - 1 );

        for ( auto&& [beFlow, summary] : (*combineSlots_)[slotNo] )
        {
            if ( (beFlow.sip & beMask) == beHash )
            {
                int     statHash = ntohl( beFlow.sip ) >> ( 32 - PREFIX_HASH_LENGTH );
                int     statThreadNo = statHash % statCollections_.size();
                int     statSlotNo = statHash / statCollections_.size();

                statCollections_[ statThreadNo ]->sPush( statSlotNo, beFlow, summary );
            }

            if ( (beFlow.dip & beMask) == beHash )
            {
                int     statHash = ntohl( beFlow.dip ) >> ( 32 - PREFIX_HASH_LENGTH );
                int     statThreadNo = statHash % statCollections_.size();
                int     statSlotNo = statHash / statCollections_.size();

                statCollections_[ statThreadNo ]->dPush( statSlotNo, beFlow, summary );
            }
        }
    }

    for ( StatCollection* sc : statCollections_ )
    {
        sc->finish( group_ );
    }
}

/**************************************************************************************************************/
bool
FlowWriter::fwrite_()
{
    for ( int slotNo = 0; slotNo < nSlots_; ++slotNo )
    {
        FileHeader*&  header = headers_[ slotNo ];

        if ( header && !header->hasGroup(group_) )
        {
            delete header;
            header = nullptr;
        }

        const auto&     slot = ( *combineSlots_ )[ slotNo ];
        long            slotRows = slot.size();
        long            slotBytes = slotRows * sizeof( FlowRecord );

        if ( slotRows <= 0 )
        {
            continue;
        }

        if ( !header )
        {
            header = new FileHeader( FileHeader::Type::FLOW, FLOW_INTERVALS, group_ );
        }

        int     hash = nThreads_ * slotNo + threadNo_;
        auto&&  file( directory_->writingFile(hash, header->oldestGroup()) );
        int     fd = open( file.path().c_str(), O_RDWR | O_CREAT, 0644 );

        assert( fd != -1 );

        if ( header->isEmpty() )
        {
            assert( header->write(fd) );
        }

        long    mapOffset = header->fileSize();
        long    mapSize = slotBytes;
        int     recOffset = mapOffset % getpagesize();

        mapOffset -= recOffset;
        mapSize += recOffset;

        if ( lseek(fd, mapOffset+mapSize-1, SEEK_SET) == -1 || write(fd, "", 1) == -1 )
        {
            // cannot increase size of the file
            close( fd );
            delete combineSlots_;
            return false;
        }

        uint8_t*  mapping = static_cast<uint8_t*>( mmap(nullptr, mapSize, PROT_WRITE, MAP_SHARED, fd, mapOffset) );

        assert( mapping != MAP_FAILED );

        FlowRecord*  record( reinterpret_cast<FlowRecord*>(mapping + recOffset) );

        for ( auto&& [beFlow, summary] : slot )
        {
            record->beFlow = beFlow;
            record->summary = summary;
            record++;
        }

        header->update( group_, mapOffset + recOffset, slotBytes );
        assert( header->write(fd) );
        munmap( mapping, mapSize );
        close( fd );
        nWritten_a_ += slotRows;
        file.updateSize( mapOffset + mapSize );
    }

    delete combineSlots_;
    return true;
}

