/* tmw.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 <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <stdexcept>
#include <libiclass/period>
#include <getopt.h>
#include <unistd.h>
#include <sys/utsname.h>

#include "dag.h"
#include "iface.h"
#include "now.h"
#include "path.h"
#include "watcher.h"

using namespace iclass;
using namespace trafficmeter;

using std::cerr;
using std::cout;
using std::endl;
using std::exception;
using std::fixed;
using std::map;
using std::setprecision;
using std::setw;
using std::string;
using std::to_string;
using std::literals::string_literals::operator""s;

static const struct option  LONG_OPTIONS_[] =
{
    { "duration",   required_argument, nullptr, 'd' },
    { "no-promisc", no_argument,       nullptr, 'P' },
    { "kbps",       no_argument,       nullptr, 'k' },
    { "mbps",       no_argument,       nullptr, 'm' },
    { "gbps",       no_argument,       nullptr, 'g' },
    { "version",    no_argument,       nullptr, 'v' },
    { "help",       no_argument,       nullptr, 'h' },
    { nullptr,      0,                 nullptr,  0  }
};

/**************************************************************************************************************/
string
bpsString( uintmax_t value, const string& suffix )
{
    static map<string, long>  divisors =
    {
        { "Kbps", 1000 },
        { "Mbps", 1000000 },
        { "Gbps", 1000000000 }
    };

    auto&&  found( divisors.find(suffix) );

    if ( found != divisors.end() )
    {
        value /= found->second;
    }

    return to_string( value ) + ' ' + suffix;
}

/**************************************************************************************************************/
int
main( int argc, char* argv[] )
{
    string  progName( Path(argv[0]).fileName() );
    string  bpsSuffix( "bps" );
    int     duration = DEFAULT_WATCHING_DURATION;
    bool    isPromisc = true;
    int     opt;

    while ( (opt = getopt_long(argc,argv,"d:pkmgvh",LONG_OPTIONS_,nullptr)) != -1 )
    {
        switch ( opt )
        {
            case 'd':

                duration = atoi( optarg );
                break;

            case 'P':

                isPromisc = false;
                break;

            case 'k':

                bpsSuffix = "Kbps";
                break;

            case 'm':

                bpsSuffix = "Mbps";
                break;

            case 'g':

                bpsSuffix = "Gbps";
                break;

            case 'v':

                cout <<
                    "Trafficmeter Watch version " << VERSION << " for " << SYSTEM_TYPE << "\n"
                    "For info, please visit http://www.trafficmeter.org\n";

                return EXIT_SUCCESS;

            case 'h':
            default:

                cout <<
                    progName << " is the utility to observe bandwidth on the source\n"
                    "Usage: " << progName << " [options] source\n"
                    "\t-d --duration=<sec>\tWatching duration in seconds, default '" << DEFAULT_WATCHING_DURATION << "'\n"
                    "\t-P --no-promisc\t\tDo not set interface to promiscuous mode\n"
                    "\t-k --kbps\t\tOutput bandwidth in Kbps\n"
                    "\t-m --mbps\t\tOutput bandwidth in Mbps\n"
                    "\t-g --mbps\t\tOutput bandwidth in Gbps\n"
                    "\t-v --version\t\tPrint version information\n"
                    "\t-h --help\t\tPrint help message\n";

                return EXIT_SUCCESS;
        }
    }

    argc -= optind;
    argv += optind;

    if ( duration <= 0 )
    {
        cerr << progName << "; incorrect duration\n";
        return EXIT_FAILURE;
    }

    if ( argc < 1 )
    {
        cerr << progName << ": source must be specified\n";
        return EXIT_FAILURE;
    }

    string      sourceName( argv[0] );
    Source*     source;

    try
    {
        if ( sourceName.find("dag") == 0 )
        {
            source = new Dag( sourceName );
        }
        else
        {
            source = new Iface( sourceName, isPromisc );
        }
    }
    catch ( exception& e )
    {
        cerr << "Cannot establish packet catcher on " << sourceName << " - " << e.what() << "\n";
        return EXIT_FAILURE;
    }

    if ( duration < 1 )
    {
        duration = 1;
    }

    struct utsname  utsName;
    Watcher         watcher( source );

    uname( &utsName );
    Now::hideDate();
    watcher.start();
    usleep( 550000 );

    gtimet      startTime( time(nullptr) + 1 );
    periodt     interval( startTime, startTime + duration );
    string      systemInfo( utsName.sysname + " "s + utsName.release + " " + utsName.machine );

    cout <<
        "Watch throughout " << duration << " seconds\n"
        "Interval:    [ " << interval.begin().str() << "; " << interval.end().str() << " )\n"
        "System:      " << systemInfo << " UserHZ=" << sysconf(_SC_CLK_TCK) << "\n"
        "Source:      " << source->name() << " - " << source->info() << endl;

    uintmax_t   nDroppedPrevious = source->nDropped();

    for ( int i = 0; i < duration; ++i )
    {
        watcher.measure();

        uintmax_t   nDropped = source->nDropped();

        cout <<
            Now() << setw(5) << watcher.pps() << " pps, " <<
            setw(12) << bpsString(watcher.bps(), bpsSuffix) << ", "
            "dropped " << setw(2) << nDropped - nDroppedPrevious << ", "
            "cputime " << setw(8) << fixed << setprecision(6) << watcher.cpuUsage() << endl;

        nDroppedPrevious = nDropped;
    }

    cout <<
        "Minimum: " << watcher.ppsMin() << " pps, " << bpsString(watcher.bpsMin(), bpsSuffix) << "\n"
        "Maximum: " << watcher.ppsMax() << " pps, " << bpsString(watcher.bpsMax(), bpsSuffix) << "\n"
        "Average: " << watcher.ppsAverage() << " pps, " << bpsString(watcher.bpsAverage(), bpsSuffix) << endl;

    watcher.stop();
    delete source;
    return EXIT_SUCCESS;
}

