/*

my heart beat: used with leds attached to parallel port

08/01/2004 V1.0 released features include
	clear mode
	pretest run
	rotating LEDs in time to the CPU (more CPU used faster LEDs go)
	System usage trend, samples and averaged out and one LED is lite up up turns and one for downturns

todo
	should takee 8 integers as a mapping to the 8 data pins as (like me) they maybe wired a bit oddly from a ~/.led.map file
	accept from cmd line port
	read config file
 	accept from cmd line port or config file
 	delay form cmd or config file
 	trend delay " 		"
	debug runtime option
	kernel module
	work out timings to be more more sensible (i.e. seconds for the trends and 100ths of seconds for the LEDs)
	tidy up code etc
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <asm/io.h>

#define base 0x378           /* printer port base address (note this is for the first parallel port, need to make it configurable) */

#define sdelay	20000		/* we are not bothered by only checking any more though this again should be configurable */

#define	tdelay	10			/* blah blah blah */

void setBm(int, int);
int getBm(int);
int getCPU();

int mappingArray[8];

int OLD_user;
int OLD_idle;
int OLD_nice;
int OLD_system;

int main(int argc, char **argv) {
	int value=255;
	int tmpV;
	int trend; // set to pin 6 or 7 for up or down
	int slept,tslept;
	int newtrend,oldtrend,divtrend;
	OLD_user=0;	// flag for the cpu average to be calculated
	// setup mapping array
	setBm(0,6);
	setBm(1,2);
	setBm(2,4);
	setBm(3,3);
	setBm(4,5);
	setBm(5,1);
	setBm(6,7);
	setBm(7,0);
	if (ioperm(base,1,1))
    	fprintf(stderr, "Couldn't get the port at %x\nYou might need to be root to access the port", base), exit(1);
	// startup loop
	for (value=0;value<8;value++) {
		tmpV = getCPU();
		outb(getBm(value) | 129, base);
		printf("outb(%d) for value %d @ %d\n",getBm(value),value, mappingArray[value]);
    	//getc(stdin);
		usleep(sdelay*10);
	}
	outb(0,base);	// clear all
	slept=0;
	tslept=0;
	value=0;
	trend=getBm(6) | getBm(7);;
	oldtrend=0;
	newtrend=oldtrend;
	divtrend=1;
	while(argc<2) { // any command args and it ends
		tmpV = getCPU();
		//printf("CPU=%d,%d,%d\n",tmpV,slept,value);
		if (slept==0) {
			if (value==5) {	// last LED segment
				value=0;
			} else { value++; }	// to have the LEDs go round in reverse just setup value to decrease from 5 to 0 instead
			outb(getBm(value) | trend, base);
			slept=20;
			if ((tmpV>=0)   && (tmpV<10)) {		slept=20;			}
			if ((tmpV>=10)  && (tmpV<20)) {		slept=18;			}
			if ((tmpV>=20)  && (tmpV<30)) {		slept=16;			}
			if ((tmpV>=30)  && (tmpV<40)) {		slept=14;			}
			if ((tmpV>=40)  && (tmpV<50)) {		slept=12;			}
			if ((tmpV>=50)  && (tmpV<60)) {		slept=10;			}
			if ((tmpV>=60)  && (tmpV<70)) {		slept=8;			}
			if ((tmpV>=70)  && (tmpV<80)) {		slept=6;			}
			if ((tmpV>=80)  && (tmpV<90)) {		slept=3;			}
			if ((tmpV>=90)  && (tmpV<=200)) {	slept=0;			}
		} else {
			slept--;
			newtrend+=tmpV;
			divtrend++;
		}
		if (tslept==0) {
			tslept=30*tdelay;
			newtrend = newtrend/divtrend;
//			printf("trend=%d,%d    %d\n",newtrend,oldtrend,divtrend);
			/* set trend */
			if (newtrend==0 && divtrend==1) {
				// at 100 percent CPU usage ignore
			} else {
				if (newtrend>oldtrend) { // increasing
					trend=getBm(6);
				}
				if (newtrend<oldtrend) {
					trend=getBm(7);	// decreasing
				}
				oldtrend=newtrend;
			}
			newtrend=0;
			divtrend=1;
		} else {
			tslept--;
		}
		//usleep(800000);
		usleep(sdelay);		// shortest delay we are allowing, no need to be any quicker, I might even slow it down.
	}
}

// simple function to store the actual LED positions vs the cycle, and the two indicators are the last items in the array
void setBm(int index, int map) {
	mappingArray[index] = map;
}

// using the premapped array returns an LED position (in binary) for a given cycle position
int getBm(int index) {
	int bas,ret,i;
	//printf("index = %d",index);
	bas = mappingArray[index];
	//printf(" bas = %d",bas);
	ret=1; // equivalent to 00000001
	//printf(" ret = %d",ret);
	for (i=0;i<bas;i++) {	// this little loop takes 00000001 and (if necessary) shifts it to
		ret<<=1;			// 00000010, 00000100, 00001000 etc
	}
	//printf(" final ret = %d\n",ret);
	return(ret);
}


/*
	Returns percentage of CPU in use (single processor system)
	uses global old values to create average.
	What we are measuring is the number of 100th/sec in each queue that differ over time.
	Apart from the first reading wich is bogus this actualy seems to mirror 'top' (and gkrellm, including odd 100% spikes )
*/
int getCPU()
{
  	int user;
  	int nice;
  	int system;
  	int idle;
  	int d_user;
  	int d_nice;
  	int d_system;
  	int d_idle;
  	int all;			// everything
	int cpuu;			// just what is in use (not idling)
	double ctmp;		// temp var, cos my math is so bad (dangerously so)
  	char cpu[10];
  	FILE *f;

  	if((f=fopen("/proc/stat", "r"))==0)
    	return -1;


  	if( fscanf(f,"%s %i %i %i %i", cpu, &user, &nice, &system, &idle) != 5 ) {
      	fclose(f);
      	return -1;
   	}

  	fclose(f);

	if (OLD_user!=0) {
		// get differences
		d_user = (user - OLD_user);	// the old value will always be the same or smaller than the new
		d_nice = (nice - OLD_nice);
		d_system = (system - OLD_system);
		d_idle = (idle - OLD_idle);
	}
	// update old values
	OLD_user = user;
	OLD_nice = nice;
	OLD_system = system;
	OLD_idle = idle;


  	// return a percentage based on all values added together being 100% and user+nice+system being a percentage of

//	printf("user=%d\n",d_user);
//	printf("nice=%d\n",d_nice);
//	printf("syst=%d\n",d_system);
//	printf("idle=%d\n",d_idle);
	cpuu = d_user + d_nice + d_system;
//	printf("cpuu=%d\n",cpuu);
	all = cpuu + d_idle;
//	printf("all =%d\n",all);

	ctmp =(double)  cpuu / all;

	return (ctmp*100);
}
