/* Author: Ross McNab, 1996 <rmcn@dcs.ed.ac.uk>
 * Vanilla Site Swap Juggler, v1.5
 * 16/1/96 - First translation into Java from C
 *  9/2/96 - Added smooth catching and improved behaviour of "1" throws
 */

import java.awt.*;
import java.applet.*;

class Pattern {
  int thrown[];
  int length = 0;
  float balls;

  boolean islower(char c) { return((c >= 'a') && (c <= 'z')); }
  boolean isdigit(char c) { return((c >= '0') && (c <= '9')); }

  public void parse_string(String strng)
  {
    int i, total;
    char c[];

    c = new char[strng.length()];
    length = strng.length();
    thrown = new int[length];
    total = 0;
    strng.getChars(0, length, c, 0);
    for(i = 0; i < length; i++) {
      if(islower(c[i]))
        thrown[i] = (c[i] - 'a') + 9;
      else {
        if(isdigit(c[i]))
          thrown[i] = (c[i] - '0');
        else {
          System.err.println("Error: unrecognised char in pattern string");
        }
      }
      total += thrown[i];
    }
    balls = total / i;
  }

  public void check_valid()
  {
    int i, j, land_time;  

    /* check number of balls is a whole number */
    if (balls != (float)((int)balls)) {
          System.err.println("Error: can't have " + balls + " balls!");
    }

    /* check no two balls land at same time */
    for(i = 0; i < length; i++) {
      land_time = i + thrown[i];
      for(j = i + 1; j < length; j++) {
        if(land_time == (j + thrown[j])) {
          System.err.println("Error: two balls land at same time");
        }
      }
    }
  }

  public int balls() {
    return((int)balls);
  }
  public int length() {
    return(length);
  }
  public int get_throw(int time_step) {
    return(thrown[time_step]);
  }
}

class Ball {
  float x;
  float re_throw_x;
  float y;
  float vx;
  float vy;
  float gravity;
  int throw_next;
  Color col;
  // Our colour table common to all balls
  static Color colours[] = { new java.awt.Color(255,0,0),
                             new java.awt.Color(0,255,0),
                             new java.awt.Color(0,0,255),
                             new java.awt.Color(255,255,0),
                             new java.awt.Color(0,255,255),
                             new java.awt.Color(255,0,255)  };


  public Ball(int i) {
    throw_next = -1;
    gravity = 0;
    col = colours[i % 6];
    x = -100;
  }

  // Draw a filled circle at (x,y) in colour c
  public void draw_ball(Graphics g, Color c)
  {
    int i, r=5, ny;
    int px;

    ny = (int)(4*y+190);
    for(i = 0; i <= r; i++) {
      px = (int)Math.sqrt(r*r - i*i);
      g.setColor(c);
      g.drawLine((int)(x+px), (ny +i),
                 (int)(x-px), (ny +i));
      g.drawLine((int)(x+px), (ny -i),
                 (int)(x-px), (ny -i));
    }
  }

  public void move_ball(float frames, int frame, Graphics g)
  {
    draw_ball(g, new java.awt.Color(192,192,192));
    if(throw_next != -1) {
      x += vx / frames;
      y += vy / frames;
      vy += (gravity / frames);
    }
    draw_ball(g, col);
  }

  public void caught_ball(Graphics g)
  {
    if((throw_next == 0) && (re_throw_x != -1)) {
      draw_ball(g, new java.awt.Color(192,192,192));
      y = 0;
      vx = (float)((re_throw_x - x)/0.5);
      gravity = -4*gravity;
      vy = (float)-(0.25 * gravity);
      draw_ball(g, col);
    }
  }

  public void reset_ball(float tx, float cx, float rtx,
                                       int throw_height, float g)
  {

    float throw_time;

    if(rtx == -1) throw_time = (float)throw_height;
             else throw_time = (float)(throw_height - 0.5);

     x = tx;                       y = 0;
    vx = (cx - tx)/throw_time;    vy = (float)-(0.5 * g * throw_time);
    throw_next = throw_height;
    re_throw_x = rtx;
    gravity = g;
  }

  public int throw_next() {
    return(throw_next);
  }

  public void next_time_step() {
    throw_next--;
  }
}

