/*
 * IceWM
 *
 * Copyright (C) 1998-2001 Marko Macek
 *
 * CPU Status
 *
 * For eWorkPanel by Mikko Lahteenmaki 2003
 */
#include "cpumonitor.h"

#include <limits.h>
#include <unistd.h>
#include <stdarg.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#if defined(linux)
//#include <linux/kernel.h>
#include <sys/sysinfo.h>
#endif

#ifdef HAVE_KSTAT_H
#include <kstat.h>
#endif

#ifdef __FreeBSD__
#include <sys/param.h>
#include <sys/sysctl.h>
#if __FreeBSD_version < 500101
#include <sys/dkstat.h>
#else
#include <sys/resource.h>
#endif
#include <sys/stat.h>
#endif



#ifdef __FreeBSD__

/* The part ripped from top... */
/*
 *  Top users/processes display for Unix
 *  Version 3
 *
 *  This program may be freely redistributed,
 *  but this entire comment MUST remain intact.
 *
 *  Copyright (c) 1984, 1989, William LeFebvre, Rice University
 *  Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
 */

/*
 *  percentages(cnt, out, new, old, diffs) - calculate percentage change
 *	between array "old" and "new", putting the percentages i "out".
 *	"cnt" is size of each array and "diffs" is used for scratch space.
 *	The array "old" is updated on each call.
 *	The routine assumes modulo arithmetic.  This function is especially
 *	useful on BSD mchines for calculating cpu state percentages.
 */

static long cp_time[CPUSTATES];
static long cp_old[CPUSTATES];
static long cp_diff[CPUSTATES];
static int cpu_states[CPUSTATES];

long percentages(int cnt, int *out, long *my_new, long *old, long *diffs)
{
    register int i;
    register long change;
    register long total_change;
    register long *dp;
    long half_total;

    /* initialization */
    total_change = 0;
    dp = diffs;

    /* calculate changes for each state and the overall change */
    for (i = 0; i < cnt; i++)
    {
	if ((change = *my_new - *old) < 0)
	{
	    /* this only happens when the counter wraps */
	    change = (int)
		((unsigned long)*my_new-(unsigned long)*old);
	}
	total_change += (*dp++ = change);
	*old++ = *my_new++;
    }

    /* avoid divide by zero potential */
    if (total_change == 0)
    {
	total_change = 1;
    }

    /* calculate percentages based on overall change, rounding up */
    half_total = total_change / 2l;

    /* Do not divide by 0. Causes Floating point exception */
    if(total_change) {
        for (i = 0; i < cnt; i++)
        {
          *out++ = (int)((*diffs++ * 1000 + half_total) / total_change);
        }
    }

    /* return the total in case the caller wants to use it */
    return(total_change);
}

#endif /* freebsd */



#define UPDATE_INTERVAL .5f

void cpu_timeout_cb(void *d) {
    ((CPUMonitor*)d)->update_status();
    Fl::repeat_timeout(UPDATE_INTERVAL, cpu_timeout_cb, d);
}

CPUMonitor::CPUMonitor()
: Fl_Widget(0,0,30,0)
{
    box(FL_THIN_DOWN_BOX);
    //box(FL_BORDER_BOX);

    m_draw_label = true;
    m_samples = m_old_samples = -1;
    cpu = 0;

    colors[IWM_USER] = FL_RED;
    colors[IWM_NICE] = FL_GREEN;
    colors[IWM_SYS]  = FL_DARK3;
    colors[IWM_IDLE] = FL_NO_COLOR;

    Fl::add_timeout(UPDATE_INTERVAL, cpu_timeout_cb, this);
}

CPUMonitor::~CPUMonitor() {
    clear();
}

void CPUMonitor::clear()
{
    if(!cpu) return;

    for (int i=0; i < samples(); i++) {
        delete cpu[i]; cpu[i] = 0;
    }
    delete cpu;
    cpu = 0;

    m_old_samples = -1;
}

