//

import java.awt.*;
import java.util.*;

/*
  THINGS TO ADD:
      Cleaner API - arrays should be created internally
      Shapes other than circular disk
      Allow some robots to be more "stubborn" than others
      General obstacle objects
*/

public class pathApplet extends BufferedApplet
{
   int w = 0, h = 0, step = 0, nSteps = 1<<6, relaxing = 0, randomSeed = 0;
   PathFinder pf;

   // EACH ROBOT HAS A RADIUS OF PERSONAL SPACE

   double radius[] = {.20,.18,.16,.14,.12,.10};

   // EACH ROBOT HAS A COLOR

   double color[][] = {
      {1 , 0, 0}, // RED
      {1 ,.5, 0}, // ORANGE
      {1 ,.8, 0}, // YELLOW
      {0 ,.7, 0}, // GREEN
      {0 , 0, 1}, // BLUE
      {.5, 0,.5}, // VIOLET
   };

   double X[][] = new double[radius.length][nSteps+1];
   double Y[][] = new double[radius.length][nSteps+1];

   // DISPLAY EVERYTHING

   public void render(Graphics g) {

      // MAKE INITIAL PATH

      if (w == 0) {
         w = bounds().width;
         h = bounds().height;
         pf = new PathFinder(radius, X, Y);
         newRandomPaths(randomSeed);
      }

      // BLANK OUT THE APPLET WINDOW

      g.setColor(Color.white);
      g.fillRect(0,0,w,h);

      // DRAW THE SOLID DISK OF EACH ROBOT

      for (int i = 0 ; i < radius.length ; i++) {
	 g.setColor(new Color((float)(.7+.3*color[i][0]),
	                      (float)(.7+.3*color[i][1]),
			      (float)(.7+.3*color[i][2])));
	 int x = X2x(X[i][step]);
	 int y = Y2y(Y[i][step]);
         int r = R2r(radius[i]);
	 g.fillOval(x-r,y-r,2*r+1,2*r+1);
      }

      // DRAW THE PATHS AND THE ROBOT OUTLINES

      for (int i = 0 ; i < radius.length ; i++) {
	 g.setColor(new Color((float)color[i][0],
			      (float)color[i][1],
			      (float)color[i][2]));
	 for (int s = 0 ; s <= nSteps ; s++) {
            int x = X2x(X[i][s]);
            int y = Y2y(Y[i][s]);
	    g.fillOval(x-1,y-1,3,3);
         }
	 int x = X2x(X[i][step]);
	 int y = Y2y(Y[i][step]);
         int r = R2r(radius[i]);
	 g.drawOval(x-r,y-r,2*r,2*r);
	 int d = step==0 || step==nSteps ? 5 : 3;
	 g.fillOval(x-d,y-d,2*d,2*d);
      }

      // DRAW THE "SEED POINTS" RECTANGLE, AND SHOW THE SETUP ID

      g.setColor(Color.black);
      //g.drawRect(X2x(-.75),Y2y(.75),R2r(1.5),R2r(1.5));
      g.drawString("" + randomSeed, 4, 14);

      // GRADUALLY RELAX THE PATHS TO SMOOTH THEM OUT

      if (relaxing-- > 0) {
	 pf.relax();
	 animating = true;
      }
   }

   // RESPOND TO USER KEY COMMANDS

   public boolean keyUp(Event e, int key) {
      switch (key) {
      case '-':             // MAKE ROBOTS SMALLER
         changeSizes(-.02);
	 break;
      case '+':             // MAKE ROBOTS LARGER
         changeSizes(.02);
	 break;
      case 1002:            // PAGE UP: GO TO END OF PATHS
         step = nSteps;
	 damage = true;
         break;
      case 1003:            // PAGE DOWN: GO TO START OF PATHS
         step = 0;
	 damage = true;
         break;
      case 1004:            // UP ARROW: ADVANCE ALONG PATHS
         step = Math.min(step + nSteps/32, nSteps);
         damage = true;
         break;
      case 1005:            // DOWN ARROW: RETREAT ALONG PATHS
         step = Math.max(step - nSteps/32, 0);
         damage = true;
         break;
      case 1006:            // LEFT ARROW: PREVIOUS RANDOM SETUP
         newRandomPaths(--randomSeed);
	 break;
      case 1007:            // RIGHT ARROW: NEXT RANDOM SETUP
         newRandomPaths(++randomSeed);
	 break;
      }
      return true;
   }

