package dmori.blindflies2;

import java.util.Random;
import java.awt.Image;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.geom.Line2D;
import java.awt.RenderingHints;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.Canvas;
import javax.swing.JApplet;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.lang.Class;
import java.applet.AudioClip;
import java.net.URL;

public class BlindFlies2 extends JApplet{
	FlySet flySet;
	int goodies,baddies,num,size,speed;
	JCheckBox TB=new JCheckBox("Antialias",true);
	JCheckBox CBP=new JCheckBox("Paused",false);
	JCheckBox CBS=new JCheckBox("Silent",false);
	JSlider JS=new JSlider(1,30,20);
	Thread mainThread;
	
	public String getAppletInfo(){return "BlindFlies v2, Nov2002 Diego Moriarty, not copyrighted";
	}
	public String[][] getParameterInfo(){
		String[][] r=new String[][]{
		{"GOODIES","1-int","number of good flies"
		},{"BADDIES","1-int","number of bad flies"
		},{"SIZE","1-int","size/2 of the body in pixels"
		}
		};
		return r;
	}
	
	public void init() {
  		goodies	= Integer.parseInt(getParameter("GOODIES"));
  		baddies	= Integer.parseInt(getParameter("BADDIES"));
		size	= Integer.parseInt(getParameter("SIZE"));
		speed   = Integer.parseInt(getParameter("SPEED"));
		flySet  = new FlySet(this, size, goodies, baddies, speed, TB.isSelected(),CBP.isSelected(),CBS.isSelected());

		getContentPane().setFont(new Font("Helvetica", Font.PLAIN, 18));
		getContentPane().add(BorderLayout.CENTER,flySet);
		JPanel JP=new JPanel();
		JP.add(TB);TB.setActionCommand(FlySet.ANTIALIAS);TB.addActionListener(flySet);
		JP.add(CBP);CBP.setActionCommand(FlySet.PAUSED);CBP.addActionListener(flySet);
		JP.add(CBS);CBS.setActionCommand(FlySet.SILENT);CBS.addActionListener(flySet);
		JP.add(new JLabel(" Size:"));
		JP.add(JS);JS.addChangeListener(flySet);
		getContentPane().add(BorderLayout.NORTH, JP);
	}

	public void start() { // Start and stop the applet, may rehappen
		(mainThread = new Thread(flySet)).start();
	}

	public void stop() {
		mainThread.interrupt();
		try{
			mainThread.join();
		}catch(InterruptedException e){ // will never happen
		}
	}

}

class FlySet extends Canvas implements Runnable, ActionListener, ChangeListener{
	static final String ANTIALIAS="A",PAUSED="P",SILENT="ST",SIZE="SE";
	Random R=new Random();
	long honkClear,honkSet=0;
	int	num,goodies;
	int	i,j,k;
	Fly[]	fly;
	int     size,width,height,speed;
	int     honkLast=-1;
	AudioClip helloClip,booClip,ouchClip;
	Image im;
	Graphics2D offscreen;
	boolean antialias, paused, silent;
	boolean firstBound=true;


//	public Dimension minimumSize(){System.out.println("here");return new Dimension(70,70);
//	}

	FlySet(JApplet parent, int size, int goodies, int baddies, int speed, boolean antialias, boolean pause, boolean silent){
		this.size=size;
		num=goodies+baddies;
		this.goodies=goodies;
		this.speed=speed;
		this.antialias=antialias;this.paused=paused;this.silent=silent;
		helloClip=parent.getAudioClip(getClass().getResource("hello.au"));
		booClip=parent.getAudioClip(getClass().getResource("boo.au"));
		ouchClip=parent.getAudioClip(getClass().getResource("ouch.au"));
		fly=new Fly[num];
		for(int i=0;i<num;i++) fly[i]=new Fly(size,speed,i<goodies);
	}

	public void run() {
		// Ooo!  Multithreaded!
		while (true) {
			long lap=System.currentTimeMillis()+25;//frame plus nap
			// a variable sleep time prevents hyperspeed in fast processors.
			// but doesn't prevent crawling in slow ones!
			//crawling is prevented by calc-ing outside of screenthread, so some
			// repaints may be skipped in slow machines ...

			if (!paused) {
				// Do force calculation
				calcNext();
				// update and draw new ones.
				moveToNext();
				repaint();//this is just a signal!
			}

			try {
				long sleep=lap-System.currentTimeMillis();
				Thread.sleep(sleep>0?sleep:0);
			}catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
			if(Thread.currentThread().interrupted()) break;

		}
	}
	
	public void actionPerformed(ActionEvent e){
		if(e.getActionCommand()==ANTIALIAS){antialias=!antialias;reAlias();
		}else if(e.getActionCommand()==PAUSED){paused=!paused;
		}else if(e.getActionCommand()==SILENT){silent=!silent;
		}
	}
	public void stateChanged(ChangeEvent e){
		size=((JSlider)e.getSource()).getValue();repaint();//ugly jslider
	}


