import java.awt.*;

class Task {
   public int left = -1, right = -1;
   public Task(int left, int right) { this.left = left; this.right = right; }
}

public class AnimatedQuickSort extends MyObject implements Runnable {

   private static XtangoApplet xa = null;
   private static final int MAX_PER_LINE = 15;
   private static int N = 10;
   private static int RANGE = 100;
   private static int NCPU = 4;
   private static boolean debug = false;
   private static final CountingSemaphore doneCount
      = new CountingSemaphore(0); // same as sending empty messages async
   private static final AsyncMessagePassing task
      = new AsyncMessagePassing();
   private static int[] nums = null;

   private int id = -1;

   public AnimatedQuickSort(XtangoApplet a) { xa = a; }

   private AnimatedQuickSort(String name, int id) {
      super(name + id);
      this.id = id;
      new Thread(this).start();
   }

//#### animator #####v   
   private static float scaleX(int x) { return (float)x/(float)N; }

   private static float scaleY(int y) { return (float)y/(float)RANGE; }

   private static int maxx(int[] number, int left, int right) {
      // maximum in number[left] to number[right] inclusive
      int mx = number[left];
      for (int i = left+1; i <= right; i++) {
         if (number[i] > mx) mx = number[i];
      }
      return mx;
   }

   private static int minn(int[] number, int left, int right) {
      // minimum in number[left] to number[right1] inclusive
      int mn = number[left];
      for (int i = left+1; i <= right; i++) {
         if (number[i] < mn) mn = number[i];
      }
      return mn;
   }

   private static final Color[] colors = {Color.red, Color.green,
      Color.blue, Color.yellow, Color.magenta, Color.cyan};
//#### animator #####^

   private static void printArray(int[] a) {
      int count = 0;
      for (int i = 0; i < a.length; i++) {
         System.out.print(" " + a[i]); count++;
         if (count % MAX_PER_LINE == 0) System.out.print("\n");
      }
      if (count % MAX_PER_LINE != 0) System.out.print("\n");
   }

   private static void quickSort(int worker, int left, int right) {

      int pivot = nums[left];
      int l = left, r = right;

      if (right-left <= 0) {
         System.err.println("right-left<=0, error!"); return;
      }

//#### animator #####v
      // enclose what this worker is working on in a black outline rectangle
      int ymin = minn(nums, left, right);
      int ymax = maxx(nums, left, right);
      float xpos, ypos, xsize, ysize;
      xpos = scaleX(left); ypos = scaleY(ymin);
      xsize = scaleX(right-left); ysize = scaleY(ymax-ymin);
      xa.rectangle("rect"+worker, xpos, ypos, xsize, ysize, Color.black,
         xa.OUTLINE);
      xa.delay(1);
      // change items sorted to this worker's color
      for (int i = left; i <= right; i++) {
         xa.color("nums"+i, colors[worker]);   // would be better to do
         xa.fill("nums"+i, xa.SOLID);          // these as a batch i.e.
      }                                        // no frameDelay
      xa.delay(1);
      // make pivot outline color
      xa.fill("nums"+left, xa.OUTLINE);
      // draw a black horizontal line from the pivot across the rectangle
      xpos = scaleX(left); ypos = scaleY(pivot);
      xsize = scaleX(right-left); ysize = 0.0f;
      xa.line("lineH"+worker, xpos, ypos, xsize, ysize, Color.black,
         xa.THIN);
      // draw two vertical lines at left+1 and right
      xpos = scaleX(l+1); ypos = scaleY(pivot);
      xsize = 0.0f; ysize = scaleY(ymax-pivot);
      xa.line("lineVL"+worker, xpos, ypos, xsize, ysize, Color.black,
         xa.THIN);
      xpos = scaleX(r); ypos = scaleY(ymin);
      xsize = 0.0f; ysize = scaleY(pivot-ymin);
      xa.line("lineVR"+worker, xpos, ypos, xsize, ysize, Color.black,
         xa.THIN);
      xa.delay(1);
//#### animator #####^

      boolean done = false;
      while (!done) {
         if (nums[l+1] > pivot) {
            while (r > l+1 && nums[r] > pivot) { r--;

//#### animator #####v
               // move the right vertical line one to the left
               if (!done) {
                  xa.jumpRelative("lineVR"+worker, scaleX(-1), 0.0f);
               }
//#### animator #####^

            }
            if (r > l+1) { l++;
               int temp = nums[r]; nums[r] = nums[l]; nums[l] = temp;
               done = l >= r-1;

//#### animator #####v
               // swap locations and ids of the objects
               xa.moveAsync("nums"+r, scaleX(l), scaleY(nums[l]));
               xa.move("nums"+l, scaleX(r), scaleY(nums[r]));
               xa.swapIds("nums"+r, "nums"+l);
//#### animator #####^

//#### animator #####v
               // move the left vertical line one to the right
               if (!done) {
                  xa.jumpRelative("lineVL"+worker, scaleX(1), 0.0f);
               }
//#### animator #####^

            } else done = true;
         } else { l++; done = l >= r;

//#### animator #####v
            // move the left vertical line one to the right
            if (!done) {
               xa.jumpRelative("lineVL"+worker, scaleX(1), 0.0f);
            }
//#### animator #####^

         }
      }
      int temp = nums[left]; nums[left] = nums[l]; nums[l] = temp;

//#### animator #####v
      // swap locations and ids of the objects
      xa.moveAsync("nums"+left, scaleX(l), scaleY(nums[l]));
      xa.move("nums"+l, scaleX(left), scaleY(nums[left]));
      xa.swapIds("nums"+left, "nums"+l);
//#### animator #####^

      if (right-(l+1) > 0) send(task, new Task(l+1, right));
      else if (right-(l+1) == 0) {  V(doneCount);

//#### animator #####v
         // color the object solid black to indicate it is in final position
         xa.color("nums"+right, Color.black);
         xa.fill("nums"+right, xa.SOLID);
//#### animator #####^

      }

//#### animator #####v
      // delete the line and rectangle objects
      xa.delete("rect"+worker);
      xa.delete("lineH"+worker);
      xa.delete("lineVL"+worker);
      xa.delete("lineVR"+worker);
//#### animator #####^

      V(doneCount);

//#### animator #####v
      // color the object solid black to indicate it is in final position
      xa.color("nums"+l, Color.black);
      xa.fill("nums"+l, xa.SOLID);
//#### animator #####^

      if ((l-1)-left > 0) send(task, new Task(left, l-1));
      else if ((l-1)-left == 0) {  V(doneCount);

//#### animator #####v
         // color the object solid black to indicate it is in final position
         xa.color("nums"+left, Color.black);
         xa.fill("nums"+left, xa.SOLID);
//#### animator #####^

      }
   }

