/* RoachesView is Copyright 1994 by Brian Hill <bhill@physics.ucla.edu>. */

#import "Roach.h"
#import "RoachesView.h"
#import <dpsclient/dpswraps.h>
/* #import "RoachWraps.h" */
#import "point.h"

#define NOT_STUCK 0
#define STUCK_TURNING_CCW 1
#define STUCK_TURNING_CW -1
#define TURN_SLOWER 0.6f
#define TURN_FASTER_WHEN_STUCK 12.0f
#define ONCE_IN_A_WHILE 0.0003f

#define ROACHSIZE (NXCoord)14.0f
/* ROACHSIZE is the radius of a Roach.  A Roach should not come within 
 * ROACHSIZE of a View's bounds, nor within 2 * ROACHSIZE of another Roach. 
 * This latter restriction is not currently imposed. */

#define RETRIES 20

#define PI 3.14159265f

/* A couple of useful functions for discrete angles. */

static float radiansConversion = 2.0f * PI / (float)RESOLUTION;
#define ANGLE2RADIANS(angle) ((float)angle * radiansConversion)

void fixUpAngle(int *angle)
{
  while (*angle <= 0) *angle += RESOLUTION;
  while (*angle > RESOLUTION) *angle -= RESOLUTION;
  return;
}

int radians2angle(float angleInRadians)
{
  /* Caller's problem if angleInRadians doesn't sensibly round to an int
   * Good place to raise an exception.  In any case, this function would
   * have to be completely rewritten to handle large angles well. */
  int angle;
  angleInRadians /= radiansConversion;
  angle = (int)floor((double)angleInRadians + 0.5);
  fixUpAngle(&angle);
  return angle;
}

/* Stuff for user paths that draw Roaches. */

typedef struct {
  float x, y;
  char op;
} userPathEntry;

#define BODY_PATH_LENGTH 14
#define LEGS_PATH_LENGTH 6
#define MAX_PATH_LENGTH 14 /* The larger of the preceding lengths. */

/* Encloses roach. */
static float bbox[4] = {
  0.0f, 
  0.0f, 
  2.0f * (float)ROACHSIZE, 
  2.0f * (float)ROACHSIZE
};

static userPathEntry bodyPathEntries[BODY_PATH_LENGTH] = {
  { 1.0,   0.0,  dps_moveto},
  { 0.95,  0.15, dps_lineto},
  { 0.90,  0.17, dps_lineto},
  { 0.45,  0.25, dps_lineto},
  { 0.25,  0.36, dps_lineto},
  {-0.36,  0.36, dps_lineto},
  {-0.72,  0.22, dps_lineto},
  {-0.95,  0.0,  dps_lineto},
  {-0.72, -0.22, dps_lineto},
  {-0.36, -0.36, dps_lineto},
  { 0.25, -0.36, dps_lineto},
  { 0.45, -0.25, dps_lineto},
  { 0.90, -0.17, dps_lineto},
  { 0.95, -0.15, dps_lineto}
};

static userPathEntry legsPathEntries[LEGS_PATH_LENGTH] = {
  {-0.70, -0.70, dps_moveto},
  { 0.70,  0.70, dps_lineto},
  { 0.0,  -0.85, dps_moveto},
  { 0.0,   0.85, dps_lineto},
  { 0.70, -0.70, dps_moveto},
  {-0.70,  0.70, dps_lineto}
};

float randomf(void);

@implementation Roach:Object

- initRoachesView:view
{
  roachesView = view;
  return [super init];
}

- setColor:(NXColor)roachesColor
{
  color = roachesColor;
  return self;
}

