#include "qgapplicationperformance.h"

#include <QTimerEvent>
#include <QElapsedTimer>
#include <QDebug>

#include <windows.h>
#include <psapi.h>
#include <stdint.h>

static QgApplicationPerformance* globalInstance;

struct MyCounters
{
    unsigned long refresh_rate;

    float cpu;
    float cpu_peek;
    float mem;
    float mem_peek;

    uint64_t kt;
    uint64_t ut;
    uint64_t tt;

    unsigned int cores;

    int tid;

    unsigned long long net_bytes;
    unsigned long long net_bytes_period;
    float net_band;

    unsigned net_index;

    QString hAddress;
    bool calculate(QElapsedTimer& t); //unsigned long period_ms);
};

static MyCounters myCounters;

QgApplicationPerformance::QgApplicationPerformance(QObject *parent) : QObject(parent)
{
    myCounters.refresh_rate=2;
    net_index=0;
    myCounters.net_index=0;
}


QgApplicationPerformance& QgApplicationPerformance::instance()
{
    if (!globalInstance)
    {
        globalInstance=new QgApplicationPerformance;

        myCounters.tid=0;

        //globalInstance->startTimer(myCounters.refresh_rate*1000, Qt::PreciseTimer);
        myCounters.ut=0;
        myCounters.kt=0;
        myCounters.tt=0;
        myCounters.cores=1;

        SYSTEM_INFO sysInfo;
        GetSystemInfo(&sysInfo);
        myCounters.cores=sysInfo.dwNumberOfProcessors>0 ? sysInfo.dwNumberOfProcessors : 1;

        globalInstance->setRefreshRate(2);

        myCounters.calculate(globalInstance->elapsedTimer);

        globalInstance->elapsedTimer.start();
     }
    return *globalInstance;
}

void QgApplicationPerformance::timerEvent(QTimerEvent* evn)
{
    if (evn->timerId()==myCounters.tid)
    {
        //qint64 elapsed=elapsedTimer.elapsed();
        if (myCounters.calculate(elapsedTimer)) //myCounters.refresh_rate*1000))
            Q_EMIT countersUpdated();

        evn->accept();
    }
    else
        QObject::timerEvent(evn);
}

void QgApplicationPerformance::setRefreshRate(unsigned long seconds)
{
    myCounters.refresh_rate=seconds;
    if (myCounters.tid)
    {
        killTimer(myCounters.tid);
        myCounters.tid=0;
    }
    myCounters.tid=globalInstance->startTimer(myCounters.refresh_rate*1000);
}

void QgApplicationPerformance::setNetworkInterfaceIndex(unsigned int index, const QString& hAddress)
{
    myCounters.net_index=index;
    myCounters.hAddress=hAddress;
}

void QgApplicationPerformance::reset()
{
    if (myCounters.tid)
        killTimer(myCounters.tid);

    myCounters.tid=0;

    elapsedTimer.invalidate();

    myCounters.ut=0;
    myCounters.kt=0;
    myCounters.tt=0;
    myCounters.mem_peek=0;
    myCounters.cpu_peek=0;
    myCounters.mem=0;
    myCounters.cpu=0;

    myCounters.net_band=0;
    myCounters.net_bytes=0;
    myCounters.net_bytes_period=0;

    myCounters.calculate(elapsedTimer);
    setRefreshRate(myCounters.refresh_rate);
}

unsigned long QgApplicationPerformance::refreshRate() const
{
    return myCounters.refresh_rate;
}

unsigned int QgApplicationPerformance::cpuUsage() const
{
    return myCounters.cpu;
}

unsigned int QgApplicationPerformance::peekCpuUsage() const
{
    return myCounters.cpu_peek;
}


unsigned long QgApplicationPerformance::memoryUsageKiB() const
{
    return myCounters.mem/1024;
}

unsigned long QgApplicationPerformance::peekMemoryUsageKiB() const
{
    return myCounters.mem_peek/1024;
}

QString QgApplicationPerformance::toString()
{
    return QString("%1%(%2%), %3MB/%4MB, N: %5%(%6KB) @%7")
            .arg(cpuUsage()).arg(peekCpuUsage()).arg(memoryUsageKiB()/1000).arg(peekMemoryUsageKiB()/1000)
            .arg(myCounters.net_band, 0, 'f', 2).arg(myCounters.net_bytes_period).arg(myCounters.hAddress);

}

