/*
   A colorful `free' clone with graphs and such.
*/
/*
 * Copyright (c) 2004, 2005, 2009, 2013, 2014  Roy Keene
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_LIBSTATGRAB) && !defined(HAVE_LIBSTATGRAB_H)
#undef HAVE_LIBSTATGRAB
#endif
#include <assert.h>
#include <stdint.h>

#ifndef HAVE_LIBSTATGRAB
#include <linux/kernel.h>
#include <sys/sysinfo.h>
#ifndef PROC_MEMINFO
#define PROC_MEMINFO "/proc/meminfo"
#endif
#else
#include <statgrab.h>
#endif
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#ifndef NO_GETOPT
#include <getopt.h>
#endif
#define BARLEN 35
#define HEADERLEN 14
#define VERSION "0.9.3"

extern char *optarg;
extern int optind, opterr, optopt;

struct freecolor_meminfo {
	uint64_t totalram;  /* Total usable main memory size */
	uint64_t freeram;   /* Available memory size */
	uint64_t sharedram; /* Amount of shared memory */
	uint64_t bufferram; /* Memory used by buffers */
	uint64_t cachedram; /* Memory used for I/O cache */
	uint64_t totalswap; /* Total swap space size */
	uint64_t freeswap;  /* swap space still available */
};

void bargraph(double percent, double secondper, char marks[BARLEN + HEADERLEN + 1], int usefull) {
	char percentone[BARLEN + 1], percenttwo[BARLEN + 1], remain[BARLEN + 1];
	unsigned int numberofmarks, numofmarkstwo, remainnum;

	numberofmarks=(int) ((double) (BARLEN * (percent / 100)));

	assert(numberofmarks <= BARLEN);

	if (!usefull) {
		numofmarkstwo = (int) ((double) (BARLEN * (secondper / 100)));
	} else {
		numofmarkstwo = (BARLEN - numberofmarks);
	}

	assert(numofmarkstwo <= BARLEN);

	remainnum = BARLEN - (numberofmarks + numofmarkstwo);

	assert(remainnum <= BARLEN);

	memset(percentone, '#', numberofmarks);
	memset(percenttwo, '%', numofmarkstwo);
	memset(remain, '.', remainnum);

	percentone[numberofmarks] = 0;
	percenttwo[numofmarkstwo] = 0;

	remain[remainnum] = 0;

	snprintf(marks, BARLEN + HEADERLEN + 1, "%s\033[1;31m%s\033[1;37m%s", percentone, percenttwo, remain);

	return;
}

#ifndef HAVE_LIBSTATGRAB
/*
  Extracted alot from sysinfo.c (part of the procps package?) by
  Michael K. Johnson <johnsonm@redhat.com>
*/
unsigned long get_meminfo(char searchfor[12]) {
	FILE *fd;
	unsigned long value = 0, results = 0;
	char buffer[256];
	char fieldname[12];

	fd = fopen(PROC_MEMINFO, "r");
	if (fd == NULL) {
		return(0);
	}

	while (!feof(fd)) {
		fgets(buffer, sizeof(buffer), fd);
		sscanf(buffer,"%11s%lu",fieldname,&value);
		if (strcmp(fieldname, searchfor) == 0) {
			results+=(value*1024);
		}
	}

	fclose(fd);

	return(results);
}

int get_all_meminfo(struct freecolor_meminfo *retval) {
	struct sysinfo sinfo;
	uint64_t meminfo_cached;
	uint64_t multiplier;

	meminfo_cached = get_meminfo("Cached:");
	sysinfo(&sinfo);

#ifdef HAVE_SYSINFO_MEM_UNIT
	multiplier = sinfo.mem_unit;
#else
	multiplier = 1;
#endif

	retval->totalram  = sinfo.totalram * multiplier;
	retval->freeram   = sinfo.freeram * multiplier;
	retval->sharedram = sinfo.sharedram * multiplier;
	retval->bufferram = sinfo.bufferram * multiplier;
	retval->totalswap = sinfo.totalswap * multiplier;
	retval->freeswap  = sinfo.freeswap * multiplier;

	retval->cachedram = meminfo_cached;

	return(0);
}
#else
int get_all_meminfo(struct freecolor_meminfo *retval) {
	static int init_called = 0;
	sg_mem_stats *mem_stats;
	sg_swap_stats *swap_stats;

	if (init_called == 0) {
		init_called = 1;

		sg_init();

#ifdef HAVE_STATGRAB_DROP_PRIVILEGES
		/* Drop setuid/setgid privileges. */
		if (sg_drop_privileges() != 0) {
			perror("Error. Failed to drop privileges");
			return(-1);
		}
#endif
	}

	mem_stats = sg_get_mem_stats();
	swap_stats = sg_get_swap_stats();

	if (mem_stats == NULL || swap_stats == NULL) {
		return(-1);
	}

	retval->totalram  = mem_stats->total;
	retval->freeram   = mem_stats->free;
	retval->cachedram = mem_stats->cache;
	retval->sharedram = 0;
	retval->bufferram = 0;
	retval->totalswap = swap_stats->total;
	retval->freeswap  = swap_stats->free;

#ifdef HAVE_SG_FREE_MEM_STATS
	sg_free_mem_stats(mem_stats);
#endif
#ifdef HAVE_SG_FREE_SWAP_STATS
	sg_free_swap_stats(swap_stats);
#endif

	return(0);
}
#endif

