/* acketwriter.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 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 <stdexcept>
#include <string>
#include <libiclass/time>
#include <fcntl.h>
#include <unistd.h>

#include "metadata.h"
#include "now.h"
#include "packetcollection.h"
#include "packetrecord.h"
#include "packetwriter.h"

using namespace iclass;
using namespace trafficmeter;

using std::cerr;
using std::cout;
using std::endl;
using std::exception;
using std::runtime_error;
using std::sort;
using std::vector;

constexpr const unsigned    BUFFER_SIZE_            = 8U << 20;                 // 8 MiB
constexpr const uintmax_t   FILE_SIZE_THRESHOLD_    = 100ULL << 20;             // 100 MiB
constexpr const int         IDLE_DURATION_          = 200000;                   // microseconds
constexpr const int         N_SLOTS_                = 1 << SUFFIX_HASH_LENGTH;

static int                  nObjects_               = 0;

/**************************************************************************************************************/
inline static bool
lessBuckets_( const PacketCollection::Bucket* lhs, const PacketCollection::Bucket* rhs )
{
    if ( lhs->slotNo != rhs->slotNo )
    {
        return lhs->slotNo < rhs->slotNo;
    }

    return lhs->oldest < rhs->oldest;
}

/**************************************************************************************************************/
PacketWriter::PacketWriter( Directory* directory, int nChunks ) :
    extension_( nObjects_++ ),
    directory_( directory ),
    stems_( N_SLOTS_ ),
    fileSizes_( N_SLOTS_ ),
    fileDescriptors_( N_SLOTS_, -1 ),
    nWritten_a_( 0 )
{
    collection_ = new PacketCollection( nChunks );
    buffer_ = new uint8_t[ BUFFER_SIZE_ ];
}

/**************************************************************************************************************/
PacketWriter::~PacketWriter()
{
    delete[] buffer_;
    delete collection_;

    for ( int fd : fileDescriptors_ )
    {
        if ( fd >= 0 )
        {
            close( fd );
        }
    }
}

/**************************************************************************************************************/
PacketCollection*
PacketWriter::getCollection()
{
    return collection_;
}

/**************************************************************************************************************/
long
PacketWriter::queueSize() const
{
    return collection_->queueSize();
}

/**************************************************************************************************************/
long
PacketWriter::nBuckets() const
{
    return collection_->nBuckets();
}

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

/**************************************************************************************************************/
void
PacketWriter::routine()
{
    cout << Now() << "tid=" << tid() << " " << extension_ << " packetwriter started" << endl;

    while ( !stopped() || nBuckets() > 0 )
    {
        try
        {
            usleep( IDLE_DURATION_ );
            fwrite_();
        }
        catch ( exception& e )
        {
            cerr << "\tpacketwriter: " << e.what() << endl;
        }
    }

    cout << Now() << "tid=" << tid() << " " << extension_ << " packetwriter finished" << endl;
}

/**************************************************************************************************************/
void
PacketWriter::fwrite_()
{
    if ( stopped() )
    {
        collection_->flush();
    }

    auto*   buckets = collection_->pull();

    if ( buckets == nullptr )
    {
        return;
    }

    sort( buckets->begin(), buckets->end(), lessBuckets_ );

    auto            bucketsIter = buckets->begin();
    auto*           bucket = *bucketsIter;
    int             slotNo = bucket->slotNo;
    uint8_t*        cursor = buffer_;
    const Packet*   packet = bucket->packets;
    int             nPackets = 0;

    while ( true )
    {
        PacketRecord*   record = reinterpret_cast<PacketRecord*>( cursor );
        Metadata        meta( packet->timestamp(), packet->data2(), packet->nBytes2() );

        record->timestamp = packet->timestamp();
        record->nBytes2 = packet->nBytes2();
        record->nBytes = meta.nBytes;
        record->vlan = meta.vlan;
        memcpy( record + 1, packet->data2(), record->nBytes2 );
        cursor += wordAlign( sizeof(*record) + record->nBytes2 );
        ++nPackets;
        ++packet;

        if ( packet < bucket->end )
        {
            if ( cursor + packet->nBytes2() <= buffer_ + BUFFER_SIZE_ )
            {
                continue;
            }
        }
        else if ( ++bucketsIter != buckets->end() && (*bucketsIter)->slotNo == slotNo )
        {
            delete bucket;
            bucket = *bucketsIter;
            packet = bucket->packets;
            continue;
        }

        time_t      stem = stems_[ slotNo ];
        uintmax_t   fileSize = fileSizes_[ slotNo ];
        int         fd = fileDescriptors_[ slotNo ];

        if
        (
            bucket->oldest != stem &&
            (
                bucket->oldest >= moduloRoundUp( stem, GROUP_FACTOR ) ||
                fileSize >= FILE_SIZE_THRESHOLD_
            )
        )
        {
            stem = bucket->oldest;
            stems_[ slotNo ] = stem;
            fileSize = 0;

            if ( fd >= 0 )
            {
                close( fd );
                fd = fileDescriptors_[ slotNo ] = -1;
            }
        }

        Directory::File     file( directory_, slotNo, stem, extension_ );

        if ( fd < 0 )
        {
            fd = open( file.path().c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644 );
            assert( fd != -1 );
            fileDescriptors_[ slotNo ] = fd;
        }

        long    nBytes = write( fd, buffer_, cursor - buffer_ );

        if ( nBytes == -1 )
        {
            delete bucket;
            delete buckets;
            throw runtime_error( "file write error " + file.path() + " - " + strerror(errno) );
        }

        fileSize += nBytes;
        cursor = buffer_;
        nWritten_a_ += nPackets;
        nPackets = 0;
        file.updateSize( fileSize );
        fileSizes_[ slotNo ] = fileSize;
        
        if ( bucketsIter == buckets->end() )
        {
            break;
        }
        
        if ( (*bucketsIter)->slotNo != slotNo )
        {
            delete bucket;
            bucket = *bucketsIter;
            packet = bucket->packets;
            slotNo = bucket->slotNo;
        }
    }

    delete bucket;
    delete buckets;
}

