/*
	CPULoad
	(C) Thomas Radtke 1994

	main:		Process, Initialisierungen und buisy loop
	druck:		Task, Graphische Ausgabe
	IdleIrq:	IRQ server, Auswertung des buisy loop

	Um double-Rechnungen zu vermeiden werden ULONG's in beide Richtungen
	geshiftet. Bei hohen Rechenleistungen kann dies ggf. zu Überläufen
	führen. Abhilfe bringt eine Verringerung des 'aktuell'-Wertes
	('aktuell>>n') um ein geeignetes n.

	Das Abbruchsignal ist CTRL-D. Niemals CTRL-C verwenden, wenn der
	Compiler dieses Signal als generellen Abbruch benutzt.

	Basiert auf Idle LED von Lindsay Meek.

	read the README !
*/

char *dummy="$VER: CPULoad 1.0ß";

#include <intuition/intuitionbase.h>
#include <exec/nodes.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <hardware/intbits.h>
#include <libraries/dos.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

/*	Stacksize für den Grafik-Task (1000 < STACK_SIZE < 10000)	*/

#define STACK_SIZE 10000L

/*	Task name und Pointer für Task Struktur	*/

struct Task *task = NULL;
char *taskname = "CPULoad";

struct timerequest *tr;
struct MsgPort *tport;
struct Message *msg;
struct Window *CustWindow;
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;

#define rp CustWindow->RPort

struct NewWindow BlankWindow=
{
	175,24,130,50,1,0,
	CLOSEWINDOW | NEWSIZE | RAWKEY,
	WINDOWSIZING | ACTIVATE | GIMMEZEROZERO | SMART_REFRESH | WINDOWCLOSE | WINDOWDRAG | WINDOWDEPTH,
	0,0,
	"CPULoad",
	0,0,70,20,0,0,
	PUBLICSCREEN
};

unsigned int maximum,aktuell;

/*	globale Kopie des Main-Prozesses für druck() (wegen der Signale)	*/

struct Task *met2;

/*	System structure name	*/

#define UTILITY_NAME "IdleCPU"

/*	various counters used to detect the idle state in the vblank	*/

static ULONG idle_vbi_cnt_limit[3];

/*	allocate the timer	*/

struct timerequest *create_timer(ULONG unit)
{
	LONG error;
	struct MsgPort *timerport;
	struct timerequest *timermsg;

	if ((timerport=(struct MsgPort *)CreatePort(0,0))==NULL) exit(1);
	if ((timermsg=(struct timerequest *)CreateExtIO(timerport,sizeof(struct timerequest)))==NULL) exit(1);
	if ((error=OpenDevice(TIMERNAME,unit,(struct IORequest *)timermsg,0))!=0) exit(1);
	return(timermsg);
}

/*	Läuft im VBL-Interrupt	*/

int IdleCPU()
{
	idle_vbi_cnt_limit[0]=1;
	aktuell=idle_vbi_cnt_limit[1];
	idle_vbi_cnt_limit[1]=0;
	return 0;
}

/*	Läuft als Task	*/

druck()
{
	struct IntuiMessage *message;	/*	Windowclose	*/
	struct IntuiMessage *GetMsg();
	struct timeval updateval;	/*	1/10 Sekunde	*/
	char name[256];			/*	PubScreen	*/
	int n;
	unsigned int sum;		/*	Summand der freien Zyklen	*/
	unsigned int height,width,right,bottom;
	unsigned int int_pix_per_load;		/*	Skalierung	*/
	int whitepen,blackpen;
	struct Screen *PubScreen;
	short pen,minpen,maxpen;
	unsigned short RGB;
	int class;

/*	init timer	*/

	tr=create_timer(UNIT_MICROHZ);

	updateval.tv_secs=0;
	updateval.tv_micro=100000;	/*	1/10 secs	*/

	IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",0);
	GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0);

	GetDefaultPubScreen(name);
	PubScreen=(struct Screen *)LockPubScreen(name);
	UnlockPubScreen(0L,PubScreen);

	BlankWindow.MaxHeight=PubScreen->Height;
	BlankWindow.MaxWidth=PubScreen->Width;

	CustWindow=(struct Window *)OpenWindow(&BlankWindow);

/*	Farbgebung aus der ColorMap bestimmen (SunClock-Methode)	*/

	whitepen = 1;
	blackpen = 0;
	maxpen=0x000;
	minpen=0xfff;

	for (n=0; n<1<<PubScreen->BitMap.Depth; n++)
	{
		RGB = GetRGB4(PubScreen->ViewPort.ColorMap,n);

		pen = (RGB & 0x00f) + ((RGB & 0x0f0) >> 4) + ((RGB & 0xf00) >> 8);

		if (pen < minpen)
		{
			minpen = pen;
			blackpen = n;
		}
		if (pen > maxpen)
		{
			maxpen = pen;
			whitepen = n;
		}
	}

	height=CustWindow->GZZHeight;
	width=CustWindow->GZZWidth;
	right=width-1;
	bottom=height-1;

/*	Ja, ja, mach mich Weiß !	*/

	SetAPen(rp,whitepen);
	RectFill(rp,0,0,right,bottom);