- (BOOL)reposition 
{
  unsigned count = 0;
  /* A Roach must be repositioned if it finds itself outside of a resized
   * window.  Going from a smaller window to a larger window, one will find
   * the Roaches initially clustered around the lower left part of the new
   * window.  A Roach must also must be positioned if it has never been 
   * positioned (a case which is flagged by zero angle).  Zero angle is
   * also used as a flag in the drawSelf method. */
  angle[DRAW] = radians2angle(2.0f * PI * randomf());
  /* An unitialized position will be zero and okPosition:: will return NO. */
  while (![roachesView okPosition:&position[DRAW] :ROACHSIZE]) {
    NXRect bounds;
    if (++count > RETRIES) return NO; /* RoachesView is too crowded. */
    [roachesView getBounds:&bounds];
    position[DRAW].x = ROACHSIZE + 
      (NXCoord)randomf() * (bounds.size.width - (NXCoord)2.0f * ROACHSIZE);
    position[DRAW].y = ROACHSIZE + 
      (NXCoord)randomf() * (bounds.size.height - (NXCoord)2.0f * ROACHSIZE);
    /* Due to nonzero ROACHSIZE (and the extremely slim possibility that
     * randomf() could return 0.0f), assignment can fail to be inside the 
     * bounds.  Hence the while statement rather than an if statement. */
  }
  return YES;
}

- (BOOL)okPosition:(const NXPoint *)positionToCheck :(NXCoord)size
{
  int i;
  for (i = 0; i < 2; ++i) {
    if (angle[i] != 0) {
      if (squareDistance(positionToCheck, &position[i]) < size + ROACHSIZE) {
	return NO;
      }
    }
  }
  return YES;
}

- (const NXPoint *)position
{
  return &position[DRAW];
}

- oneStep
{
  /* If necessary, Roaches will have been repositioned and sizeInvalid */
  /* will be NO by the time this method is reached. */
  NXPoint translation;
  NXPoint saveOldPosition; /* In case I want it back.  Why overwrite 
			      position[DRAW] before I know I need it?  Because
			      okPosition:: is expecting exactly one Roach to
			      be at the same position. */ 
  int exposed, newExposed;
  float deltaAngle;
  unitVector(&translation, ANGLE2RADIANS(angle[DRAW]));
  scale(&translation, (NXCoord)[roachesView scaledSpeed]);
  set(&saveOldPosition, &position[DRAW]);
  translate(&position[DRAW], &translation);
  deltaAngle = TURN_SLOWER * (2.0f * randomf() - 1.0f);
  /* deltaAngle is in units imposed by RESOLUTION.
   * The formula for deltaAngle must give a value larger in absolute value
   * than 0.5 at least some of the time, or a Roach that is not stuck will 
   * never turn.  See comment where RESOLUTION is defined in RoachesView.h. */
  /* See if new position is ok.  What I do with deltaAngle depends on this. */
  exposed = [roachesView positionExposed:&saveOldPosition :ROACHSIZE];
  newExposed = [roachesView positionExposed:&position[DRAW] :ROACHSIZE];
  if (newExposed == YES) {
    /* Once in a while a roach goes out despite exposure. */
    if (randomf() < ONCE_IN_A_WHILE) newExposed = NO;
  }
  if ((exposed == EXPOSED || newExposed == HIDDEN) &&
      [roachesView okPosition:&position[DRAW] :ROACHSIZE]) {
    stuck = NOT_STUCK;
  } else {
    /* Roach can't go this direction. Scrap new position, and only
     * change angle.  So that Roach will
     * be a little more intelligent about getting unstuck, the stuck instance
     * variable records
     * whether it is turning cc or ccw to get unstuck.  If it is set, it
     * continues in that direction. */
    set(&position[DRAW], &saveOldPosition);
    if (stuck == NOT_STUCK) {
      stuck = deltaAngle >= 0.0f ? STUCK_TURNING_CCW : STUCK_TURNING_CW;
    }
    deltaAngle = stuck * fabs(deltaAngle) * TURN_FASTER_WHEN_STUCK;
  }
  {
    float temp;
    temp = (float)angle[DRAW] + deltaAngle + 0.5f;
    angle[DRAW] = (int)floor((double)temp);
    fixUpAngle(&angle[DRAW]);
  }
  return self;
}