public class Juggle extends java.applet.Applet implements Runnable {
  Thread artist=null;         // The animation thread
  boolean allow_suspend = true;
  boolean threadSuspended = true;  // Used so we can stop anim by clicking
  Pattern p = new Pattern();  // The pattern we are juggling
  Ball balls[];               // Array string the balls
  float frames;               // How many frames are there in a "1" handoff?
  int frame = 0;              // The frame we are on
  int time_step = 0;          // Our current position in the ss-pattern
  boolean left = false;       // Next ball thrown with left hand?
  
  /* Initialise graphics area, and get parameters */
  public void init() {
    int i;
    String s = null, s2 = null, s3 = null;

    resize(320,200);              // Set juggle window size
    /* read in the pattern */
    s = getParameter("pattern");
    if(s == null) {
      System.err.println("Error: no pattern given, default: 3");
      s = "3";
    }
    p.parse_string(s);
    p.check_valid();
    /* read in the number of frames */
    s2 = getParameter("frames");
    if(s2 == null) {
      System.err.println("Error: no frames given, default: 20");
      s2 = "20";
    }
    frames = Float.valueOf(s2).floatValue();
    /* Do we want to allow starting and stopping? */
    s3 = getParameter("allow_suspend");
    if(s3 == null) {
      s3 = "true";
    }
    if(s3.equals("false")) { threadSuspended = false; allow_suspend = false; }
    balls = new Ball[p.balls()];
    for(i = 0; i < p.balls(); i++) {
      balls[i] = new Ball(i);
    }
  }


  /* This does all the work */
  public void paint(Graphics g) {
    int throw_height; // The height to throw the next ball
    int throw_ball;   // Index to next ball to be thrown
    float tx, cx, rtx;     // Throw, catch and re-throw X co-ords
    float gravity;    // The gravity the ball should experience
    int i;
    
    if(frame != 0) {
      frame = (frame + 1) % (int)frames;
      for(i = 0; i < p.balls(); i++)
        balls[i].move_ball(frames, frame, g);
      if(frame == (int)(frames/2))
        for(i = 0; i < p.balls(); i++)
          balls[i].caught_ball(g);
    } else {
      throw_height = p.get_throw(time_step);
      if(throw_height != 0) { // We don't want to throw a ball for 0
        // Find the ball due to be thrown
        for(throw_ball = 0; throw_ball < p.balls(); throw_ball++) {
          if(balls[throw_ball].throw_next() <=0) break;
        }
        if(left) {      /* which hands are we throwing from/to? */
          tx = 160 - (4 * 4);  // TLX
          cx = 160 + (13 * 4); // CRX
          rtx = 160 + (4*4);   // TRX
          if((throw_height % 2) == 0){  /* even throw, catch in same hand */
            cx = 160 - (13 * 4);     // CLX
            rtx = 160- (4 * 4);      // TLX
          }
        } else {
          tx = 160 + (4 * 4);      // TRX
          cx = 160 - (13 * 4);     // CLX
          rtx = 160 - (4 * 4);     // TLX
          if((throw_height % 2) == 0){  /* even throw, catch in same hand */
            cx = 160 + (13 * 4);    // CRX
            rtx = 160 + (4 * 4);    // TRX
          }
        }
        if(throw_height == 1) {
          cx = rtx;
          rtx = -1;
        }
        if(throw_height == 2) {
          gravity = 0;
          cx = tx;
        } else
          gravity = (float) 9.81;
        balls[throw_ball].draw_ball(g, new java.awt.Color(192,192,192));
        /* set up the ball's new position and speeds */
        balls[throw_ball].reset_ball(tx, cx, rtx, throw_height, gravity);
      }
      /* move on to next time step */
      for(i = 0; i < p.balls(); i++) {
        balls[i].next_time_step();
      }
      time_step = (time_step + 1) % p.length();
      left = !(left);
      frame = (frame + 1) % (int)frames;
    }
  }

  public void update(Graphics g) {
    paint(g);
  }

  public void start() {
    if (artist == null) {
      artist = new Thread(this);
      artist.start();
      if(threadSuspended) artist.suspend();
    }
  }

  public void stop() {
      artist = null;
  }

  public void run() {
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
    while (artist != null) {
      try {Thread.sleep(10);} catch (InterruptedException e){}
      repaint();
    }
    artist = null;
  }

  /* Handy function to start and stop the animation by clicking on it */
  public boolean mouseDown(java.awt.Event evt, int x, int y) {
    if (allow_suspend) {
      if (threadSuspended) {
        artist.resume();
      } else {
        artist.suspend();
      }
      threadSuspended = !threadSuspended;
    }
    return true;
  }

}