/*	FOREVER	*/

	while (1)
	{
		sum=0;			/*	Zyklenzähler	*/
		for (n=0; n<10; n++)
		{

/*	Akkumuliere CPU-Auslastung	*/

			sum+=aktuell;

/*	wait for 1/10 secs to give others a chance	*/

/*	Delay() funktioniert nicht, weil dos nicht erwünscht in task	*/

			tr->tr_node.io_Command=TR_ADDREQUEST;
			tr->tr_time=updateval;
			DoIO((struct IORequest *)tr);
		}

		sum/=10;	/*	Average	*/

/*	Verschiebe Bild nach links	*/

		height=CustWindow->GZZHeight;
		width=CustWindow->GZZWidth;
		right=width-1;
		bottom=height-1;

		int_pix_per_load=(height<<16)/maximum;

		ScrollRaster(rp,1,0,0,0,width,height);

/*	Jede Sekunde Ausdruck	*/

		SetAPen(rp,blackpen);
		Move(rp,right,bottom);

		Draw(rp,right,(sum*int_pix_per_load)>>16);
		if (rp->cp_y)
		{
			SetAPen(rp,whitepen);
			Draw(rp,right,0);
		}

/*
	Dies ist der gefährliche Teil des Universums. Falls das nicht auf allen
	Maschinen läuft, zum testen rausschmeissen
*/

		message=GetMsg(CustWindow->UserPort);
		if ((class=message->Class)==CLOSEWINDOW)
		{
			ReplyMsg(message);		/*	!!!	*/
			Signal(met2,SIGBREAKF_CTRL_D);	/*	Kill all tasks	*/
		}
		else if (class==NEWSIZE)
		{
			ReplyMsg(message);
			SetAPen(rp,whitepen);
			RectFill(rp,0,0,right,bottom);
		}	
		else if (class==RAWKEY)
		{
			if (message->Code==89) WindowToFront(CustWindow);
			ReplyMsg(message);
		}
	}
}

/* Run this as a process. It aborts when it detects a CTRL_D signal */

main()
{
	extern struct Task *FindTask(char *);
	struct Task *met;
	struct Interrupt mei;
	WORD oldpri;
	char *oldname;
	int i;

/*	switch off CTRL_C handling	*/

	signal(SIGINT,SIG_IGN);

	met=FindTask(UTILITY_NAME);	/*	Gibts mich schon ?	*/

	if (met!=NULL)			/*	Jawoll.	*/
	{

/* Send CTRL_D signal to task */

		Signal(met,SIGBREAKF_CTRL_D);	/*	dann Adieu !	*/
		exit(1);			/*	+-:-)	*/
	}

/*	setup für IRQ	*/

	mei.is_Node.ln_Type = NT_INTERRUPT;
	mei.is_Node.ln_Pri  = -127;
	mei.is_Node.ln_Name = UTILITY_NAME;
	mei.is_Data = (APTR)0; /* idle_vbi_cnt_limit; */
	mei.is_Code = IdleCPU;

	AddIntServer(INTB_VERTB, &mei);		/*	Install IRQ server	*/

	met=FindTask(NULL);			/*	Finde mich selber	*/
	oldpri=met->tc_Node.ln_Pri;		/*	Save old priority	*/
	oldname=met->tc_Node.ln_Name;		/*	Save old name	*/

/*	Redirect task name, damit niemand durch rename die gute Absicht vereitelt	*/

	met->tc_Node.ln_Name=UTILITY_NAME;

	met->tc_Node.ln_Pri=40;		/*	Raise priority for calibration	*/

	met2=met;	/*	Pointer für druck()	*/

/*	reset counters	*/

	idle_vbi_cnt_limit[0]=0;
	idle_vbi_cnt_limit[1]=0;
	idle_vbi_cnt_limit[2]=0;

/*	wait for next vbi, nicht ganz astrein	*/

	Forbid();

	while(idle_vbi_cnt_limit[0]==0) ;

/*	count # of increments in one frame (for calibration)	*/

	idle_vbi_cnt_limit[2]=0;
	idle_vbi_cnt_limit[0]=0;
	while (idle_vbi_cnt_limit[0]==0) idle_vbi_cnt_limit[2]++;

	Permit();

/*	druck() braucht maximum	*/

	maximum=idle_vbi_cnt_limit[2];

/*	printf("%d\n",maximum);	*/

/*	und endlich...trara !	*/

	task=CreateTask(taskname,30,druck,STACK_SIZE);

	met->tc_Node.ln_Pri=-127;

/*	Busy loop for eating up idle cpu cycles	*/

	while((met->tc_SigRecvd & SIGBREAKF_CTRL_D)==0)	/*	Abort on CTRL_D	*/
	{
		idle_vbi_cnt_limit[1]++;	/*	Increment idle counter	*/
	}

/*	und Tschüs	*/

	RemIntServer(INTB_VERTB,&mei);	/*	Remove IRQ server	*/
	met->tc_Node.ln_Pri=oldpri;	/*	Change priority back	*/
	met->tc_Node.ln_Name=oldname;	/*	Change name back	*/

	Forbid();
	DeleteTask(task);
	Permit();

	CloseWindow(CustWindow);
	CloseLibrary(IntuitionBase);
	CloseLibrary(GfxBase);

	exit(1);
}