- prepareToDraw
{
  NXImage *drawBuffer, **sharedImages;
  int i;
  NXColor backgroundColor;
  const NXSize imageSize = {
    (NXCoord)(2.0f * ROACHSIZE), 
    (NXCoord)(2.0f * ROACHSIZE)
  };
  sharedImages = [roachesView sharedImages];
  if ([roachesView imageClean][angle[DRAW]] || angle[DRAW] == 0) { 
    /* Already prepared to draw. */
  } else {
    float pts[2 * MAX_PATH_LENGTH];
    char ops[MAX_PATH_LENGTH];
    [roachesView imageClean][angle[DRAW] % RESOLUTION] = YES;
    /* Lazy instantiation of sharedImages. */
    drawBuffer = sharedImages[angle[DRAW] % RESOLUTION];
    if (!drawBuffer) {
      drawBuffer = [NXImage allocFromZone:[self zone]];
      [drawBuffer initSize:&imageSize];
      sharedImages[angle[DRAW] % RESOLUTION] = drawBuffer;
    }
    [drawBuffer lockFocus];
    PSsetlinewidth(0.0f);
    /* The following choice of background colors is important when a device
     * uses patterns to represents colors outside of it's color space. */
    backgroundColor = NX_COLORCLEAR;
/*    PSsetrgbcolor(NXRedComponent(backgroundColor), 
		  NXGreenComponent(backgroundColor), 
		  NXBlueComponent(backgroundColor)); */
    PSsetalpha(0.0f);
    PSrectfill(0.0f, 0.0f, 2.0f * (float)ROACHSIZE, 2.0f * (float)ROACHSIZE);
    PSsetrgbcolor(NXRedComponent(color), 
		  NXGreenComponent(color), 
		  NXBlueComponent(color));
    PSsetalpha(1.0f);
    
    for (i = 0; i < BODY_PATH_LENGTH; ++i) {
      NXPoint pathPoint, translation = {(NXCoord)1.0f, (NXCoord)1.0f};
      pathPoint.x = (NXCoord)bodyPathEntries[i].x;
      pathPoint.y = (NXCoord)bodyPathEntries[i].y;
      rotate(&pathPoint, ANGLE2RADIANS(angle[DRAW]));
      translate(&pathPoint, &translation);
      scale(&pathPoint, ROACHSIZE);
      pts[2 * i] = pathPoint.x;
      pts[2 * i + 1] = pathPoint.y;
      ops[i] = bodyPathEntries[i].op;
    }
    DPSDoUserPath((void *)pts, 2 * BODY_PATH_LENGTH, dps_float, ops, 
		  BODY_PATH_LENGTH, (void *)bbox, dps_ufill);
    
    /* Should eliminate duplication between legs path code and body path 
     * code */
    for (i = 0; i < LEGS_PATH_LENGTH; ++i) {
      NXPoint pathPoint, translation = {(NXCoord)1.0f, (NXCoord)1.0f};
      pathPoint.x = (NXCoord)legsPathEntries[i].x;
      pathPoint.y = (NXCoord)legsPathEntries[i].y;
      rotate(&pathPoint, ANGLE2RADIANS(angle[DRAW]));
      translate(&pathPoint, &translation);
      scale(&pathPoint, ROACHSIZE);
      pts[2 * i] = pathPoint.x;
      pts[2 * i + 1] = pathPoint.y;
      ops[i] = legsPathEntries[i].op;
    }
    DPSDoUserPath((void *)pts, 2 * LEGS_PATH_LENGTH, dps_float, ops, 
		  LEGS_PATH_LENGTH, (void *)bbox, dps_ustroke);
    [drawBuffer unlockFocus];
  }
  return self;
}

- erase
{
  if (angle[ERASE] == 0) return self;
  PSrectfill((float)(position[ERASE].x - ROACHSIZE),
	     (float)(position[ERASE].y - ROACHSIZE),
	     (float)(2.0f * ROACHSIZE),
	     (float)(2.0f * ROACHSIZE));
  return self;
}

- compositeDraw
{
  NXPoint compositePoint;
  /* I could choose to not to draw the Roach if rects[0] (or rects[1] and 
   * rects[2]) do not intersect it.  In that case though, animation speed
   * would depend on the number of Roaches that are visible. */
  set(&position[ERASE], &position[DRAW]);
  angle[ERASE] = angle[DRAW];
  if (angle[DRAW] == 0) return self;
  compositePoint.x = (NXCoord)(position[DRAW].x - ROACHSIZE);
  compositePoint.y = (NXCoord)(position[DRAW].y - ROACHSIZE);
  [[roachesView sharedImages][angle[DRAW] % RESOLUTION] composite:NX_SOVER toPoint:&compositePoint];
  return self;
}

@end

static const float factor=(1.0f/(float)LONG_MAX);

float randomf(void)
{
  return random() * factor;
}
