// package PongishGame;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;

import edu.neu.ccs.util.JPTUtilities;
import edu.neu.ccs.gui.*;

public class Mover implements Runnable{
	private volatile Thread me;            //this thread (stored so it can safely terminate later)
	public final int max = 1000;           //max number of balls allowed on canvas
	Ball[] ball = new Ball[max];           //array of ball objects on the canvas
	ArrayList obstacles = new ArrayList(); //arraylist of items the ball can safely collide with
	double playerPos;                      //position of the player in degrees
	int paddleWidth = 30;                  //length of the paddle in degrees
	boolean dragging = false;              //is a ball being dragged
	int retCount = 0;      //count the number of times a ball was returned.
	int curBall = 0;       //current ball being moved(if applicable)
	int numBalls = 0;      //total number of balls on canvas
	int width, height;     //width and height of canvas
	int rBound;            //radius of the bounding circle
	int mx, my;            //current mouse x and y position
	double grav = 0.0;     //value of gravity
	boolean eMouse = true; //enable use of mouse if true
	boolean hitOut;        //was a ball missed
	BufferedPanel b;       //buffered panel to paint on
	
	//create the graphical components
    Arc2D.Double arc, arc2;
    ShapePaintable c, bound;
    TextPaintable loseText, insText, countText, counter;
    