   // ALLOW USER TO DRAG ROBOTS AROUND WITH THE MOUSE

   int mx, my, id = -1;

   public boolean mouseDown(Event e, int x, int y) {

      // ON MOUSE DOWN: SEE WHAT DISK, IF ANY, THE MOUSE IS OVER

      if (step == 0 || step == nSteps)
         for (id = radius.length-1 ; id >= 0 ; id--) {
	    double dx = x2X(x) - X[id][step];
	    double dy = y2Y(y) - Y[id][step];
	    if (dx*dx + dy*dy < radius[id]*radius[id])
	       break;
         } 
      mx = x;
      my = y;
      return true;
   }

   public boolean mouseDrag(Event e, int x, int y) {
      if (id >= 0) {
	 X[id][step] += r2R(x - mx);
	 Y[id][step] -= r2R(y - my);
	 mx = x;
	 my = y;
	 pf.repel(step);
	 pf.modifyPaths();
	 relaxing = 500;
	 damage = true;
      }
      return true;
   }

   public boolean mouseUp(Event e, int x, int y) {
      id = -1;
      damage = true;
      return true;
   }

   // CHANGE SIZES OF ROBOTS (WITHIN LIMITS)

   void changeSizes(double delta) {

      // MAKE SURE WE'RE NOT TRYING TO MAKE ROBOTS TOO BIG OR TOO SMALL

      double minRadius = 100, maxRadius = -100;
      for (int i = 0 ; i < radius.length ; i++) {
         minRadius = Math.min(minRadius, radius[i]);
         maxRadius = Math.max(maxRadius, radius[i]);
      }

      // IF WITHIN LIMITS, THEN CHANGE SIZES AND RECOMPUTE THE PATHS

      if (minRadius + delta > .01 && maxRadius + delta < .4) {
         for (int i = 0 ; i < radius.length ; i++)
	    radius[i] += delta;
         pf.repel(0);
         pf.repel(nSteps);
	 pf.findPaths();
	 damage = true;
      }
   }

   // MAKE A NEW PATH, GIVEN A RANDOM SEED

   void newRandomPaths(int seed) {

      // CHOOSE RANDOM POSITIONS AT START AND END OF PATH

      Random rand = new Random(seed);
      randomize(rand, 0);
      randomize(rand, nSteps);

      // FIND THE PATHS, AND START GRADUAL RELAXATION

      pf.findPaths();
      step = 0;
      relaxing = 500;
      damage = true;
   }

   // ARRANGE ROBOTS IN SOME RANDOM CONFIGURATION AROUND A SQUARE

   void randomize(Random rand, int s) {
      for (int i = 0 ; i < radius.length ; i++) {
         double x = (Math.abs(rand.nextDouble()) % 1) - .5;
         double y = (Math.abs(rand.nextDouble()) % 1) - .5;
	 if (Math.abs(x) > Math.abs(y)) {
	    y /= Math.abs(x);
	    x /= Math.abs(x);
	 }
	 else {
	    x /= Math.abs(y);
	    y /= Math.abs(y);
	 }
         X[i][s] = .75*x;
         Y[i][s] = .75*y;
      }
      pf.repel(s);
   }

   // CONVERT SCREEN COORDS TO ROBOT COORDS

   double x2X(int x) { return (double)(x - w/2) /  (w/2); }
   double y2Y(int y) { return (double)(y - h/2) / -(w/2); }
   double r2R(int r) { return (double)r / (w/2); }

   // CONVERT ROBOT COORDS TO SCREEN COORDS

   int X2x(double X) { return (int)(w/2 + X * w/2); }
   int Y2y(double Y) { return (int)(h/2 - Y * w/2); }
   int R2r(double R) { return (int)(R * w/2); }
}