/*
    wmcpuwatch - WindowMaker dock app to watch all CPUs of a system
    Copyright (C) 2017 Andreas Tscharner <andy@vis.ethz.ch>

    This program 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.

    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/** \file wmcpuwatch.c
  *
  * This is the main (and only source) file for the wmcpuwatch dockapp.
  *
  * The code is based on <a href="http://www.dockapps.net/wmmon">wmmon</a>,
  * but lots of code has been removed, only the CPU code was left.
  *
  * See the ChangeLog file for changes
  *
  * \author wmmon authors
  * \author Andreas Tscharner <andy@vis.ethz.ch>
  * \date 2017-09-03
  * \copyright GNU General Public License, v3
  */


#define _GNU_SOURCE      /*!< Use GNU extensions */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <sys/wait.h>

#include <libdockapp/wmgeneral.h>
#include <libdockapp/misc.h>

#include "ulllib.h"
#include "wmcpuwatch-master.xpm"
#include "wmcpuwatch-mask.xbm"

#include "config.h"

#define MAX_CPU (40)     /*!< Maximum number of CPUs: height of the lower frame */
#define MAX_HEIGHT (9)   /*!< Maximum height of one row */


/* Type definition */
/** \struct stat_dev
  * \brief Struct to keep CPU load data
  *
  * This struct is used to keep the several CPU load data, needed to display
  * them properly
  */
typedef struct {
	long rt_stat;        //!< Field for current average cpu load
	ullong statlast;     //!< Field for the last value of the average cpu load
	long rt_idle;        //!< Field for current average cpu idle time
	ullong idlelast;     //!< Field for the last average idle time value of the cpu
	/* Processors stats */
	long *cpu_stat;      //!< Fields for all cpu loads
	ullong *cpu_last;    //!< Values for last cpu load
	long *idle_stat;     //!< Fields for all cpu idle times
	ullong *idle_last;   //!< Values for last idle cpu times
} stat_dev;


/* Forward declarations
   See the implementation for detailed description */
void usage(char*);
void print_version(void);
void wmcpuwatch(int, char **);
void clear_widgets(void);
void initialize_stat_device(stat_dev *, int, const char *);
int get_number_of_CPU(FILE *);
void get_statistics(FILE *, ullong *, ullong *, ullong *, ullong *);
void update_stat_cpu(FILE *, stat_dev *, ullong *, ullong *, int);
unsigned long get_width(long, long, long);


/**
  * \mainpage wmcpuwatch
  *
  * \image html wmcpuwatch.png "wmpcuwatch dockapp"
  *
  * \b wmcpuwatch displays the average CPU load in the upper left widget and
  * the load of all CPUs in the lower area. It can display the load for up
  * to 40 CPUs
  *
  * Call \b wmcpuwatch with the option \c \-h to display all possible
  * command line arguments
  *
  * \see wmcpuwatch.c
  */

/** \brief Main function
  *
  * The main function parses the command line parameters and starts the
  * actual dockapp
  */
int main(int argc, char *argv[])
{
	char *name = argv[0];

	/* Parse Command Line */
	for (int i = 1; i < argc; i++) {
		char *arg = argv[i];

		if (*arg=='-')
			switch (arg[1]) {
			case 'd' :
				if (strcmp(arg+1, "display")) {
					usage(name);
					return 1;
				}
				break;
			case 'g' :
				if (strcmp(arg+1, "geometry")) {
					usage(name);
					return 1;
				}
			case 'v' :
				print_version();
				return 0;
			default:
				usage(name);
				return 1;
			}
	}

	wmcpuwatch(argc, argv);
	exit(0);
}


/** \brief Actual function for the dockapp
  *
  * This is the actual function for the dockapp. It initializes all the
  * data and contains the main loop.
  *
  * \param argc Number of arguments
  * \param argv Arguments
  */
