// Copyright 1991, 1994 Scott Hess.  Permission to use, copy,
// modify, and distribute this software and its documentation for
// any purpose and without fee is hereby granted, provided that
// this copyright notice appear in all copies.
// 
// Scott Hess makes no representations about the suitability of
// this software for any purpose.  It is provided "as is" without
// express or implied warranty.
//
#import "Percentages.h"
#import "TimeMonWraps.h"
#import "loadave.h"
#import "AppDefaults.h"
#import "TimeMonColors.h"
#import <syslog.h>

	// Determines how much movement is needed for a display/redisplay.
#define MINSHOWN	0.01
	// Minimum values for the defaults.
#define MINPERIOD	0.1
#define MINFACTOR	4
#define MINLAGFACTOR	1


@implementation Percentages

- initFrame:(const NXRect *)r
{
    self=[super initFrame:r];
    if( self) {
	    // Cache the stipple image to use as a background.
	    // This is a holdover from the NS2.x code - under NS2.x,
	    // this would leave stipple==nil, and I check that and
	    // do rectfills to clear.
	stipple=[NXImage findImageNamed:"NXAppTile"];
    }
    return self;
}
- update
{
    static float radii[ 3]={ 23, 17, 11};
    int i;
    BOOL drawn=NO;
    PSsetlinewidth( 1.0);

	// Clear to the background color if all rings are to be
	// redrawn.
    if( updateFlags[ 0]) {
	if( stipple) {
	    [stipple composite:NX_COPY toPoint:&(bounds.origin)];
	} else {
	    PSsetgray( NX_LTGRAY);
	    NXRectFill( &bounds);
	}
    }
    
	// For each ring, if it's changed, redraw that ring.
    for( i=0; i<3; i++) {
	if( updateFlags[ i]) {
		// Note that we need a redraw.
	    drawn=YES;
	    
		// Note that we redrew this ring.
	    updateFlags[ i]=NO;
	    
		// Store away the values we redraw.
	    bcopy( pcents[ i], lpcents[ i], sizeof( lpcents[ i]));
	
		// Don't draw the "nice" segment if there isn't one.
	    if( pcents[ i][ 2]>=MINSHOWN) {
		drawArc2( radii[ i], 90-(pcents[ i][ 0])*360,
				90-(pcents[ i][ 0]+pcents[ i][ 1])*360,
				90-(pcents[ i][ 0]+pcents[ i][ 1]+pcents[ i][ 2])*360);
	    } else {
		drawArc1( radii[ i], 90-(pcents[ i][ 0])*360,
				90-(pcents[ i][ 0]+pcents[ i][ 1])*360);
	    }
	}
    }
    
	// If any rings redrew, redraw the circles.  Since the
	// circles are constant, this _should_ be cached by the
	// server and future draws should be insanely fast.  Or
	// something like that.
    if( drawn) {
	static float bbox[]={ 0, 0, 64, 64};
	static float coords[]={ 55.5, 32,		// moveto
				32, 32, 23.5, 0, 360,	// arc
				49.5, 32,		// moveto
				32, 32, 17.5, 0, 360,	// arc
				43.5, 32,		// moveto
				32, 32, 11.5, 0, 360,	// arc
				32, 32,			// moveto
				32, 56,			// lineto
			    };
	static char ops[]={ dps_ucache, dps_moveto, dps_arc,
			    dps_moveto, dps_arc,
			    dps_moveto, dps_arc,
			    dps_moveto, dps_lineto};
	PSsetgray( NX_BLACK);
	DPSDoUserPath( coords, sizeof( coords)/sizeof( coords[ 0]), dps_float,
			ops, sizeof( ops)/sizeof( ops[ 0]),
			bbox, dps_ustroke);
    }
    return self;
}
- drawSelf:(const NXRect *)r :(int)count
{
	// If we are paused, draw differently.
    if( !te) {
	static NXImage *app=nil, *pause=nil;
	NXPoint p={ 8, 8};
	
	    // Cache the app and pause tiffs.
	if( !app) {
	    app=[NXImage findImageNamed:"app"];
	    pause=[NXImage findImageNamed:"TimeMonP.tiff"];
	}
	
	    // Put af background on the icon.
	if( stipple) {
	    [stipple composite:NX_COPY toPoint:&(bounds.origin)];
	} else {
	    PSsetgray( NX_LTGRAY);
	    NXRectFill( &bounds);
        }
	
	    // Layer over the app and pause tiffs.
	[app composite:NX_SOVER toPoint:&p];
	[pause composite:NX_SOVER toPoint:&p];
    } else {
	    // Redrawing normally is simple - just mark all rings
	    // for update and update it.
	updateFlags[ 0]=updateFlags[ 1]=updateFlags[ 2]=YES;
	[self update];
    }
    return self;
}
- step
{
    int i, j, oIndex;
    float total;
    
	// Read the new CPU times.
    la_read( oldTimes[ laIndex]);
    
	// The general idea for calculating the ring values is to
	// first find the earliest valid index into the oldTimes
	// table for that ring.  Once in a "steady state", this is
	// determined by the lagFactor and/or layerFactor values.
	// Prior to steady state, things are restricted by how many
	// steps have been performed.  Note that the index must
	// also be wrapped around to remain within the table.
	// 
	// The values are all then easily calculated by subtracting
	// the info at the old index from the info at the current
	// index.
    
	// Calculate values for the innermost "lag" ring.
    oIndex=(laIndex-MIN( lagFactor, steps)+laSize)%laSize;
    for( total=0, i=0; i<CPUSTATES; i++) {
	total+=oldTimes[ laIndex][ i]-oldTimes[ oIndex][ i];
    }
    if( total) {
	pcents[ 2][ 0]=(oldTimes[ laIndex][  CP_SYS]-oldTimes[ oIndex][  CP_SYS])/total;
	pcents[ 2][ 1]=(oldTimes[ laIndex][ CP_USER]-oldTimes[ oIndex][ CP_USER])/total;
	pcents[ 2][ 2]=(oldTimes[ laIndex][ CP_NICE]-oldTimes[ oIndex][ CP_NICE])/total;
    }

	// Calculate the middle ring.
    oIndex=(laIndex-MIN( lagFactor+layerFactor, steps)+laSize)%laSize;
    for( total=0, i=0; i<CPUSTATES; i++) {
	total+=oldTimes[ laIndex][ i]-oldTimes[ oIndex][ i];
    }
    if( total) {
	pcents[ 1][ 0]=(oldTimes[ laIndex][  CP_SYS]-oldTimes[ oIndex][  CP_SYS])/total;
	pcents[ 1][ 1]=(oldTimes[ laIndex][ CP_USER]-oldTimes[ oIndex][ CP_USER])/total;
	pcents[ 1][ 2]=(oldTimes[ laIndex][ CP_NICE]-oldTimes[ oIndex][ CP_NICE])/total;
    }

	// Calculate the outer ring.
    oIndex=(laIndex-MIN( lagFactor+layerFactor*layerFactor, steps)+laSize)%laSize;
    for( total=0, i=0; i<CPUSTATES; i++) {
	total+=oldTimes[ laIndex][ i]-oldTimes[ oIndex][ i];
    }
    if( total) {
	pcents[ 0][ 0]=(oldTimes[ laIndex][  CP_SYS]-oldTimes[ oIndex][  CP_SYS])/total;
	pcents[ 0][ 1]=(oldTimes[ laIndex][ CP_USER]-oldTimes[ oIndex][ CP_USER])/total;
	pcents[ 0][ 2]=(oldTimes[ laIndex][ CP_NICE]-oldTimes[ oIndex][ CP_NICE])/total;
    }

	// Move the index forward for the next cycle.
    laIndex=(laIndex+1)%laSize;
    steps++;

	// Look through the rings and see if any values changed by
	// one percent or more, and if so mark that and inner rings
	// for update.
    for( i=0; i<3; i++) {
	for( j=0; j<3; j++) {
	    if( rint( pcents[ i][ j]*100)!=rint( lpcents[ i][ j]*100)) {
		for( ; i<3; i++) {
		    updateFlags[ i]=YES;
		}
		break;
	    }
	}
    }
    
	// If there's a need for updating of any rings, call update.
    if( updateFlags[ 2]) {
	[self lockFocus];
	[self update];
	[window flushWindow];
	[self unlockFocus];
	NXPing();
    }
    return self;
}
    // This was for debugging, no longer needed.  I used it to hook
    // up a menu item to manually call step.
