/*
  Percentages.m

	Implementation file for the Percentage class.

  Copyright 1991 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.  The copyright notice need not appear
  on binary-only distributions - just in source code.
  
  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 <libc.h>
#import <dpsclient/wraps.h>
#import <appkit/Panel.h>
#import <appkit/Application.h>
#import <syslog.h>
#import <sys/file.h>
#import <nlist.h>
#import <string.h>
#import "TimeMonWraps.h"
#define INLINE_MATH
#import <appkit/nextstd.h>
#import <appkit/defaults.h>
#import <appkit/MenuCell.h>
#import <mach.h>
#import <appkit/NXImage.h>
#import <grp.h>

	/* By default, we'll hope that we don't have to use /dev/kmem. */
#ifndef USEKMEM
#define USEKMEM 0
#endif USEKMEM

	/* Determines how much movement is needed for a display/redisplay. */
#define MINSHOWN	0.01
	/* Minimum values for the defaults. */
#define MINPERIOD	0.2
#define MINFACTOR	4
@implementation Percentages

static struct nlist nl[]={{{"_cp_time"}, 0, 0, 0, 0}, {{""}, 0, 0, 0, 0}};
#if !USEKMEM
#import <sys/table.h>
extern int table( int, int, void *, int, int);
#endif

- initFrame:(NXRect *)r
{
  int i, j;
  [super initFrame:r];
  kmem=-1;
  for( i=0; i<3; i++)
    for( j=0; j<3; j++)
      pcents[ i][ j]=0.0;
  return self;
}
- update
{
  static float radii[ 3]={ 23, 17, 11};
  int i;
  BOOL drawn=NO;
  PSsetlinewidth( 1.0);
  if( updateFlags[ 0])
    {
      PSsetgray( NX_LTGRAY);
      NXRectFill( &bounds);
    }
  for( i=0; i<3; i++)
    if( updateFlags[ i])
      {
        drawn=YES;
	updateFlags[ i]=NO;
	bcopy( pcents[ i], lpcents[ i], sizeof( lpcents[ i]));
	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( drawn)
    {
      static float bbox[]={ 0, 0, 48, 48};
      static float coords[]={ 47.5, 24,			/* moveto */
			      24, 24, 23.5, 0, 360,	/* arc */
			      41.5, 24,			/* moveto */
			      24, 24, 17.5, 0, 360,	/* arc */
			      35.5, 24,			/* moveto */
			      24, 24, 11.5, 0, 360,	/* arc */
			      24, 24,			/* moveto */
			      24, 48,			/* 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:(NXRect *)r :(int)count
{
  if( !te)
    {
      static NXImage *app, *pause;
      if( !app)
        {
	  app=[NXImage findImageNamed:"app"];
	  pause=[NXImage findImageNamed:"TimeMonP.tiff"];
	}
      PSsetgray( NX_LTGRAY);
      NXRectFill( &bounds);
      [app composite:NX_SOVER toPoint:&(bounds.origin)];
      [pause composite:NX_SOVER toPoint:&(bounds.origin)];
    }
  else
    {
      updateFlags[ 0]=updateFlags[ 1]=updateFlags[ 2]=YES;
      [self update];
    }
  return self;
}
	/* Just a handy shorthand for use below. */
#define LF2		(layerFactor*layerFactor)
- step
{
  long cp_time[ CPUSTATES];
  int i, j;
  float total;
  steps++;
#if !USEKMEM
  if( kmem==-1)			/* Use table()? */
    {
      struct tbl_cpuinfo tc;
      table( TBL_CPUINFO, 0, &tc, 1, sizeof( tc));
      bcopy( tc.ci_cptime, cp_time, sizeof( cp_time));
    }
  else				/* Nope, use kmem directly. */
    {
#endif
	/* Seek to where _cp_time resides. */
      if( lseek( kmem, nl[ 0].n_value, L_SET)!=nl[ 0].n_value)
	{
	  syslog( LOG_WARNING, "TimeMon: Unable to seek in /dev/kmem\n");
	  return nil;
	}
	/* Read in the variable. */
      if( read( kmem, cp_time, sizeof( cp_time))!=sizeof( cp_time))
	{
	  syslog( LOG_WARNING, "TimeMon: Unable to read from /dev/kmem\n");
	  return nil;
	}
#if !USEKMEM
    }
#endif
  total=cp_time[ CP_USER]+cp_time[ CP_NICE]
        +cp_time[ CP_SYS]+cp_time[ CP_IDLE];
  total-=oldTimes[ CP_USER]+oldTimes[ CP_NICE]
         +oldTimes[ CP_SYS]+oldTimes[ CP_IDLE];
  if( total)		/* Make sure we divide reasonably. */
    {
      pcents[ 2][ 0]=(cp_time[ CP_SYS]-oldTimes[ CP_SYS])/total;
      pcents[ 2][ 1]=(cp_time[ CP_USER]-oldTimes[ CP_USER])/total;
      pcents[ 2][ 2]=(cp_time[ CP_NICE]-oldTimes[ CP_NICE])/total;
      bcopy( cp_time, oldTimes, sizeof( cp_time));
      if( steps<layerFactor)
	for( i=0; i<3; i++)
	  pcents[ 1][ i]=(pcents[ 1][ i]*(steps-1)
	                  +pcents[ 2][ i])/steps;
      else
	for( i=0; i<3; i++)
	  pcents[ 1][ i]=(pcents[ 1][ i]*(layerFactor-1)
	  		  +pcents[ 2][ i])/layerFactor;
      if( steps<LF2)
	for( i=0; i<3; i++)
	  pcents[ 0][ i]=(pcents[ 0][ i]*(steps-1)
	                  +pcents[ 2][ i])/steps;
      else
	for( i=0; i<3; i++)
	  pcents[ 0][ i]=(pcents[ 0][ i]*(LF2-1)
	                  +pcents[ 2][ i])/LF2;
      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;
	    }
      [self lockFocus];
      [self update];
      [self unlockFocus];
      [window flushWindow];
      NXPing();
    }
  return self;
}
- cycle:sender	{ return [self step]; }
static void _step( DPSTimedEntry te, double curTime, id self)
{ [self step]; }
- 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;
}
- appDidInit:sender
{
  Window *iw;
  float f=0.5;
  NXDefaultsVector defs={
    { "UpdatePeriod",	"0.5"},
    { "LayerFactor",	"16"},
    { NULL, NULL},
  };
#if !USEKMEM
  struct tbl_cpuinfo tc;
#endif

	/* Register the defaults. */
  NXRegisterDefaults( [NXApp appName], defs);
#if !USEKMEM
  if( table( TBL_CPUINFO, 0, &tc, 1, sizeof( tc))>=0)
    bcopy( tc.ci_cptime, oldTimes, sizeof( oldTimes));
  else
    {
      syslog( LOG_WARNING, "TimeMon: table() call failed.\n");
      syslog( LOG_WARNING, "TimeMon: Attempting direct /dev/kmem access.\n");
#endif
	/* Get nlist. */
      if( nlist( "/mach", nl))
	{
	  syslog( LOG_WARNING, "TimeMon: Cannot nlist /mach.\n");
	  NXRunAlertPanel( "nlist", 
	      "Cannot nlist /mach.  Exitting.", "Ok", NULL, NULL);
	  [NXApp terminate:sender];
	}
	/* Open kmem. */
      kmem=open( "/dev/kmem", O_RDONLY);
      if( kmem<0)
	{
	    /* Check permissions. */
	  gid_t gid=getegid();
	  struct group *kmemgr=getgrnam( "kmem");
	  syslog( LOG_WARNING, "TimeMon: Cannot open /dev/kmem.\n");
	  if( kmemgr->gr_gid!=gid)
	    {
	      syslog( LOG_WARNING, "TimeMon: Not installed setgid kmem.\n");
	      NXRunAlertPanel( "Permissions",
		    "TimeMon must be installed setgid to group kmem.  Exitting.",
		    "Ok", NULL, NULL);
	    }
	  else
	    NXRunAlertPanel( "/dev/kmem",
		    "Cannot open /dev/kmem.  Exitting.", "Ok", NULL, NULL);
	  [NXApp terminate:sender];
	}
	/* Seek to where _cp_time resides. */
      if( lseek( kmem, nl[ 0].n_value, L_SET)!=nl[ 0].n_value)
	{
	  syslog( LOG_WARNING, "TimeMon: Unable to seek in /dev/kmem\n");
	  NXRunAlertPanel( "/dev/kmem",
	      "Cannot seek in /dev/kmem.  Exitting.", "Ok", NULL, NULL);
	  [NXApp terminate:sender];
	}
	/* Initialize the oldTimes. */
      if( read( kmem, oldTimes, sizeof( oldTimes))!=sizeof( oldTimes))
	{
	  syslog( LOG_WARNING, "TimeMon: Unable to read from /dev/kmem\n");
	  NXRunAlertPanel( "/dev/kmem", 
	      "Cannot read from /dev/kmem.  Exitting.", "Ok", NULL, NULL);
	  [NXApp terminate:sender];
	}
#if !USEKMEM
    }
#endif
	/* 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. */
  if( NXGetDefaultValue( [NXApp appName], "UpdatePeriod"))
    {
      f=atof( NXGetDefaultValue( [NXApp appName], "UpdatePeriod"));
      f=MAX( f, MINPERIOD);
    }
  [periodText setFloatValue:f];
  te=DPSAddTimedEntry( f, (void *)_step, self, NX_MODALRESPTHRESHOLD);
  	/* Get the layer factor. */
  layerFactor=16;
  if( NXGetDefaultValue( [NXApp appName], "LayerFactor"))
    {
      layerFactor=atoi( NXGetDefaultValue( [NXApp appName], "LayerFactor"));
      layerFactor=MAX( layerFactor, MINFACTOR);
    }
  [factorText setIntValue:layerFactor];
  return [self display];
}
- appWillTerminate:sender
{
	/* If te is installed, remove it. */
  if( te)
    {
      DPSRemoveTimedEntry( te);
      te=(DPSTimedEntry)0;
    }
	/* Make sure we close kmem. */
  if( kmem>-1)
    {
      close( kmem);
      kmem=-1;
    }
  return self;
}
- togglePause:sender
{
  if( te)
    {
      [pauseMenuCell setTitle:"Continue"];
      DPSRemoveTimedEntry( te);
      te=(DPSTimedEntry)0;
    }
  else
    {
      float f=0.5;
      [pauseMenuCell setTitle:"Pause"];
      if( NXGetDefaultValue( [NXApp appName], "UpdatePeriod"))
	{
	  f=atof( NXGetDefaultValue( [NXApp appName], "UpdatePeriod"));
	  f=MAX( f, MINPERIOD);
	}
      [periodText setFloatValue:f];
      te=DPSAddTimedEntry( f, (void *)_step, self, NX_MODALRESPTHRESHOLD);
    }
  return [self display];
}
- setPeriod:sender
{
  NXWriteDefault( [NXApp appName], "UpdatePeriod", [periodText stringValue]);
  if( te)
    {
      float f=0.5;
      DPSRemoveTimedEntry( te);
      if( NXGetDefaultValue( [NXApp appName], "UpdatePeriod"))
	{
	  f=atof( NXGetDefaultValue( [NXApp appName], "UpdatePeriod"));
	  f=MAX( f, MINPERIOD);
	}
      [periodText setFloatValue:f];
      te=DPSAddTimedEntry( f, (void *)_step, self, NX_MODALRESPTHRESHOLD);
    }
  return self;
}
- setFactor:sender
{
  NXWriteDefault( [NXApp appName], "LayerFactor", [factorText stringValue]);
  layerFactor=16;
  if( NXGetDefaultValue( [NXApp appName], "LayerFactor"))
    {
      layerFactor=atoi( NXGetDefaultValue( [NXApp appName], "LayerFactor"));
      layerFactor=MAX( layerFactor, MINFACTOR);
    }
  [factorText setIntValue:layerFactor];
  return [self display];
}
- 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
