/* flowreport.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 <cstring>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <fcntl.h>

#include "../db-1.85/db.h"
#include "flowreport.h"
#include "tmdefs.h"

using namespace iclass;
using namespace trafficmeter;

using std::cout;
using std::endl;
using std::fixed;
using std::left;
using std::right;
using std::setprecision;
using std::setw;
using std::string;
using std::to_string;

static DB*  db_ = nullptr;

/**************************************************************************************************************/
static string
name_( iclass::ipaddr4t ip, bool shouldResolve )
{
    if ( shouldResolve && db_ )
    {
        DBT       key;
        DBT       value;
        uint32_t  beIp = htonl( ip );

        memset( &key, 0, sizeof(DBT) );
        memset( &value, 0, sizeof(DBT) );
        key.data = &beIp;
        key.size = sizeof( beIp );

        if ( !db_->get(db_, &key, &value, 0) )
        {
            return { reinterpret_cast<const char*>( value.data ), value.size };
        }
    }

    return std::move( ip.str() );
}

/**************************************************************************************************************/
FlowReport::FlowReport() :
    shouldHideTime_( false ),
    shouldShowVlans_( false ),
    shouldShowFlows_( false ),
    shouldResolve_( true ),
    divisor_( 1.0 ),
    minimalTraffic_( 0.0 )
{
    if ( !db_ )
    {
        db_ = dbopen( (Path::base + '/' + NAME_DB).c_str(), O_RDONLY, 0, DB_BTREE, nullptr );
    }
}

/**************************************************************************************************************/
FlowReport::~FlowReport()
{
    if ( db_ )
    {
        db_->close( db_ );
    }
}

/**************************************************************************************************************/
void
FlowReport::hideTime()
{
    shouldHideTime_ = true;
}

/**************************************************************************************************************/
void
FlowReport::showVlans()
{
    shouldShowVlans_ = true;
}

/**************************************************************************************************************/
void
FlowReport::showFlows()
{
    shouldShowFlows_ = true;
}

/**************************************************************************************************************/
void
FlowReport::showNumeric()
{
    shouldResolve_ = false;
}

/**************************************************************************************************************/
bool
FlowReport::setDivisor( double value )
{
    if ( divisor_ != 1.0 || value <= 0.0 )
    {
        return false;
    }

    divisor_ = value;
    return true;
}

/**************************************************************************************************************/
bool
FlowReport::setMinimalTraffic( double value )
{
    if ( value <= 0.0 )
    {
        return false;
    }

    minimalTraffic_ = value;
    return true;
}

/**************************************************************************************************************/
void
FlowReport::printHeader() const
{
    if ( !shouldHideTime_ )
    {
        cout << setw(16) << "time";
    }

    if ( shouldShowVlans_ )
    {
        cout << setw(6) << "vlan";
    }

    cout << setw(6) << "proto" << "   " << left << setw(23) << "host" << "  " << setw(43) << "peer" << right;

    if ( shouldShowFlows_ )
    {
        cout << setw(12) << "i.flows" << setw(12) << "i.packets";
    }

    cout << setw(15) << "i.traffic";

    if ( shouldShowFlows_ )
    {
        cout << setw(12) << "o.flows" << setw(12) << "o.packets";
    }

    cout << setw(15) << "o.traffic" << endl;
}

/**************************************************************************************************************/
bool
FlowReport::printRow( const FlowQuery::Key& flow, const SummaryIo& summary ) const
{
    uintmax_t   minimalBytes = static_cast<uintmax_t>( minimalTraffic_ * divisor_ );

    if ( minimalBytes && summary.nBytesIn() < minimalBytes && summary.nBytesOut() < minimalBytes )
    {
        return false;
    }

    if ( !shouldHideTime_ )
    {
        static time_t    timeCache = 0;
        static char      timeCacheString[ 32 ];

        if ( timeCache != flow.group )
        {
            struct tm  timeComponents;

            timeCache = flow.group;
            localtime_r( &timeCache, &timeComponents );
            strftime( timeCacheString, sizeof(timeCacheString), "%FT%H:%M", &timeComponents );
        }

        cout << setw(16) << timeCacheString;
    }

    if ( shouldShowVlans_ )
    {
        cout << setw(6) << flow.vlan;
    }

    string&&    host = flow.host.str();
    string&&    peer = name_( flow.peer, shouldResolve_ );

    if ( flow.hport >= 0 )
    {
        host += " :" + to_string( flow.hport );
    }

    if ( flow.pport >= 0 )
    {
        peer += " :" + to_string( flow.pport );
    }

    cout << setw(6) << flow.proto << "   " << left << setw(23) << host << "  " << setw(43) << peer << right;

    if ( shouldShowFlows_ )
    {
        cout << setw(12) << summary.nFlowsIn() << setw(12) << summary.nPacketsIn();
    }

    cout << setw(15);

    if ( divisor_ == 1.0 )
    {
        cout << summary.nBytesIn();
    }
    else
    {
        cout << fixed << setprecision(2) << summary.nBytesIn() / divisor_;
    }

    if ( shouldShowFlows_ )
    {
        cout << setw(12) << summary.nFlowsOut() << setw(12) << summary.nPacketsOut();
    }

    cout << setw(15);

    if ( divisor_ == 1.0 )
    {
        cout << summary.nBytesOut() << endl;
    }
    else
    {
        cout << fixed << setprecision(2) << summary.nBytesOut() / divisor_ << endl;
    }

    return true;
}

/**************************************************************************************************************/
void
FlowReport::printFooter( string message, const SummaryIo& summary ) const
{
    cout << setw(93 + shouldHideTime_*-16 + shouldShowVlans_*6) << left << message << right;

    if ( shouldShowFlows_ )
    {
        cout << setw(12) << summary.nFlowsIn() << setw(12) << summary.nPacketsIn();
    }

    cout << setw(15);

    if ( divisor_ == 1.0 )
    {
        cout << summary.nBytesIn();
    }
    else
    {
        cout << fixed << setprecision(2) << summary.nBytesIn() / divisor_;
    }

    if ( shouldShowFlows_ )
    {
        cout << setw(12) << summary.nFlowsOut() << setw(12) << summary.nPacketsOut();
    }

    cout << setw(15);

    if ( divisor_ == 1.0 )
    {
        cout << summary.nBytesOut() << endl;
    }
    else
    {
        cout << fixed << setprecision(2) << summary.nBytesOut() / divisor_ << endl;
    }
}