void QgApplicationPerformance::updateCounters()
{
}

//
static unsigned long long GetMemoryUsage()
{
    PROCESS_MEMORY_COUNTERS_EX memInfo;
    memInfo.cb=sizeof memInfo;
    auto ok=GetProcessMemoryInfo(GetCurrentProcess(),
                                 (PROCESS_MEMORY_COUNTERS*)&memInfo,
                                 sizeof memInfo);
    if (ok)
    {
        return memInfo.PrivateUsage; //WorkingSetSize;
    }
    return 0;
}

static bool GetNetworkLoad(unsigned int index, unsigned long long &bytes, unsigned long long &speed);

bool MyCounters::calculate(QElapsedTimer & timer)
{
    auto m=GetMemoryUsage();
    if (m>myCounters.mem_peek)
        myCounters.mem_peek=m;
    myCounters.mem=m;

    FILETIME pct;
    FILETIME pext;
    FILETIME pkt;
    FILETIME put;

    auto period_ms=timer.isValid() ? timer.elapsed() : 0;
    auto ok=GetProcessTimes(GetCurrentProcess(), &pct, &pext, &pkt, &put);
    if (ok)
    {
        timer.start();
        uint64_t k=pkt.dwHighDateTime;
        k=k<<32;
        k|=uint64_t(pkt.dwLowDateTime);

        uint64_t u=put.dwHighDateTime;
        u=u<<32;
        u|=uint64_t(put.dwLowDateTime);

        if (period_ms)
        {
            uint64_t period_us=period_ms;
            period_us=period_us*10;

            uint64_t dk=k-myCounters.kt;
            uint64_t du=u-myCounters.ut;

            //From 100ns to us
            dk=dk/10;
            du=du/10;

            uint64_t tot_us=dk+du;

            double cpu_usage=(double(tot_us)/double(period_us))/cores;

            myCounters.cpu=(cpu_usage+0.5);
            if (myCounters.cpu>cpu_peek)
                myCounters.cpu_peek=myCounters.cpu;
        }
        kt=k;
        ut=u;

        //return true;
    }

    if (net_index)
    {
        unsigned long long bytes;
        unsigned long long speed;
        if (!GetNetworkLoad(net_index, bytes, speed))
            return true;
        if (speed>0)
        {
            if (period_ms)
            {
                float seconds=float(period_ms)*0.001;
                unsigned long long d=bytes-net_bytes;
                //float ds=d*float(period_ms*10000);
                net_bytes_period=(d/seconds)/1000; //ds/(period_ms*10000);
                if (speed)
                {
                    float s=(float(speed)/8)*seconds;
                    float b=d/s;
                    b=b*100;
                    net_band=b;
                }
            }
            net_bytes=bytes;
            //net_band=0;
        }
    }
    return true;
}

#include <iphlpapi.h>

#if 0
struct myiftable_t{
  DWORD dwNumEntries;
  MIB_IFROW table[42];
};
static myiftable_t static_iftab;
static MIB_IFTABLE* iftab;
#endif

static MIB_IFROW* ifrow;
static MIB_IFROW saved_ifrow;
static bool net_info_error;

static bool GetNetworkLoad(unsigned int index, unsigned long long& bytes, unsigned long long &speed)
{
    bytes=0;
    speed=1;
    if (net_info_error)
        return false;
#if 0
    if (!ifrow)
    {
        DWORD dwSize=sizeof static_iftab;
        iftab=(MIB_IFTABLE*)&static_iftab;

        DWORD r=GetIfTable(iftab, &dwSize, 0);
        if (r==ERROR_INSUFFICIENT_BUFFER)
        {
            iftab=(MIB_IFTABLE*)new char[dwSize+100];
            memset(iftab, dwSize, 0);
            r=GetIfTable(iftab, &dwSize, 0);
        }

        if (r!=NO_ERROR)
        {
            net_info_error=true;
            return false;
        }
        saved_ifrow.dwIndex=iftab->table[20].dwIndex;
        qDebug()<<"Perf-mon: ETH"<<saved_ifrow.dwIndex;
        ifrow=&saved_ifrow;
    }
#else
    saved_ifrow.dwIndex=index;
    ifrow=&saved_ifrow;
#endif
    GetIfEntry(ifrow);
    unsigned long long total=ifrow->dwInOctets+ifrow->dwOutOctets;
    bytes=total;
    speed=ifrow->dwSpeed;
    return true;
}
