/* flow2writer.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 <sys/mman.h>

#include "flow2writer.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

/**************************************************************************************************************/
Flow2Writer::Flow2Writer( Directory* directory ) :
    directory_( directory ),
    header_( nullptr ),
    nWritten_a_( 0 )
{
}

/**************************************************************************************************************/
Flow2Writer::~Flow2Writer()
{
    for ( const Flow2Collection* c : collections_ )
    {
        delete c;
    }

    if ( header_ )
    {
        delete header_;
    }
}

/**************************************************************************************************************/
Flow2Collection*
Flow2Writer::createCollection()
{
    Flow2Collection*    collection = new Flow2Collection;

    collections_.push_back( collection );
    return collection;
}

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

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

    return nRows;
}

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

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

    group_ = ( *min_element(collections_.begin(), collections_.end(), lessGroup) )->oldestGroup();
    cout << Now() << "tid=" << tid() << " flow2writer 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() << " flow2writer finished"
        << "(" << nRows() << "," << nWritten() << ")" << endl;
}

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

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

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

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

    for ( Flow2Collection* c : collections_ )
    {
        if ( c->oldestGroup() == group_ )
        {
            if ( isEmpty )
            {
                isEmpty = false;
                combine_ = c->pull();
            }
            else
            {
                map<BeFlow2,Summary2>*  map = c->pull();

                for ( auto p : *map )
                {
                    auto&&  [ row, wasInserted ] = combine_->insert( p );

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

                delete map;
            }
        }
    }

    assert( !isEmpty );
}

/**************************************************************************************************************/
bool
Flow2Writer::fwrite_()
{
    if ( header_ && !header_->hasGroup(group_) )
    {
        delete header_;
        header_ = nullptr;
    }

    long    rows = combine_->size();
    long    bytes = rows * sizeof( Flow2Record );

    if ( rows <= 0 )
    {
        delete combine_;
        return true;
    }

    if ( !header_ )
    {
        header_ = new FileHeader( FileHeader::Type::FLOW2, FLOW2_INTERVALS, group_ );
    }

    auto&&  file( directory_->writingFile(0, 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 = bytes;
    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 combine_;
        return false;
    }

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

    assert( mapping != MAP_FAILED );

    Flow2Record*  record = reinterpret_cast<Flow2Record*>( mapping + recOffset );

    for ( const auto& row : *combine_ )
    {
        record->beFlow2 = row.first;
        record->summary2 = row.second;
        record++;
    }

    header_->update( group_, mapOffset + recOffset, bytes );
    assert( header_->write(fd) );
    munmap( mapping, mapSize );
    close( fd );
    delete combine_;
    nWritten_a_ += rows;
    file.updateSize( mapOffset + mapSize );
    return true;
}

