/* namewriter.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 <cstring>
#include <ctime>
#include <iostream>
#include <libiclass/time>
#include <fcntl.h>
#include <unistd.h>

#include "../db-1.85/db.h"
#include "namewriter.h"
#include "now.h"

using namespace iclass;
using namespace trafficmeter;

using std::cout;
using std::endl;
using std::map;
using std::string;

constexpr const int     IDLE_DURATION_ = 50000;     // microseconds

/**************************************************************************************************************/
static void
dbWrite_( DB* db, ipaddr4t ip, const string& host )
{
    DBT         key;
    DBT         value;
    uint32_t    ipData = htonl( ip );

    memset( &key, 0, sizeof(DBT) );
    memset( &value, 0, sizeof(DBT) );
    key.data = &ipData;
    key.size = sizeof( ipData );
    value.data = const_cast<char*>( host.c_str() );
    value.size = host.size();
    db->put( db, &key, &value, 0 );
}

/**************************************************************************************************************/
NameWriter::NameWriter() :
    nWritten_a_( 0 )
{
}

/**************************************************************************************************************/
NameWriter::~NameWriter()
{
}

/**************************************************************************************************************/
NameCollection*
NameWriter::createCollection()
{
    NameCollection*     collection = new NameCollection;

    collections_.push_back( collection );
    return collection;
}

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

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

    return nRows;
}

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

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

    DB*     db = dbopen( NAME_DB, O_RDWR | O_CREAT, 0644, DB_BTREE, nullptr );
    time_t  pullTime = moduloRoundUp( time(nullptr), GROUP_FACTOR );

    assert( db != nullptr );

    while ( !stopped() || nRows() > 0 )
    {
        while ( !stopped() && time(nullptr) <= pullTime )
        {
            usleep( IDLE_DURATION_ );
        }

        map<NameCollection::Key,int>*   combine = nullptr;

        for ( NameCollection* c : collections_ )
        {
            if ( combine == nullptr )
            {
                combine = c->pull();
            }
            else
            {
                map<NameCollection::Key,int>*   map = c->pull();

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

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

                delete map;
            }
        }

        assert( combine != nullptr );

        int         nHits = 0;
        long        nWritten = 0;
        ipaddr4t    ip;
        string      host;

        for ( const auto& row : *combine )
        {
            if ( row.first.ip != ip )
            {
                if ( nHits > 0 )
                {
                    dbWrite_( db, ip, host );
                    ++nWritten;
                }

                ip = row.first.ip;
                host = row.first.host;
                nHits = row.second;
            }
            else if ( row.second > nHits || (row.second == nHits && row.first.host.size() < host.size()) )
            {
                host = row.first.host;
                nHits = row.second;
            }
        }

        if ( !combine->empty() )
        {
            dbWrite_( db, ip, host );
            db->sync( db, 0 );
            nWritten_a_ += nWritten + 1;
        }

        pullTime += GROUP_FACTOR;
        delete combine;
    }

    db->close( db );
    cout << Now() << "tid=" << tid() << " namewriter finished" << endl;
}