void CPUMonitor::draw()
{
    if(!cpu) {
        fl_push_clip(0,0,w(),h());
        parent()->draw_group_box();
        draw_frame();
        fl_pop_clip();
        return;
    }

    if(colors[IWM_IDLE] == FL_NO_COLOR) {
        fl_push_clip(0,0,w(),h());
        parent()->draw_group_box();
        fl_pop_clip();
    }
    draw_frame();

    int n, h = height() - box()->dh();

    int c=0;
    for (int i=box()->dx(); i < samples()+box()->dx(); i++)
    {
        int user  = cpu[c][IWM_USER];
        int nice  = cpu[c][IWM_NICE];
        int sys   = cpu[c][IWM_SYS];
        int idle  = cpu[c][IWM_IDLE];
        int total = user + sys + nice + idle;

        c++;

        int y = height() - 1 - box()->dy();

        if (total > 0)
        {
            if (sys) {
                n = (h * (total - sys)) / total; // check rounding
                if (n >= y) n = y;
                if (n < 1) n = 1;
                fl_color(colors[IWM_SYS]);
                fl_line(i, y, i, n);
                y = n - 1;
            }

            if (nice) {
                n = (h * (total - sys - nice))/ total;
                if (n >= y) n = y;
                if (n < 1) n = 1;
                fl_color(colors[IWM_NICE]);
                fl_line(i, y, i, n);
                y = n - 1;
            }

            if (user) {
                n = (h * (total - sys - nice - user))/ total;
                if (n >= y) n = y;
                if (n < 1) n = 1;
                fl_color(colors[IWM_USER]);
                fl_line(i, y, i, n);
                y = n - 1;
            }
        }

        if (idle) {
            if(colors[IWM_IDLE] != FL_NO_COLOR)
            {
                fl_color(colors[IWM_IDLE]);
                fl_line(i, box()->dy(), i, y);
            }
        }
    }

    int cpu_percent = cpu[samples()-1][0]*2;
    if(m_draw_label && cpu_percent<=100) {
        Fl_String l(cpu_percent);
        l += '%';
        label(l);
        draw_inside_label();
    }
}

void CPUMonitor::layout()
{
    label_size(h()/2);

    w(h()*2);
    m_samples = w() - box()->dw();

    if(!cpu || m_old_samples != m_samples) {
        clear();

        cpu = new int*[m_samples];
        for(int i=0; i < m_samples; i++) {
            cpu[i] = new int[IWM_STATES];
            cpu[i][IWM_USER] = cpu[i][IWM_NICE] = cpu[i][IWM_SYS] = 0;
            cpu[i][IWM_IDLE] = 1;
        }
        last_cpu[IWM_USER] = last_cpu[IWM_NICE] = last_cpu[IWM_SYS] = last_cpu[IWM_IDLE] = 0;

        update_status();
        m_old_samples = m_samples;
    }

    Fl_Widget::layout();
}

void CPUMonitor::update_status()
{
    if(!cpu) return;

    for (int i=1; i < samples(); i++) {
        cpu[i - 1][IWM_USER] = cpu[i][IWM_USER];
        cpu[i - 1][IWM_NICE] = cpu[i][IWM_NICE];
        cpu[i - 1][IWM_SYS]  = cpu[i][IWM_SYS];
        cpu[i - 1][IWM_IDLE] = cpu[i][IWM_IDLE];
    }

    get_cpu_info();

    // Update tooltip
    char load[255];
    snprintf(load, sizeof(load)-1,
             _("CPU Load:\n"
             "User: %d%%\n"
             "Nice: %d%%\n"
             "Sys:  %d%%\n"
             "Idle: %d%%"),
             cpu[samples()-1][0]*2, cpu[samples()-1][1]*2,
             cpu[samples()-1][2]*2, cpu[samples()-1][3]*2);
    tooltip(load);

    redraw();
}

