package wormhole;

import java.util.*;
import java.text.*;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.lang.*;


public class bouncer extends Applet implements Runnable{
  Thread timer = null;
  boolean isStandalone = false;
  final int NUMDOTS =  4;
  final int RAD     =  5;
  final int Hitside=0,Egress=1,Collide=2,Ingress=3, // TEvent
          Bounce=0,Hidden=1,Interact=2,Duplic=3;  //TSeq


  class TCoord{double x,y;}

  double B,Time,DTime,STime,EventTime,T1,Hgt,Wid,XHit,YHit;
  double vel,vel2,diff,vsq,tmp;
  double Z,alpha;
  double phi,psi,theta,R,Y,calph;
  boolean below,flg,On,chng;
  int nballs;
  int NextEvent;
  int CurSeq;
  TCoord v1=new TCoord();
  TCoord v2=new TCoord();
  TCoord p1=new TCoord();
  TCoord p2=new TCoord();
  int brad,wrad,xoffs,yoffs,h,ps,phgt,pwid;
  double r,g,b,dr,dg,db;
  double scale;
  Image BackImg;
  Color bcol=new Color(230,240,240);
//LOGBRUSH LBr;


  public void init() {
  int i;
  p1.x=0;p1.y=0;
  v1.x=0.8;v1.y=-0.30;
  r=8;g=8;b=8;h=0;ps=0;
  dr=0.073;dg=0.173;db=-0.149;
  Wid=1.32;B=0.13;
  pwid=600;
  phgt=450;
  resize(pwid,phgt);
  xoffs=pwid/2;yoffs=phgt/2;
  scale=pwid/(2*Wid);
  Hgt=  phgt/(2*scale);
  wrad=(int)(B*scale);brad=(int)(0.05*scale);
  nballs=1;
  DTime=0.06f;
  WhatNext();STime=0;
  On=false;chng=false;
  BackImg=createImage(pwid,phgt);
  setBackground(bcol);
}

double sqr(double x){return x*x;}

double phi1;
double f(){
double cphi,sphi,tphi,dY,dR,dTheta,err;
double deriv,phi2,b0;

  phi=phi1;
  cphi=Math.cos(phi);tphi=Math.tan(phi);sphi=Math.sin(phi);
  Y=(B*(B-cphi)+0.25*(1-tphi*tphi))/(2*B*sphi-tphi);
  R=tphi/2-Y;
  tmp=(Z-Y)*Math.cos(calph)/R;  //tmp in [0,pi]
  if (Math.abs(tmp)>1){
    phi=(phi+Math.PI/2)/2;
    return -999;}
  theta=(Math.acos(tmp)+calph)/2;      // + for 1st intersect  OK for /2
//  v1:=4*R*sphi*sphi*cos(theta)/cos(gamma-theta);
  dY=(tphi/2-Y)*(2*B*cphi-1-tphi*tphi)/(2*B*sphi-tphi);
  dR=(1+tphi*tphi)/2-dY;
  dTheta=(dY*Math.cos(calph)+dR*Math.cos(2*theta-calph))/(2*R*Math.sin(2*theta-calph));
  err=4*R*sphi*sphi*Math.cos(theta)-vel*Math.sin(theta-calph);
  deriv=4*sphi*((dR*sphi+2*R*cphi)*Math.cos(theta)-R*sphi*Math.sin(theta)*dTheta)-vel*Math.cos(theta-calph)*dTheta;
  phi2=phi-err/deriv;
  b0=Math.asin(Math.sqrt(2*phi/Math.PI));
  phi1=Math.PI*sqr(Math.sin(b0-2*err/(deriv*Math.PI*Math.sin(2*b0))))/2;
  return(phi1-phi)/phi;}

boolean Collision(){
final double eps=0.000001;
double T,fr;
int   ct;
int ctlim=25;
below=v1.y>0;
Z=diff/v1.x;
if (below){Z=-Z;calph=-alpha;}
else calph=alpha;
phi1=1;ct=0;
while ((Math.abs(fr=f())>eps)&&(ct<ctlim))ct=ct+1;
if (ct>=ctlim ) return false;
XHit=-R*Math.sin(2*theta);
YHit=Y+R*Math.cos(2*theta);
T1=(1+Math.tan(theta)/Math.tan(phi))/2;
vel2=4*R*Math.sin(phi)*Math.cos(theta);
if (below){
 YHit=-YHit;
 phi=-phi;
 theta=-theta;}
EventTime=(XHit-p1.x)/v1.x-T1;
psi=Math.PI-phi+theta;
CurSeq=Interact;
NextEvent=Egress;
return true;}

boolean Join(){
vel=Math.sqrt(sqr(v1.x)+sqr(v1.y));
return v1.y*p1.y<0 &&Math.abs(diff)<Math.abs(v1.y)*(0.5-B)&&Collision();}

boolean Worm(int d){
double xpos,ypos,q,c,det,det1;
boolean result;
c=d-1.5;
q= c*v1.x-p1.x*v1.x-p1.y*v1.y;
det= q*q-vsq*(p1.x*p1.x+p1.y*p1.y+0.25-2*c*p1.x-B*B);
//det1=vsq*sqr(B)-sqr(diff+c*v1.y);
result= (q>0)&& (det>0)&&(q*q>det);
if (!result) return result;
EventTime= (q-Math.sqrt(det))/vsq;
ypos= p1.y+v1.y*EventTime;
xpos= p1.x+v1.x*EventTime-c;
vel=Math.sqrt(vsq);
if (d==1){
  if (xpos>-0.5&&Collision())return true;
  else{
    CurSeq= Duplic;
    EventTime= EventTime-1;
    NextEvent= Egress;
    phi= Math.atan2(ypos,xpos);
    psi= alpha-2*phi;}}
else {CurSeq= Hidden;
  NextEvent=Ingress;
  phi= Math.atan2(ypos,-xpos);
  psi= alpha+2*phi;}
  return true;
}

void WhatNext(){
double d;
boolean Yout;
CurSeq=Bounce;
if (v1.y>0) {
  YHit=Hgt;
  Yout=p1.y>B;}
else {
  YHit=-Hgt;
  Yout=p1.y<-B;}
diff=p1.y*v1.x-p1.x*v1.y;
vsq= v1.x*v1.x+v1.y*v1.y;
vel=Math.sqrt(vsq);
alpha=Math.atan2(v1.y,v1.x);
if (v1.x>0){
  if (p1.x>B+0.5||Yout||!((p1.x<B-0.5)&&Worm(1)||Worm(2)||Join()))
    XHit=Wid;}
else if (p1.x<-B-0.5||Yout||!(p1.x>0.5-B && Worm(2)||Worm(1)||Join())){
  XHit= -Wid;}
if (CurSeq!=Bounce)return;
NextEvent=Hitside;
d=Math.abs((YHit-p1.y)*v1.x)-Math.abs((XHit-p1.x)*v1.y);
flg=d>0;
if (flg) EventTime= (XHit-p1.x)/v1.x;
else EventTime=(YHit-p1.y)/v1.y;}

void Move(){
double tfrac;
 Time=Time+DTime;
 if (Time>EventTime){
  tfrac=Time-EventTime;
  Time=0;
  switch (NextEvent){
    case Hitside:
      if (flg){v1.x=-v1.x;
	p1.y=p1.y+DTime*v1.y;
        if (Math.abs(p1.y)>Hgt) p1.y=2*YHit-p1.y;
	p1.x=p1.x+tfrac*v1.x;}
      else {v1.y=-v1.y;
	p1.x=p1.x+DTime*v1.x;
	if (Math.abs(p1.x)>Wid) p1.x=2*XHit-p1.x;
	p1.y=p1.y+tfrac*v1.y;}
      WhatNext();break;
   case Egress:
      if (CurSeq==Hidden){
        nballs=1;
	v1.x=vel*Math.cos(psi);
        v1.y=vel*Math.sin(psi);
        p1.x=-0.5+B*Math.cos(phi);
        p1.y=B*Math.sin(phi);
        WhatNext();}
      else{nballs=2;
	v2.x=vel*Math.cos(psi);
        v2.y=vel*Math.sin(psi);
        p2.x=0.5-B*Math.cos(phi)+tfrac*v2.x;
        p2.y=B*Math.sin(phi)+tfrac*v2.y;
        if (CurSeq==Interact){
          NextEvent=Collide;
          EventTime=T1-tfrac;}
        else{
          NextEvent=Ingress;
	  EventTime=1-tfrac;}}
      break;
    case Ingress:if (CurSeq==Hidden){
        nballs=0;
	NextEvent=Egress;
        EventTime=1;}
      else{nballs=1;
	p1.x=p2.x;v1.x=v2.x;p1.y=p2.y;v1.y=v2.y;
	WhatNext();}
      break;
    case Collide:
        v1.x=-vel2*Math.cos(theta+phi);
        v1.y=-vel2*Math.sin(theta+phi);
        v2.x=vel*Math.cos(2*theta-alpha);
        v2.y=vel*Math.sin(2*theta-alpha);
        p1.x=XHit+v1.x*tfrac;
        p1.y=YHit+v1.y*tfrac;
        p2.x=XHit+v2.x*tfrac;
        p2.y=YHit+v2.y*tfrac;
        NextEvent=Ingress;
	EventTime=1-T1-tfrac;}
}
else{
  if (nballs>0){
    p1.x=p1.x+DTime*v1.x;p1.y=p1.y+DTime*v1.y;
  if (nballs>1){p2.x=p2.x+DTime*v2.x;p2.y=p2.y+DTime*v2.y;}}}
}

public void paint(Graphics gr){
long tc;
int wx,wy,sx,sy,sx2,sy2;
wx=(int)(0.5*scale+xoffs);wy=yoffs;
gr.setColor(new Color((int)(r*r),(int)(g*g),(int)(b*b)));
gr.fillOval(wx-wrad,wy-wrad,2*wrad,2*wrad);
wx=(int)(-0.5*scale+xoffs);
gr.setColor(new Color((int)sqr(16-r),(int)sqr(16-g),(int)sqr(16-b)));
gr.fillOval(wx-wrad,wy-wrad,2*wrad,2*wrad);
gr.setColor(Color.magenta);
if (nballs>0){
  sx=(int)(p1.x*scale+xoffs);sy=(int)(yoffs-p1.y*scale);
  sx2=(int)(xoffs+p2.x*scale);sy2=(int)(yoffs-p2.y*scale);
  gr.fillOval(sx-brad,sy-brad,2*brad,2*brad);
  if (nballs>1)gr.fillOval(sx2-brad,sy2-brad,2*brad,2*brad);
  gr.setColor(Color.black);
  gr.drawOval(sx-brad,sy-brad,2*brad-1,2*brad-1);
  if (nballs>1)gr.drawOval(sx2-brad,sy2-brad,2*brad-1,2*brad-1);
}}

public void start() {
if(timer == null){
  timer = new Thread(this);
  timer.start();}
}

public void stop() {
  timer = null;
}

public void run() {
while (timer != null) {
  try {Thread.sleep(50);} catch (InterruptedException e){}
if (r+dr>16||r+dr<0){dr=-dr;
  h=h+1;if (h>3)h=0;}
else r=r+dr;
if (b+db>16||b+db<0){db=-db;
  if (g+dg>16||g+dg<0)dg=-dg;else g=g+dg;
  ps=ps+1;if (ps>4) ps=0;}
else b=b+db;
Move();
repaint();}
timer = null;
}

public void update(Graphics gr) {
Graphics BackGr=BackImg.getGraphics();
BackGr.setColor(bcol);
BackGr.fillRect(0,0,pwid,phgt);
paint(BackGr);
gr.drawImage(BackImg,0,0,this);}

public bouncer() {}

}