   public void run() {
      Task m = null;
      String ids = String.valueOf(id);
      if (debug)
         System.out.println("*DEBUG* age()=" + age() + ", worker "
            + id + " alive");
      while (true) {
         m = (Task) receive(task);
         if (debug)
            System.out.println("*DEBUG* age()=" + age() + ", worker "
               + id + " received task, left=" + m.left
               + " right=" + m.right);
         xa.fill(ids, xa.SOLID);          //#### animator #####
         quickSort(id, m.left, m.right);
         xa.fill(ids, xa.OUTLINE);        //#### animator #####
      }
   }

   public static void main(String[] args) {

      // parse command line options, if any, to override defaults
      GetOpt go = new GetOpt(args, "Udn:r:p:");
      go.optErr = false;
      String usage = "Usage: -d -n N -r RANGE -p NCPU"
         + " nums[i] in [1,RANGE] for i=0,1,...,N";
      int ch = -1;
      while ((ch = go.getopt()) != go.optEOF) {
         if      ((char)ch == 'U') {
            System.out.println(usage);  System.exit(0);
         }
         else if ((char)ch == 'd') {
            debug = true;
            go.optErr = true;
         }
         else if ((char)ch == 'n')
            N = go.processArg(go.optArgGet(), N);
         else if ((char)ch == 'r')
            RANGE = go.processArg(go.optArgGet(), RANGE);
         else if ((char)ch == 'p')
            NCPU = go.processArg(go.optArgGet(), NCPU);
         else {
            System.err.println(usage);  System.exit(1);
         }
      }
      if (NCPU > 6) {
         System.err.println("Too many CPUs, must be <= 6, exiting");
         System.exit(1);
      }
      System.out.println("Quick sorting " + N
         + " numbers between 1 and " + RANGE
         + " using " + NCPU + " CPUs (debug=" + debug + ")\n"
         + " (or read POSITIVE numbers from the command line)");
      nums = new int[N];
      int argNum = go.optIndexGet();
      for (int i = 0; i < N; i++) {
         nums[i] = 1 + (int)(Math.random()*RANGE);
         nums[i] = go.tryArg(argNum++, nums[i]);
      }
      System.out.println("Original numbers:");
      printArray(nums);

//#### animator #####v
      xa.begin(); // wait here for user to click "Start"
      // change coordinates so there is room for CPU busy circles
      xa.coords(-0.05f, -0.05f, 1.3f, 1.3f);
      for (int i = 0; i < N; i++) { // display original numbers
         xa.circle("nums"+i, scaleX(i), scaleY(nums[i]), 0.75f*scaleX(1),
            Color.black, xa.OUTLINE);
      }
      xa.delay(1);
      // draw a line separating data from CPU busy circles
      xa.line("CPUline", 0.0f, 1.05f, 1.05f, 0.0f, Color.black, xa.THIN);
      // draw outline circles for each CPU
      for (int i = 0; i < NCPU; i++) {
         String ids = String.valueOf(i);
         xa.circle(ids, 0.1f+0.1f*i, 1.15f, 0.05f, colors[i], xa.OUTLINE);
      }
      xa.text("CPUtext", 0.1f+0.1f*NCPU, 1.15f, false, Color.black,
         "busy CPUs are solid");
      xa.delay(1);
//#### animator #####^

      // create the workers with self-starting threads
      for (int i = 0; i < NCPU; i++) new AnimatedQuickSort("Worker", i);
      System.out.println("age()=" + age() + ", all workers started");
      send(task, new Task(0, N-1));
      // wait for enough "singletons" to be produced
      for (int i = 0; i < N; i++) P(doneCount);
      System.out.println("Sorted   numbers:");
      printArray(nums);
      System.out.println("age()=" + age() + ", done");
      xa.end();                                //#### animator #####
   }
}