void wmcpuwatch(int argc, char **argv)
{
    FILE *fp_stat;
    XEvent Event;
    ullong istat, idle, *istat2, *idle2;
    stat_dev stat_device;
    int nb_cpu;


    fp_stat = fopen("/proc/stat", "r");
    nb_cpu = get_number_of_CPU(fp_stat);

    initialize_stat_device(&stat_device, nb_cpu, argv[0]);

    istat2 = calloc(nb_cpu, sizeof(ullong));
    idle2 = calloc(nb_cpu, sizeof(ullong));
    if (!istat2 || !idle2) {
        fprintf(stderr, "%s: Unable to alloc memory !!\n", argv[0]);
        exit(1);
    }

    openXwindow(argc, argv, wmcpuwatch_master_xpm, (char *)wmcpuwatch_mask_bits,
        wmcpuwatch_mask_width, wmcpuwatch_mask_height);

    /* Collect information on each panel */
    get_statistics(fp_stat, &istat, &idle, istat2, idle2);
    stat_device.statlast = istat;
    stat_device.idlelast = idle;

    for (int cpu = 0; cpu < nb_cpu; cpu++) {
        stat_device.cpu_last[cpu] = istat2[cpu];
        stat_device.idle_last[cpu] = idle2[cpu];
    }

    while (1) {
        waitpid(0, NULL, WNOHANG);

        update_stat_cpu(fp_stat, &stat_device, istat2, idle2, nb_cpu);

        /* Clear both widgets */
        clear_widgets();

        /* show average CPU */
        int j = get_width(stat_device.rt_stat, stat_device.rt_idle, 32);
        copyXPMArea(32, 64, j, 12, 28, 4);

        /* show all CPUs */
        int h = (MAX_CPU / nb_cpu) > MAX_HEIGHT ? MAX_HEIGHT : MAX_CPU / nb_cpu;
        for (int cpu = 0; cpu < nb_cpu; cpu++) {
            j = get_width(stat_device.cpu_stat[cpu], stat_device.idle_stat[cpu], 56);
            copyXPMArea(1, 95, j, h, 5, 19 + h * cpu);
        }

        RedrawWindow();

        while (XPending(display)) {
            XNextEvent(display, &Event);
            switch (Event.type) {
                case Expose:
                    RedrawWindow();
                    break;
                case DestroyNotify:
                    XCloseDisplay(display);
                    exit(0);
                    break;
            }
        }
        usleep(250000L);
    }

    fclose(fp_stat);
}


/** \brief Clear both widgets
  *
  * This function clears the upper right (smaller) widget for the average
  * load over all CPUs and the lower (big) widget for the load of all CPUs
  */
void clear_widgets(void)
{
        /* Clear average load */
        copyXPMArea(0, 64, 32, 12, 28,  4);

        /* Clear full field */
        copyXPMArea(0, 80, 56, 10,  4, 18);
        copyXPMArea(0, 81, 56,  9,  4, 27);
        copyXPMArea(0, 81, 56,  9,  4, 36);
        copyXPMArea(0, 81, 56,  9,  4, 45);
        copyXPMArea(0, 81, 56,  4,  4, 54);
        copyXPMArea(0, 75, 56,  1,  4, 58);
}

/** \brief Initialize stat_device struct
  *
  * This function initializes and allocates memory for the main structure
  * for the device stats
  *
  * \param statdevice Pointer to main stat_dev struct
  * \param cpucount Number of CPUs
  * \param dockappname Name of the dockapp
  */
void initialize_stat_device(stat_dev *statdevice, int cpucount, const char *dockappname)
{
    statdevice->cpu_stat = calloc(cpucount, sizeof(long));
    statdevice->cpu_last = calloc(cpucount, sizeof(ullong));
    statdevice->idle_stat = calloc(cpucount, sizeof(long));
    statdevice->idle_last = calloc(cpucount, sizeof(ullong));
    if (!statdevice->cpu_stat ||
        !statdevice->cpu_last ||
        !statdevice->idle_stat ||
        !statdevice->idle_last) {
            fprintf(stderr, "%s: Unable to alloc memory !\n", dockappname);
            exit(1);
    }
}

/** \brief Update CPU stats
  *
  * This function is used to update the given data
  *
  * \param fpstat File pointer to open \c /proc/stat
  * \param st Pointer to data structure to update
  * \param istat2 Field used for average and last cpu load
  * \param idle2 Field used for average and last cpu idle time
  * \param cpucount Number of CPUs
  */
void update_stat_cpu(FILE *fpstat, stat_dev *st, ullong *istat2, ullong *idle2, int cpucount)
{
	ullong istat, idle;

	get_statistics(fpstat, &istat, &idle, istat2, idle2);

	st->rt_idle = ullsub(&idle, &st->idlelast);
	st->idlelast = idle;

	st->rt_stat = ullsub(&istat, &st->statlast);
	st->statlast = istat;

    for (int cpu = 0; cpu < cpucount; cpu++) {
        st->idle_stat[cpu] = ullsub(&idle2[cpu], &st->idle_last[cpu]);
        st->idle_last[cpu] = idle2[cpu];

        st->cpu_stat[cpu] = ullsub(&istat2[cpu], &st->cpu_last[cpu]);
        st->cpu_last[cpu] = istat2[cpu];
    }
}