int main(int argc, char **argv) {
	struct freecolor_meminfo sinfo;
	uint64_t cachedbuffer, divisor;
	double percentram, percentswap, percentbuffer, percentused, percentfree;
	double ramfree, ramtotal, swapfree, swaptotal, doloop, totaltotal;
	char realbarchart[BARLEN  +HEADERLEN + 1], swapbarchart[BARLEN + HEADERLEN + 1], totalbarchart[BARLEN + HEADERLEN + 1];
	int i, dototals, doold, linestoup;
	int gam_ret;

	/* Set defaults */
	divisor = 1024;
	doold = 0;
	linestoup = 2;
	dototals = 0;
	doloop = 0;

	/* Get arguments */
#ifdef NO_GETOPT
	for (i=1;i<argc;i++) {
		if (argv[i][0] != '-') {
			break;
		}

		switch(argv[i][1]) {
			case 's' : 
				if (argc > (i+1)) {
					doloop=atof(argv[i+1]); i++;
				}
				break;
#else
	while ((i=getopt(argc, argv, "obkmVs:t")) != -1) {
		switch(i) {
			case 's' : 
				doloop=atof(optarg);
				break;
#endif /* NO_GETOPT */
			case 'b' : divisor=1; break;
			case 'k' : divisor=1024; break;
			case 'm' : divisor=1048576; break;
			case 'V' : printf("freecolor version %s\n", VERSION); return(0);
			case 't' : dototals=1; linestoup++; break;
			case 'o' : doold=1; linestoup++; break;
			case '?' :  printf("usage: %s [-b|-k|-m] [-o] [-t] [-s delay] [-V]\n",argv[0]); return(1);
			default  : printf("%s: illegal option -- %c\n",argv[0],argv[i][1]); printf("usage: %s [-b|-k|-m] [-t] [-s delay] [-V]\n",argv[0]); return(1);
		}
  	}

	/* Display information */
	while(1) {
		gam_ret = get_all_meminfo(&sinfo);
		if (gam_ret < 0) {
			fprintf(stderr, "Error getting memory information.\n");

			return(1);
		}

		ramtotal  = sinfo.totalram;
		ramfree   = sinfo.freeram;
		swapfree  = sinfo.freeswap;
		swaptotal = sinfo.totalswap;

		totaltotal   = sinfo.totalram + sinfo.totalswap;
		cachedbuffer = sinfo.bufferram + sinfo.cachedram;

		percentram    = (double) ((ramfree / ramtotal) * 100);
		percentbuffer = (double) ((cachedbuffer / ramtotal) * 100);
		percentfree   = (double) (((sinfo.freeram + sinfo.freeswap + cachedbuffer) / totaltotal) * 100);
		percentused   = (int) (100 - ((int) percentfree));

		if (swaptotal!=0) {
			percentswap = (double) (( swapfree / swaptotal) * 100);
		} else {
			percentswap = 0;
		}

		bargraph(percentswap, 0, swapbarchart, 0);
		bargraph(percentram, percentbuffer, realbarchart, 0);
		bargraph((int) percentfree,(int) percentused, totalbarchart,1);

		if (!doold) {
			printf("Physical  : \033[1;30m[\033[1;32m%s\033[1;30m]\033[1;37m %i\033[0;31m%%\033[0m\t(%lu/%lu)\n",
			    realbarchart,
			    (int) (percentram + percentbuffer),
			    (unsigned long) ((ramfree + cachedbuffer) / divisor),
			    (unsigned long) (ramtotal / divisor)
			);

			printf("Swap      : \033[1;30m[\033[1;32m%s\033[1;30m]\033[1;37m %i\033[0;31m%%\033[0m\t(%lu/%lu)\n",
			    swapbarchart,
			     (int) percentswap,
			     (unsigned long) (swapfree / divisor),
			     (unsigned long) (swaptotal/divisor)
			);

			if (dototals) {
				printf("Total     : \033[1;30m[\033[1;32m%s\033[1;30m]\033[1;37m \033[0m(%lu=%lu+%lu)\n",
				    totalbarchart,
				    (unsigned long) ((sinfo.totalram + sinfo.totalswap) / divisor),
				    (unsigned long) ((sinfo.freeram + sinfo.freeswap) / divisor),
				    (unsigned long) ((totaltotal - ((sinfo.freeram + sinfo.freeswap + cachedbuffer))) / divisor)
				);
			}
		} else {
			printf("             total       used       free     shared    buffers     cached\n");
			printf("Mem:  %12lu %10lu %10lu %10lu %10lu %10lu\n",
			    (unsigned long) (sinfo.totalram / divisor),
			    (unsigned long) ((sinfo.totalram - sinfo.freeram) / divisor),
			    (unsigned long) (sinfo.freeram / divisor),
			    (unsigned long) (sinfo.sharedram / divisor),
			    (unsigned long) (sinfo.bufferram / divisor),
			    (unsigned long) (cachedbuffer / divisor)
			); 

			printf("Swap: %12lu %10lu %10lu\n",
			    (unsigned long) (sinfo.totalswap / divisor),
			    (unsigned long) ((sinfo.totalswap - sinfo.freeswap) / divisor),
			    (unsigned long) (sinfo.freeswap / divisor)
			);

			if (dototals) {
				printf("Total: %11lu = (%8lu (used) + %8lu (free))\n",
				    (unsigned long) (totaltotal / divisor),
				    (unsigned long) ((((long) totaltotal) - ((sinfo.freeram+sinfo.freeswap))) / divisor),
				    (unsigned long) ((sinfo.freeram+sinfo.freeswap+((unsigned long) cachedbuffer)) / divisor)
				);
			}
		}

		if (doloop==0) {
			break;
		}

		usleep(doloop * 1000000);
		printf("\033[%iA", linestoup);
	}

	return(0);
}
