/* From wmload.c, v0.9.2, licensed under the GPL. */
#include <config.h>
#include <sys/types.h>
#include <sys/statvfs.h>
#include <math.h>
#include <fcntl.h>
#include <unistd.h>

#include <glibtop.h>
#include <glibtop/cpu.h>
#include <glibtop/mem.h>
#include <glibtop/swap.h>
#include <glibtop/loadavg.h>
#include <glibtop/netload.h>
#include <glibtop/netlist.h>
#include <glibtop/mountlist.h>
#include <glibtop/fsusage.h>

#include "linux-proc.h"
#include "autoscaler.h"

static const unsigned needed_cpu_flags =
(1 << GLIBTOP_CPU_USER) +
(1 << GLIBTOP_CPU_IDLE) +
(1 << GLIBTOP_CPU_SYS) +
(1 << GLIBTOP_CPU_NICE);

#if 0
static const unsigned needed_page_flags =
(1 << GLIBTOP_SWAP_PAGEIN) +
(1 << GLIBTOP_SWAP_PAGEOUT);
#endif

static const unsigned needed_mem_flags =
(1 << GLIBTOP_MEM_USED) +
(1 << GLIBTOP_MEM_FREE);

static const unsigned needed_swap_flags =
(1 << GLIBTOP_SWAP_USED) +
(1 << GLIBTOP_SWAP_FREE);

static const unsigned needed_loadavg_flags =
(1 << GLIBTOP_LOADAVG_LOADAVG);

static const unsigned needed_netload_flags =
(1 << GLIBTOP_NETLOAD_IF_FLAGS) +
(1 << GLIBTOP_NETLOAD_BYTES_TOTAL);


void
GetLoad (int Maximum, int data [5], LoadGraph *g)
{
    int usr, nice, sys, iowait, free;
    int total;

    glibtop_cpu cpu;

    glibtop_get_cpu (&cpu);

    g_return_if_fail ((cpu.flags & needed_cpu_flags) == needed_cpu_flags);

    g->cpu_time [0] = cpu.user;
    g->cpu_time [1] = cpu.nice;
    g->cpu_time [2] = cpu.sys;
    g->cpu_time [3] = cpu.iowait + cpu.irq + cpu.softirq;
    g->cpu_time [4] = cpu.idle;

    if (!g->cpu_initialized) {
        memcpy (g->cpu_last, g->cpu_time, sizeof (g->cpu_last));
        g->cpu_initialized = 1;
    }

    usr  = g->cpu_time [0] - g->cpu_last [0];
    nice = g->cpu_time [1] - g->cpu_last [1];
    sys  = g->cpu_time [2] - g->cpu_last [2];
    iowait = g->cpu_time [3] - g->cpu_last [3];
    free = g->cpu_time [4] - g->cpu_last [4];

    total = usr + nice + sys + free + iowait;

    memcpy(g->cpu_last, g->cpu_time, sizeof g->cpu_last);

    usr  = rint (Maximum * (float)(usr)  / total);
    nice = rint (Maximum * (float)(nice) / total);
    sys  = rint (Maximum * (float)(sys)  / total);
    iowait = rint (Maximum * (float)(iowait) / total);
    free = Maximum - usr - nice - sys - iowait;

    data [0] = usr;
    data [1] = sys;
    data [2] = nice;
    data [3] = iowait;
    data [4] = free;
}