- cycle:sender
{
    return [self step];
}
    // Callback for timed entry.
static void _step( DPSTimedEntry te, double curTime, id self)
{
    [self step];
}
    // Set up to have a low priority from the get-go.
- appWillInit:sender
{
    struct task_basic_info tbi;
    unsigned ic=TASK_BASIC_INFO_COUNT;
    if( task_info( task_self(), TASK_BASIC_INFO, (task_info_t)&tbi, &ic)!=KERN_SUCCESS) {
	return nil;
    }
    task_priority( task_self(), tbi.base_priority-4, TRUE);
    return self;
}
    // Resize the oldTimes array and rearrange the values within
    // so that as many as possible are retained, but no bad values
    // are introduced.
- __reallocOldTimes
{
    CPUTime *newTimes;
	// Get the new size for the array.
    unsigned newSize=layerFactor*layerFactor+lagFactor+1;
    
	// Allocate info for the array.
    newTimes=NXZoneMalloc( [self zone], sizeof( float)*CPUSTATES*newSize);
    bzero( newTimes, sizeof( float)*CPUSTATES*newSize);
    
	// If there was a previous array, copy over values.  First,
	// an index is found for the first valid time.  Then enough
	// times to fill the rings are copied, if available.
    if( oldTimes) {
	unsigned ii, jj, elts;
	
	elts=MIN( lagFactor+layerFactor*layerFactor+1, steps);
	ii=(laIndex+laSize-elts)%laSize;
	jj=MIN( laSize-ii, elts);
	
	if( jj) {
	    bcopy( oldTimes+ii, newTimes+0, jj*sizeof( oldTimes[ 0]));
	}
	if( jj<elts) {
	    bcopy( oldTimes+0, newTimes+jj, (elts-jj)*sizeof( oldTimes[ 0]));
	}
	
	    // Free the old times.
	NXZoneFree( [self zone], oldTimes);
    }
	// Reset everything so that we only access valid data.
    oldTimes=newTimes;
    laIndex=MIN( steps, laSize);
    laIndex=MIN( laIndex, newSize)%newSize;
    steps=MIN( steps, laSize);
    steps=MIN( steps, newSize);
    laSize=newSize;
    return self;
}
- appDidInit:sender
{
    Window *iw;
    float f;
    int ret;
    CPUTime cp_time;
    NXDefaultsVector defs={
	{ "UpdatePeriod",	"0.5"},
	{ "LagFactor",		"4"},
	{ "LayerFactor",	"16"},
	{ "HideOnAutolaunch",	"YES"},
	{ "NXAutoLaunch",	"NO"},
	    // For color systems.
	{ "IdleColor",		"1.000 1.000 1.000"},	// White
	{ "NiceColor",		"0.333 0.667 0.867"},	// A light blue-green
	{ "UserColor",		"0.200 0.467 0.800"},	// A darker blue-green
	{ "SystemColor",	"0.000 0.000 1.000"},	// Blue
	    // For monochrome systems.
	{ "IdleGray",		"1.000"},		// White
	{ "NiceGray",		"0.667"},		// Light gray
	{ "UserGray",		"0.333"},		// Dark gray
	{ "SystemGray",		"0.000"},		// Black
	{ NULL, NULL},
    };

	// Register the defaults.
    NXRegisterDefaults( [NXApp appName], defs);
    
	// Shoot out error codes if there was an error.
    if( ret=la_init( cp_time)) {
	const char *syslogs[]={
	    NULL,				// LA_NOERR
	    "Cannot nlist /mach.",		// LA_NLIST
	    "Must be installed setgid kmem.",	// LA_PERM
	    "Cannot open /dev/kmem.",		// LA_KMEM
	    "Unable to seek in /dev/kmem",	// LA_SEEK
	    "Unable to read from /dev/kmem",	// LA_READ
	    "table() call failed.",		// LA_TABLE
	};
	char buf[ 64];
	sprintf( buf, "TimeMon: %s\n", syslogs[ ret]);
	syslog( LOG_WARNING, buf);
	sprintf( buf, "%s  Exitting.", syslogs[ ret]);
	NXRunAlertPanel( "TimeMon", "%s.", "OK", NULL, NULL, syslogs[ ret]);
	[NXApp terminate:sender];
    }
	// Load up the needed DPS procedures.
    drawInit();
    
	// Move ourselves over to the appIcon window.
    iw=[NXApp appIcon];
    [[iw contentView] addSubview:self];
    
  	// Get us registered for periodic exec.
    f=[NXApp defaultAsFloat:"UpdatePeriod"];
    f=MAX( f, MINPERIOD);
    [periodText setFloatValue:f];
    te=DPSAddTimedEntry( f, (void *)_step, self, NX_MODALRESPTHRESHOLD);
    
  	// Get the lag factor.
    lagFactor=[NXApp defaultAsInt:"LagFactor"];
    lagFactor=MAX( lagFactor, MINLAGFACTOR);
    [lagText setIntValue:lagFactor];
    
  	// Get the layer factor.
    layerFactor=[NXApp defaultAsInt:"LayerFactor"];
    layerFactor=MAX( layerFactor, MINFACTOR);
    [factorText setIntValue:layerFactor];
    
    [self __reallocOldTimes];
    bcopy( cp_time, oldTimes[ 0], sizeof( CPUTime));
    laIndex=1;
    steps=1;

    [colorFields readColors];
    [self display];
    if( [NXApp defaultAsBOOL:"HideOnAutolaunch" default:YES]
     && [NXApp defaultAsBOOL:"NXAutoLaunch" default:NO]) {
	[NXApp hide:self];
    }
    return self;
}
- appWillTerminate:sender
{
	// If te is installed, remove it.
    if( te) {
	DPSRemoveTimedEntry( te);
	te=(DPSTimedEntry)0;
    }
    la_finish();
    return self;
}
- togglePause:sender
{
    if( te) {
	[pauseMenuCell setTitle:"Continue"];
	DPSRemoveTimedEntry( te);
	te=(DPSTimedEntry)0;
    } else {
	float f;
	[pauseMenuCell setTitle:"Pause"];
	f=[NXApp defaultAsFloat:"UpdatePeriod"];
	f=MAX( f, MINPERIOD);
	[periodText setFloatValue:f];
	te=DPSAddTimedEntry( f, (void *)_step, self, NX_MODALRESPTHRESHOLD);
    }
    return [self display];
}
- setPeriod:sender
{
    [NXApp default:"UpdatePeriod" asFloat:[periodText floatValue]];
    if( te) {
	float f;
	DPSRemoveTimedEntry( te);
	f=[NXApp defaultAsFloat:"UpdatePeriod"];
	f=MAX( f, MINPERIOD);
	[periodText setFloatValue:f];
	te=DPSAddTimedEntry( f, (void *)_step, self, NX_MODALRESPTHRESHOLD);
    }
    return self;
}
- setLag:sender
{
    [NXApp default:"LagFactor" asInt:[lagText intValue]];
    lagFactor=[NXApp defaultAsInt:"LagFactor"];
    lagFactor=MAX( lagFactor, MINLAGFACTOR);
    [lagText setIntValue:lagFactor];
    [self __reallocOldTimes];
    return [self display];
}
- setFactor:sender
{
    [NXApp default:"LayerFactor" asInt:[factorText intValue]];
    layerFactor=[NXApp defaultAsInt:"LayerFactor"];
    layerFactor=MAX( layerFactor, MINFACTOR);
    [factorText setIntValue:layerFactor];
    [self __reallocOldTimes];
    return [self display];
}
-(BOOL)textWillEnd:sender
{
    id delegate=[sender delegate];
    if( delegate==factorText) {
	[self setFactor:factorText];
    } else if( delegate==periodText) {
	[self setPeriod:periodText];
    } else if( delegate==lagText) {
	[self setLag:lagText];
    }
    return NO;
}
- windowWillResize:sender toSize:(NXSize *)aSize
{
    static BOOL gotSize=NO;
    static NXSize minSize={ 0, 0};
    if( !gotSize) {
	NXRect r;
	[sender getFrame:&r];
	minSize=r.size;
	gotSize=YES;
    }
    aSize->width=MAX( aSize->width, minSize.width);
    aSize->height=MAX( aSize->height, minSize.height);
    return self;
}
@end