	public void draw(Graphics2D g){
		for (int i=0; i < num; i++){
			fly[i].draw(g,size);
		}
		if(honkLast!=-1) fly[honkLast].draw(g,size*1.2f);
	}

	public void calcNext(){
		int     i;
		for(i=0;i<num;i++){
			fly[i].calcNext(width,height,size);
			if(fly[i].bumping&&!silent) ouchClip.play();
			fly[i].bumping=false;
		}
		if(honkLast==-1 && System.currentTimeMillis()>honkSet){
			honkSet=System.currentTimeMillis()+2000+R.nextInt(500);
			honkLast=R.nextInt(num);
			fly[honkLast].honking=true;
			for(i=0;i<num;i++){
			        if(i!=honkLast&&(honkLast<goodies||i<goodies))
						fly[honkLast].honk(fly[i],honkLast>=goodies&&i<goodies,speed);
			}	
			if(!silent){
				if(honkLast<goodies) helloClip.play();
				else booClip.play();
			}
			honkClear=System.currentTimeMillis()+300;
		}
		if(honkLast!=-1 && System.currentTimeMillis()>honkClear){
			fly[honkLast].honking=false;
			honkLast=-1;
		}
		
	}

	public void moveToNext(){
		int     i;
		for(i=0;i<num;i++){
			fly[i].moveToNext();
		}
	}

    public void setBounds(int x, int y, int width, int height){
    	this.width=width;this.height=height;
    	im = createImage(width, height);
    	offscreen = (Graphics2D)im.getGraphics();
		reAlias();
		if(firstBound){
			for(int i=0;i<num;i++){
				fly[i].x=(size+(float)Math.random()*(width-size*2));
				fly[i].y=(size+(float)Math.random()*(height-size*2));
				fly[i].sx=width/2;
				fly[i].sy=height/2;
			}
			firstBound=false;
		}
    	super.setBounds(x,y,width,height);
    }
	public void reAlias(){
		offscreen.setRenderingHint(RenderingHints.KEY_ANTIALIASING
		,antialias?RenderingHints.VALUE_ANTIALIAS_ON:RenderingHints.VALUE_ANTIALIAS_OFF);
		repaint();
	}
	
	
	public void update(Graphics g){
		offscreen.clearRect(0,0,im.getWidth(null),im.getHeight(null));
		draw(offscreen);
		paint(g); //note, no blanking of background
	}

    public void paint(Graphics g) {
		g.drawImage(im, 0, 0, null);
    }

}


class Fly {

    float     x,y,sx,sy,nx,ny;
    int     speed;
    float	ang,nang;
	boolean honking, goodie, bumping;

    Fly(int size, int speed, boolean goodie){
		ang=(float)Math.random()*360-180;
		this.speed=speed;
		this.goodie=goodie;
	}

	public void draw(Graphics2D g, float size){
		g.setColor(honking?Color.yellow:goodie?Color.blue:Color.red);
		g.fill(new RoundRectangle2D.Float(x-size, y-size, size*2, size*2,size*2,size*2));
		antena(g,size,-30);
		antena(g,size,30);
	}

	private void antena(Graphics2D g, float size, float direction){
		g.draw(new Line2D.Float(x,y,
			(float)(x+2*size*Math.cos((ang+direction)/360*2*Math.PI)),
			(float)(y+2*size*Math.sin((ang+direction)/360*2*Math.PI))
			));
	}

	public void calcNext(int width, int height, int size){
		float   bang,tang;
		float     dx,dy;

		nx=x+(float)(Math.cos(ang/360*2*Math.PI)*speed);
		ny=y+(float)(Math.sin(ang/360f*2f*Math.PI)*speed);
		dx=sx-nx;
		dy=sy-ny;

		if(dx==0) tang=(dy>=0)?90:-90 ;
		else{
			tang=(float)(Math.atan((float)dy/(float)dx)/Math.PI*180);
			if(dx<0) tang+=180;
		}
		tang=norm(norm(tang)-nang)/20f*(.8f+.4f*(float)Math.random());
		ang=ang+tang;

		nang=ang;
		if(nx+size>width||nx<size) nang=norm(180-nang);
		if(ny+size>height||ny<size) nang=-nang;
		if(nx+size>width) nx=width-size;
		if(nx<size) nx=size;
		if(ny+size>height) ny=height-size;
		if(ny<size) ny=size;
		if(nang!=ang) bumping=true; //if bouncing change angle
		ang=nang;

	}

	private float norm(float ang){
		if (ang>180) return ang-360;
		if (ang<-180) return ang+360;
		return ang;
	}

	public void honk(Fly victim, boolean escape, int speed){
		float     dx,dy;
		float   dist;
		if(escape){
			dx=victim.x-x;
			dy=victim.y-y;
			dist=(float)Math.sqrt(dx*dx+dy*dy);
			victim.sx=(dx/dist)*1000; //kludgeee
			victim.sy=(dy/dist)*1000;
			victim.speed=speed*2;
		}else{
			victim.sx=x;
			victim.sy=y;
			victim.speed=speed;
		}
	}

	public void moveToNext(){
		x=nx;
		y=ny;
		ang=nang;
	}

}

