/* statwriter.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 <cassert>
#include <cstdlib>
#include <iostream>
#include <libiclass/time>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#include "now.h"
#include "statwriter.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

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

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

/**************************************************************************************************************/
StatWriter::~StatWriter()
{
    for ( StatCollection* c : collections_ )
    {
        delete c;
    }

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

    delete[] headers_;
}

/**************************************************************************************************************/
StatCollection*
StatWriter::createCollection()
{
    StatCollection*    collection = new StatCollection( threadNo_, nThreads_ );

    collections_.push_back( collection );
    return collection;
}

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

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

    return nRows;
}

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

/**************************************************************************************************************/
void
StatWriter::routine()
{
    cout << Now() << "tid=" << tid() << " statwriter started" << endl;

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

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

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

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

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

        for ( const StatCollection* c : collections_ )
        {
            if ( !c->isPullable() )
            {
                isBusy = true;
                usleep( IDLE_DURATION_ );
                break;
            }
        }
    }
}

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

    for ( StatCollection* c : collections_ )
    {
        if ( !c->isPullable() )
        {
            continue;
        }

        if ( isEmpty )
        {
            isEmpty = false;
            combineSlots_ = c->pull();
            group_ = c->group();
        }
        else
        {
            StatCollection::SlotVector*     movingSlots = c->pull();

            for ( int slotNo = 0; slotNo < nSlots_; ++slotNo )
            {
                map<BeStat,SummaryIo>&  combineSlot = ( *combineSlots_ )[ slotNo ];

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

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

            delete movingSlots;
        }
    }
}

/**************************************************************************************************************/
bool
StatWriter::fwrite_()
{
    if ( combineSlots_ == nullptr )
    {
        return true;
    }

    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( StatRecord );

        if ( slotRows <= 0 )
        {
            continue;
        }

        if ( !header )
        {
            header = new FileHeader( FileHeader::Type::STAT, STAT_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_;
            combineSlots_ = nullptr;
            return false;
        }

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

        assert( mapping != MAP_FAILED );

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

        for ( const auto& row : slot )
        {
            record->beStat = row.first;
            record->summaryIo = row.second;
            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_;
    combineSlots_ = nullptr;
    return true;
}

