/* namequery.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 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 <cstring>
#include <limits>
#include <stdexcept>
#include <fcntl.h>

#include "../db-1.85/db.h"
#include "namequery.h"

using namespace iclass;
using namespace trafficmeter;

using std::numeric_limits;
using std::runtime_error;
using std::set;
using std::string;

struct NameQuery::Db_
{
    DB*                         db;
    DBT                         key;
    DBT                         value;
    ipset4t::const_iterator     network;
    int                         rcSeq;
};

/**************************************************************************************************************/
static int
dbSetCursor_( DB* db, DBT* key, DBT* value, ipaddr4t ip )
{
    uint32_t    beIp = htonl( ip );

    memset( key, 0, sizeof(DBT) );
    memset( value, 0, sizeof(DBT) );
    key->data = &beIp;
    key->size = sizeof( beIp );
    return db->seq( db, key, value, R_CURSOR );
}

/**************************************************************************************************************/
NameQuery::NameQuery( const Path& dbPath ) :
    path_( dbPath ),
    db_( nullptr )
{
}

/**************************************************************************************************************/
NameQuery::~NameQuery()
{
    if ( db_ != nullptr )
    {
        db_->db->close( db_->db );
        delete db_;
    }
}

/**************************************************************************************************************/
void
NameQuery::addNetwork( ipnet4t network )
{
    if ( db_ != nullptr )
    {
        throw runtime_error( "cannot change query parameters when reading is started" );
    }

    networks_.insert( network );
}

/**************************************************************************************************************/
bool
NameQuery::hasRows()
{
    if ( db_ == nullptr )
    {
        db_ = new Db_;
        db_->db = dbopen( path_.c_str(), O_RDONLY, 0644, DB_BTREE, nullptr );

        if ( db_->db == nullptr )
        {
            throw runtime_error( "cannot open database: " + path_ );
        }

        db_->network = networks_.begin();
        db_->rcSeq = dbSetCursor_( db_->db, &db_->key, &db_->value, db_->network->front() );
    }

    ipaddr4t    ip( ntohl(*reinterpret_cast<const uint32_t*>(db_->key.data)) );

    if ( ip > db_->network->back() )
    {
        ++(db_->network);

        if ( db_->network == networks_.end() )
        {
            return false;
        }

        db_->rcSeq = dbSetCursor_( db_->db, &db_->key, &db_->value, db_->network->front() );
    }

    if ( db_->rcSeq == -1 )
    {
        throw runtime_error( "sequential read error" );
    }

    return db_->rcSeq == 0;
}

/**************************************************************************************************************/
NameQuery::Row
NameQuery::getRow()
{
    ipaddr4t    ip( ntohl( *reinterpret_cast<const uint32_t*>(db_->key.data)) );
    string      name( {reinterpret_cast<const char*>(db_->value.data), db_->value.size} );

    db_->rcSeq = db_->db->seq( db_->db, &db_->key, &db_->value, R_NEXT );
    return { ip, std::move(name) };
}

/**************************************************************************************************************/
set<NameQuery::Row>
NameQuery::fetchSmall( int nRowsLimit )
{
    set<Row>    names;

    while ( hasRows() )
    {
        Row&&   row = getRow();

        if ( names.size() < nRowsLimit )
        {
            names.insert( row );
        }
        else if ( !(*(names.rbegin()) < row) )
        {
            names.insert( row );
            names.erase( --(names.end()) );
        }
    }

    return std::move( names );
}

/**************************************************************************************************************/
set<NameQuery::Row>
NameQuery::fetchLarge( int nRowsLimit )
{
    set<Row>    names;

    while ( hasRows() )
    {
        Row&&   row = getRow();

        if ( names.size() < nRowsLimit )
        {
            names.insert( row );
        }
        else if ( *(names.begin()) < row )
        {
            names.insert( row );
            names.erase( names.begin() );
        }
    }

    return std::move( names );
}

/**************************************************************************************************************/
long
NameQuery::nFileRecords() const
{
    DB*     db = dbopen( path_.c_str(), O_RDONLY, 0644, DB_BTREE, nullptr );

    if ( db == nullptr )
    {
        throw runtime_error( "cannot open database: " + path_ );
    }

    DBT     key;
    DBT     value;
    long    nRecords = 0;

    while ( db->seq(db, &key, &value, R_NEXT) == 0 )
    {
        ++nRecords;
    }

    db->close( db );
    return nRecords;
}

/**************************************************************************************************************/
long
NameQuery::fileSize() const
{
    return path_.fileSize();
}