void
GetDiskLoad (int Maximum, int data [3], LoadGraph *g)
{
    static gboolean first_call = TRUE;
    static guint64 lastread = 0, lastwrite = 0;
    static AutoScaler scaler;

    guint i;
    int max;
    gboolean nvme_diskstats;

    guint64 read, write;
    guint64 readdiff, writediff;

    nvme_diskstats = g_settings_get_boolean (g->multiload->settings, "diskload-nvme-diskstats");

    if(first_call)
    {
        autoscaler_init(&scaler, 60, 500);
    }

    read = write = 0;

    if (nvme_diskstats)
    {
        FILE *fdr;
        char line[255];
        guint64 s_read, s_write;

        fdr = fopen("/proc/diskstats", "r");
        if (!fdr)
        {
            g_settings_set_boolean (g->multiload->settings, "diskload-nvme-diskstats", FALSE);
            return;
        }

        while (fgets(line, 255, fdr))
        {
            /* Match main device, rather than individual partitions (e.g. nvme0n1) */
            if (!g_regex_match_simple("\\snvme\\d+\\w+\\d+\\s", line, 0, 0))
            {
                continue;
            }

            /*
               6 - sectors read
               10 - sectors written
               */
            if (sscanf(line, "%*d %*d %*s %*d %*d %ld %*d %*d %*d %ld", &s_read, &s_write) == 2)
            {
                read += 512 * s_read;
                write += 512 * s_write;
            }
        }
        fclose(fdr);
    }
    else
    {
        glibtop_mountlist mountlist;
        glibtop_mountentry *mountentries;

        mountentries = glibtop_get_mountlist (&mountlist, FALSE);

        for (i = 0; i < mountlist.number; i++)
        {
            struct statvfs statresult;
            glibtop_fsusage fsusage;

            if (strstr (mountentries[i].devname, "/dev/") == NULL)
                continue;

            if (strstr (mountentries[i].mountdir, "/media/") != NULL)
                continue;

            if (statvfs (mountentries[i].mountdir, &statresult) < 0)
            {
                g_debug ("Failed to get statistics for mount entry: %s. Reason: %s. Skipping entry.",
                         mountentries[i].mountdir, strerror(errno));
                continue;
            }

            glibtop_get_fsusage(&fsusage, mountentries[i].mountdir);
            read += fsusage.read; write += fsusage.write;
        }

        g_free(mountentries);
    }

    readdiff  = read - lastread;
    writediff = write - lastwrite;

    lastread  = read;
    lastwrite = write;

    if(first_call)
    {
        first_call = FALSE;
        memset(data, 0, 3 * sizeof data[0]);
        return;
    }

    max = autoscaler_get_max(&scaler, readdiff + writediff);

    data[0] = (float)Maximum *  readdiff / (float)max;
    data[1] = (float)Maximum * writediff / (float)max;
    data[2] = (float)Maximum - (data [0] + data[1]);
}

#if 0
void
GetPage (int Maximum, int data [3], LoadGraph *g)
{
    static int max = 100; /* guess at maximum page rate (= in + out) */
    static u_int64_t lastin = 0;
    static u_int64_t lastout = 0;
    int in, out, idle;

    glibtop_swap swap;

    glibtop_get_swap (&swap);

    assert ((swap.flags & needed_page_flags) == needed_page_flags);

    if ((lastin > 0) && (lastin < swap.pagein)) {
	in = swap.pagein - lastin;
    }
    else {
	in = 0;
    }
    lastin = swap.pagein;

    if ((lastout > 0) && (lastout < swap.pageout)) {
	out = swap.pageout - lastout;
    }
    else {
	out = 0;
    }
    lastout = swap.pageout;

    if ((in + out) > max) {
	/* Maximum page rate has increased. Change the scale without
	   any indication whatsoever to the user (not a nice thing to
	   do). */
	max = in + out;
    }

    in   = rint (Maximum * ((float)in / max));
    out  = rint (Maximum * ((float)out / max));
    idle = Maximum - in - out;

    data [0] = in;
    data [1] = out;
    data [2] = idle;
}
#endif /* 0 */

void
GetMemory (int Maximum, int data [5], LoadGraph *g)
{
    int user, shared, buffer, cached;

    glibtop_mem mem;

    glibtop_get_mem (&mem);

    g_return_if_fail ((mem.flags & needed_mem_flags) == needed_mem_flags);

    user    = rint (Maximum * (float)mem.user / (float)mem.total);
    shared  = rint (Maximum * (float)mem.shared / (float)mem.total);
    buffer  = rint (Maximum * (float)mem.buffer / (float)mem.total);
    cached = rint (Maximum * (float)mem.cached / (float)mem.total);

    data [0] = user;
    data [1] = shared;
    data [2] = buffer;
    data [3] = cached;
    data [4] = Maximum-user-shared-buffer-cached;
}

void
GetSwap (int Maximum, int data [2], LoadGraph *g)
{
    int used;

    glibtop_swap swap;

    glibtop_get_swap (&swap);
    g_return_if_fail ((swap.flags & needed_swap_flags) == needed_swap_flags);

    if (swap.total == 0) {
        used = 0;
    }
    else {
        used = rint (Maximum * (float)swap.used / swap.total);
    }

    data [0] = used;
    data [1] = Maximum - used;
}