/** \brief Parses \c /proc/stat and gets required values
  *
  * This function parses \c /proc/stat and saves the required values to the
  * given variables
  *
  * \param fpstat File pointer to open \c /proc/stat
  * \param ds "Array" for the cpu load of all CPUs
  * \param idle "Array" for the cpu idle time of all CPUs
  * \param ds2 Field for the average cpu load
  * \param idle2 Field for average cpu idle time
  */
void get_statistics(FILE *fpstat, ullong *ds, ullong *idle, ullong *ds2, ullong *idle2)
{
    static char *line = NULL;
    static size_t line_size = 0;
    char *p;
    char *tokens = " \t\n";
    ullong ulltmp;


    ullreset(ds);
	ullreset(idle);

    fseek(fpstat, 0, SEEK_SET);
    while ((getline(&line, &line_size, fpstat)) > 0) {
        if (strstr(line, "cpu")) {
            int cpu = -1;	/* by default, cumul stats => average */
            if (!strstr(line, "cpu ")) {
                sscanf(line, "cpu%d", &cpu);
                ullreset(&ds2[cpu]);
                ullreset(&idle2[cpu]);
            }

            p = strtok(line, tokens);
            /* 1..3, 4 == idle, we don't want idle! */
            for (int i=0; i<3; i++) {
                p = strtok(NULL, tokens);
                ullparse(&ulltmp, p);
                if (cpu == -1)
                    ulladd(ds, &ulltmp);
                else
                    ulladd(&ds2[cpu], &ulltmp);
            }

            p = strtok(NULL, tokens);
            if (cpu == -1)
                ullparse(idle, p);
            else
                ullparse(&idle2[cpu], p);
        }
    }
}


/** \brief Get widht of active part
  *
  * This functions returns the width of the active part of the row
  *
  * \param actif CPU load value
  * \param idle CPU idle value
  * \param line_len Actual total length of row
  *
  * \returns Length in pixels of active part
  */
unsigned long get_width(long actif, long idle, long line_len)
{
	/* wbk - work with a decimal value so we don't round < 1 down to zero.  */
	double j = 0;

	j = (actif + idle);
	if (j != 0)
		j = (actif * 100) / j;

	j = j * (double)(line_len / 100.0);

	/* round up very low positive values so they are visible. */
	if (actif > 0 && j < 2)
		j = 2;

	if (j > line_len)
		j = (double)line_len;

	return (unsigned long) j;
}


/** \brief Returns the number of CPUs
  *
  * This function returns the number of logical CPUs in the current system
  *
  * \param fpstat File pointer to open \c /proc/stat
  *
  * \returns Number of logical CPUs
  */
int get_number_of_CPU(FILE *fpstat)
{
	static char *line = NULL;
	static size_t line_size = 0;
	int cpu = 0;

	fseek(fpstat, 0, SEEK_SET);
	while ((getline(&line, &line_size, fpstat)) > 0) {
		if (strstr(line, "cpu") && !strstr(line, "cpu "))
			sscanf(line, "cpu%d", &cpu);
	}

	return cpu+1;
}


/** /brief Show usage
  *
  * This function shows all possible command line arguments together with
  * a small disclaimer
  *
  * \param name Program name as it was called
  */
void usage(char *name)
{
    printf("\n    wmcpuwatch  Copyright (C) 2017  Andreas Tscharner\n");
    printf("    This program comes with ABSOLUTELY NO WARRANTY;\n");
    printf("    This is free software, and you are welcome to\n");
    printf("    redistribute it under certain conditions;\n");
    printf("\n\n");
    printf("Usage: %s [OPTION]...\n", name);
    printf("WindowMaker dockapp that displays the cpu load of all CPUs.\n");
    printf("  -display DISPLAY     contact the DISPLAY X server\n");
    printf("  -geometry GEOMETRY   position the dockapp at GEOMETRY\n");
    printf("  -h                   display this help and exit\n");
    printf("  -v                   output version information and exit\n");
}


/** \fn print_version
  * \brief Shows the current version of the dockapp
  */

/** \brief Definition of current version
  *
  * Definition of the current program version
  */
void print_version(void)
{
    printf("wmcpuwatch version %s\n", PACKAGE_VERSION);
}