    @SuppressWarnings("serial") MouseAction mousePressedAction =
        new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                mousePressed(evt);
            }
    };
        
    @SuppressWarnings("serial") MouseAction mouseDraggedAction =
        new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                mouseDragged(evt);
            }
    };
    
    @SuppressWarnings("serial") MouseAction mouseReleasedAction = 
    	new MouseAction(){
    		public void mouseActionPerformed(MouseEvent evt) {
    			mouseReleased(evt);
    		}
    };
    
    @SuppressWarnings("serial") MouseAction mouseMovedAction = 
    	new MouseAction(){
    		public void mouseActionPerformed(MouseEvent evt){
    			mouseMoved(evt);
    		}
    };
    
    @SuppressWarnings("serial") MouseAction mouseClickedAction = 
    	new MouseAction(){
    		public void mouseActionPerformed(MouseEvent evt){
    			mouseClicked(evt);
    		}
    };
    
	public Mover(int w, int h, BufferedPanel b){
		width = w;
		height = h;
		this.b = b;
	}
	
	public void newGame(){
		//initialize variables
		rBound = 400;
		hitOut = false;
		clearAllBalls();
		
		//add balls to canvas
		Random r = new Random();
		double nx, ny;
		double vx, vy, nr, nt;
		
		int increment = 36;
		for(int i=0; i<360; i+=increment){
	        obstacles.add(new Arc2D.Double(0,0,width,height,i,increment,Arc2D.OPEN));
		}

		for(int i=0; i<5; i++){
			//compute a random location and velocity for the ball
			nr = r.nextDouble() * rBound * .4;
			nt = r.nextDouble() * 360;
			nx = nr * Math.cos(nt);
			ny = nr * Math.sin(nt);
			
			vx = r.nextDouble()*4 - 2;
			vy = r.nextDouble()*4 - 2;
			
			this.addBall(new Ball(width/2 + nx, height/2 + ny, vx, vy, 50, 10, true, this));
		}

		start();
	}
	
	//paint all balls on the canvas
	public void paint(Graphics g) {
		for (int i=0; i<numBalls; i++) 
			ball[i].draw(g);
	}

	//call this method to add a ball to the canvas
	public void addBall(Ball b) {
		if (numBalls < max) ball[numBalls++] = b;
	}
	
	public void clearAllBalls(){
		int x = 0;
		while(x < numBalls)	ball[x++] = null;
		numBalls = 0;
	}
	
	public void run() {
		//store the current thread, so that it can be stopped safely
		Thread currentThread = Thread.currentThread();

		//add listeners for mouse actions
		MouseActionAdapter adapter = b.getMouseActionAdapter();
		adapter.addMousePressedAction(mousePressedAction);
        adapter.addMouseDraggedAction(mouseDraggedAction);
        adapter.addMouseReleasedAction(mouseReleasedAction);
        adapter.addMouseMovedAction(mouseMovedAction);
        adapter.addMouseClickedAction(mouseClickedAction);
		
        //initialize graphical elements
        arc = new Arc2D.Double(0,0,width,height,-20,20,Arc2D.OPEN);
        arc2 = new Arc2D.Double(0,0,width,height,0,360,Arc2D.OPEN);
    	
        c = new ShapePaintable(arc, PaintMode.DRAW, Color.black);
		bound = new ShapePaintable(arc2, PaintMode.FILL, Color.lightGray);
		
    	loseText = new TextPaintable("You Lost!", new Font("Arial", Font.PLAIN, 60));
        loseText.setAnchorLocator(TextAnchor.CENTER_BASELINE);
		loseText.setAnchorPosition(width/2, height/2);
		
		insText = new TextPaintable("Click for a new game", new Font("Arial", Font.PLAIN, 30));
		insText.setAnchorLocator(TextAnchor.CENTER_BASELINE);
		insText.setAnchorPosition(width/2, height/2 + 80);
		
		countText = new TextPaintable("" + retCount, new Font("Arial", Font.PLAIN, 40));
		countText.setAnchorLocator(TextAnchor.LEFT_BASELINE);
		countText.setAnchorPosition(0, 40);
		
		//get the buffered graphics context to paint on
		Graphics2D bg = b.getBufferGraphics();
		
		while (me == currentThread) {
			//bound each ball to the limits of this canvas
			for (int i=0; i<numBalls; i++)
				if(ball[i].update()) hitOut = true;
				
			//make all balls interact with each other
			for (int i=0; i<numBalls-1; i++)
				for (int j=i+1; j<numBalls; j++)
					ball[i].interact(ball[j]);
			
			//if mouse is dragging, adjust the current ball's
			//   velocity in direction of mouse
			if (dragging) {
				ball[curBall].vx = (mx - ball[curBall].x) / 20;
				ball[curBall].vy = (my - ball[curBall].y) / 20;
			}
			
			countText.setString("" + retCount);
			
			//repaint the canvas and pause before looping again
			b.clearPanel();
			bound.paint(bg);
			c.paint(bg);
			countText.paint(bg);
			paint(bg);
			
			//what happens if a ball went out of bounds
			if(hitOut){
				loseText.paint(bg);
				insText.paint(bg);
				me = null;
			}
			JPTUtilities.pauseThread(20);
			b.repaint();
		}
	}
	
	//start new thread and store it in variable 'me' 
	public void start(){
		this.me = new Thread(this);
		this.me.start();
	}
	
	//set thread 'me' to null so this thread will halt safely
	public void stop(){
		this.me = null;
	}
	
	//  gives the index of the ball closest to mouse position (x, y)
	int nearestBall(int x, int y, int ex) {
		double d=999999999999.0, t; 
		int j=0;
		for (int i=0; i<numBalls; i++) {
			t = Ball.hypot(x - ball[i].x, y - ball[i].y);
			if (t < d) {
				d = t;
				j = i;
			}
		}
		return j;
	}

	public void movePlayer(double loc){
		loc += 90;
		arc = new Arc2D.Double(0,0,width,height,loc-paddleWidth/2,paddleWidth,Arc2D.OPEN);
		c = new ShapePaintable(arc, PaintMode.DRAW, Color.black);
	}
	
	//called when canvas needs to be resized to given w and h
	public void resized(Dimension d){
		// set size
		this.width = d.width;
		this.height = d.height;
	}
	
	// perform mouse actions
	public void mouseClicked(MouseEvent e){
		if(this.hitOut){
			retCount = 0;
			newGame();
		}
	}
	
	public void mouseReleased(MouseEvent e) {
		this.dragging = false;
	}

	public void mousePressed(MouseEvent e){
        mx = e.getX();
		my = e.getY();
    
		curBall = nearestBall(mx, my, numBalls);
		if(eMouse) dragging = true;
	}

	public void mouseMoved(MouseEvent e){
		double tx = e.getX() - width / 2;
		double ty = e.getY() - height/2;
		double playerRad = Math.atan2(tx,ty) + Math.PI;
		this.playerPos = Math.toDegrees(playerRad);
		movePlayer(playerPos);

	}
	
	public void mouseDragged(MouseEvent e){
		mx = e.getX();
		my = e.getY();
		mouseMoved(e);
	}
}