void
GetLoadAvg (int Maximum, int data [2], LoadGraph *g)
{
    glibtop_loadavg loadavg;
    glibtop_get_loadavg (&loadavg);

    g_return_if_fail ((loadavg.flags & needed_loadavg_flags) == needed_loadavg_flags);

    /* g->loadavg1 represents %used */
    g->loadavg1 = loadavg.loadavg[0];

    data [0] = rint ((float) Maximum * g->loadavg1);
    data [1] = Maximum - data[0];
}

/*
 * Return true if a network device (identified by its name) is virtual
 * (ie: not corresponding to a physical device). In case it is a physical
 * device or unknown, returns false.
 */
static gboolean
is_net_device_virtual(char *device)
{
    /*
     * There is not definitive way to find out. On some systems (Linux
     * kernels ≳ 2.19 without option SYSFS_DEPRECATED), there exist a
     * directory /sys/devices/virtual/net which only contains virtual
     * devices.  It's also possible to detect by the fact that virtual
     * devices do not have a symlink "device" in
     * /sys/class/net/name-of-dev/ .  This second method is more complex
     * but more reliable.
     */
    gboolean ret = FALSE;
    char *path = malloc (strlen (device) + strlen ("/sys/class/net//device") + 1);

    if (path == NULL)
        return FALSE;

    /* Check if /sys/class/net/name-of-dev/ exists (may be old linux kernel
     * or not linux at all). */
    do {
        if (sprintf(path, "/sys/class/net/%s", device) < 0)
            break;
        if (access(path, F_OK) != 0)
            break; /* unknown */

        if (sprintf(path, "/sys/class/net/%s/device", device) < 0)
            break;
        if (access(path, F_OK) != 0)
            ret = TRUE;
    } while (0);

    free (path);
    return ret;
}

void
GetNet (int Maximum, int data [4], LoadGraph *g)
{
    enum Types {
        IN_COUNT = 0,
        OUT_COUNT = 1,
        LOCAL_COUNT = 2,
        COUNT_TYPES = 3
    };

    static int ticks = 0;
    static gulong past[COUNT_TYPES] = {0};
    static AutoScaler scaler;

    gulong present[COUNT_TYPES] = {0};

    guint i;
    gchar **devices;
    glibtop_netlist netlist;

    if(ticks == 0)
    {
        autoscaler_init(&scaler, 60, 501);
    }

    devices = glibtop_get_netlist(&netlist);

    for(i = 0; i < netlist.number; ++i)
    {
        glibtop_netload netload;

        glibtop_get_netload(&netload, devices[i]);

        g_return_if_fail((netload.flags & needed_netload_flags) == needed_netload_flags);

        if (!(netload.if_flags & (1L << GLIBTOP_IF_FLAGS_UP)))
            continue;

        if (netload.if_flags & (1L << GLIBTOP_IF_FLAGS_LOOPBACK)) {
            /* for loopback in and out are identical, so only count in */
            present[LOCAL_COUNT] += netload.bytes_in;
            continue;
        }

        /*
         * Do not include virtual devices (VPN, PPPOE...) to avoid
         * counting the same throughput several times.
         */
        if (is_net_device_virtual(devices[i]))
            continue;

        present[IN_COUNT] += netload.bytes_in;
        present[OUT_COUNT] += netload.bytes_out;
    }

    g_strfreev(devices);
    netspeed_add(g->netspeed_in, present[IN_COUNT]);
    netspeed_add(g->netspeed_out, present[OUT_COUNT]);

    if(ticks < 2) /* avoid initial spike */
    {
        ticks++;
        memset(data, 0, COUNT_TYPES * sizeof data[0]);
    }
    else
    {
        int delta[COUNT_TYPES];
        int total = 0;

        for (i = 0; i < COUNT_TYPES; i++)
        {
            /* protect against weirdness */
            if (present[i] >= past[i])
                delta[i] = (present[i] - past[i]);
            else
                delta[i] = 0;
            total += delta[i];
        }

        //max = autoscaler_get_max(&scaler, total);

        for (i = 0; i < COUNT_TYPES; i++)
            data[i]   = delta[i];

    }

    memcpy(past, present, sizeof past);
}