void CPUMonitor::get_cpu_info()
{
    if(!cpu) return;

#ifdef linux
    char *p, buf[128];
    long cur[IWM_STATES];
    int len, fd = open("/proc/stat", O_RDONLY);

    cpu[samples()-1][IWM_USER] = 0;
    cpu[samples()-1][IWM_NICE] = 0;
    cpu[samples()-1][IWM_SYS] = 0;
    cpu[samples()-1][IWM_IDLE] = 0;

    if (fd == -1)
        return;
    len = read(fd, buf, sizeof(buf) - 1);
    if (len != sizeof(buf) - 1) {
        close(fd);
        return;
    }
    buf[len] = 0;

    p = buf;
    while (*p && (*p < '0' || *p > '9'))
        p++;

    for (int i = 0; i < 4; i++) {
        cur[i] = strtoul(p, &p, 10);
        cpu[samples()-1][i] = cur[i] - last_cpu[i];
        last_cpu[i] = cur[i];
    }
    close(fd);
#if 0
    fprintf(stderr, "cpu: %d %d %d %d",
            cpu[samples()-1][IWM_USER], cpu[samples()-1][IWM_NICE],
            cpu[samples()-1][IWM_SYS],  cpu[samples()-1][IWM_IDLE]);
#endif

#endif /* linux */

#ifdef HAVE_KSTAT_H
# ifdef HAVE_OLD_KSTAT
# define ui32 ul
#endif

    static kstat_ctl_t  *kc = NULL;
    static kid_t        kcid;
    kid_t               new_kcid;
    kstat_t             *ks = NULL;
    kstat_named_t       *kn = NULL;
    int                 changed,change,total_change;
    unsigned int        thiscpu;
    register int        i,j;
    static unsigned int ncpus;
    static kstat_t      **cpu_ks=NULL;
    static cpu_stat_t   *cpu_stat=NULL;
    static long         cp_old[CPU_STATES];
    long                cp_time[CPU_STATES], cp_pct[CPU_STATES];

    /* Initialize the kstat */
    if (!kc) {
        kc = kstat_open();
        if (!kc) {
            perror("kstat_open ");
            return;/* FIXME : need err handler? */
        }
        changed = 1;
        kcid = kc->kc_chain_id;
        fcntl(kc->kc_kd, F_SETFD, FD_CLOEXEC);
    } else {
        changed = 0;
    }
    /* Fetch the kstat data. Whenever we detect that the kstat has been
     changed by the kernel, we 'continue' and restart this loop.
     Otherwise, we break out at the end. */
    while (1) {
        new_kcid = kstat_chain_update(kc);
        if (new_kcid) {
            changed = 1;
            kcid = new_kcid;
        }
        if (new_kcid < 0) {
            perror("kstat_chain_update ");
            return;/* FIXME : need err handler? */
        }
        if (new_kcid != 0)
            continue; /* kstat changed - start over */
        ks = kstat_lookup(kc, "unix", 0, "system_misc");
        if (kstat_read(kc, ks, 0) == -1) {
            perror("kstat_read ");
            return;/* FIXME : need err handler? */
        }
        if (changed) {
            /* the kstat has changed - reread the data */
            thiscpu = 0; ncpus = 0;
            kn = (kstat_named_t *)kstat_data_lookup(ks, "ncpus");
            if ((kn) && (kn->value.ui32 > ncpus)) {
                /* I guess I should be using 'new' here... FIXME */
                ncpus = kn->value.ui32;
                if ((cpu_ks = (kstat_t **)
                     realloc(cpu_ks, ncpus * sizeof(kstat_t *))) == NULL)
                {
                    perror("realloc: cpu_ks ");
                    return;/* FIXME : need err handler? */
                }
                if ((cpu_stat = (cpu_stat_t *)
                     realloc(cpu_stat, ncpus * sizeof(cpu_stat_t))) == NULL)
                {
                    perror("realloc: cpu_stat ");
                    return;/* FIXME : need err handler? */
                }
            }
            for (ks = kc->kc_chain; ks; ks = ks->ks_next) {
                if (strncmp(ks->ks_name, "cpu_stat", 8) == 0) {
                    new_kcid = kstat_read(kc, ks, NULL);
                    if (new_kcid < 0) {
                        perror("kstat_read ");
                        return;/* FIXME : need err handler? */
                    }
                    if (new_kcid != kcid)
                        break;
                    cpu_ks[thiscpu] = ks;
                    thiscpu++;
                    if (thiscpu > ncpus) {
                        fprintf(stderr, "kstat finds too many cpus: should be %d", ncpus);
                        return;/* FIXME : need err handler? */
                    }
                }
            }
            if (new_kcid != kcid)
                continue;
            ncpus = thiscpu;
            changed = 0;
        }
        for (i = 0; i<(int)ncpus; i++) {
            new_kcid = kstat_read(kc, cpu_ks[i], &cpu_stat[i]);
            if (new_kcid < 0) {
                perror("kstat_read ");
                return;/* FIXME : need err handler? */
            }
        }
        if (new_kcid != kcid)
            continue; /* kstat changed - start over */
        else
            break;
    } /* while (1) */

    /* Initialize the cp_time array */
    for (i = 0; i < CPU_STATES; i++)
        cp_time[i] = 0L;
    for (i = 0; i < (int)ncpus; i++) {
        for (j = 0; j < CPU_STATES; j++)
            cp_time[j] += (long) cpu_stat[i].cpu_sysinfo.cpu[j];
    }
    /* calculate the percent utilization for each category */
    /* cpu_state calculations */
    total_change = 0;
    for (i = 0; i < CPU_STATES; i++) {
        change = cp_time[i] - cp_old[i];
        if (change < 0) /* The counter rolled over */
            change = (int) ((unsigned long)cp_time[i] - (unsigned long)cp_old[i]);
        cp_pct[i] = change;
        total_change += change;
        cp_old[i] = cp_time[i]; /* copy the data for the next run */
    }
    /* this percent calculation isn't really needed, since the repaint
     routine takes care of this... */
    for (i = 0; i < CPU_STATES; i++)
        cp_pct[i] =
            ((total_change > 0) ?
             ((int)(((1000.0 * (float)cp_pct[i]) / total_change) + 0.5)) :
             ((i == CPU_IDLE) ? (1000) : (0)));

    /* OK, we've got the data. Now copy it to cpu[][] */
    cpu[samples()-1][IWM_USER] = cp_pct[CPU_USER];
    cpu[samples()-1][IWM_NICE] = cp_pct[CPU_WAIT];
    cpu[samples()-1][IWM_SYS]  = cp_pct[CPU_KERNEL];
    cpu[samples()-1][IWM_IDLE] = cp_pct[CPU_IDLE];

#endif /* have_kstat_h */


#ifdef __FreeBSD__
//    char *p, buf[128];
//    long cur[IWM_STATES];
//    int len, fd = open("/proc/stat", O_RDONLY);
    size_t len = sizeof(cp_time);

    cpu[samples()-1][IWM_USER] = 0;
    cpu[samples()-1][IWM_NICE] = 0;
    cpu[samples()-1][IWM_SYS] = 0;
    cpu[samples()-1][IWM_IDLE] = 0;

    if (sysctlbyname("kern.cp_time", &cp_time, &len, NULL, 0) == -1)
        return; /* FIXME : need err handler? */

    percentages(CPUSTATES, cpu_states, cp_time, cp_old, cp_diff);

    // Translate FreeBSD stuff into ours (probably the same thing anyway)
    cpu[samples()-1][IWM_USER] = cp_diff[CP_USER];
    cpu[samples()-1][IWM_NICE] = cp_diff[CP_NICE];
    cpu[samples()-1][IWM_SYS] = cp_diff[CP_SYS];
    cpu[samples()-1][IWM_IDLE] = cp_diff[CP_IDLE];

#if 0
    fprintf(stderr, "cpu: %d %d %d %d\n",
            cpu[samples()-1][IWM_USER], cpu[samples()-1][IWM_NICE],
            cpu[samples()-1][IWM_SYS],  cpu[samples()-1][IWM_IDLE]);
#endif

#endif /* freebsd */
}

