package ballworlds.framework;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JPanel;

import ballworlds.ball.Ball;

/**
 * A WorldPanel displays its associated World and the Balls in that World.
 *
 * @author David Mutchler, Salman Azhar and others, January 2005.
 * Modified September, 2008.
 */
public class WorldPanel extends JPanel implements MouseListener, MouseMotionListener, Runnable {

	/**
	 * Color of the selected Ball.
	 */
	public static final Color SELECTED_BALL_COLOR = Color.red;
	private static final short LEFT_MOUSE_BUTTON = 1;

	private World world;
	private Ball selectedBall = null;
	private int timeToSleep = 10; // milliseconds

	/**
	 * Makes this panel respond to mouse actions
	 * and repeatedly repaint itself.
	 *
	 * Also sets the size of this panel to the given size and
	 * associates this WorldPanel with the given World,
	 *
	 * @param size size of this panel.
	 * @param color Background color for this WorldPanel.
	 * @param world the World associated with this WorldPanel.
	 */
	public WorldPanel(Dimension size, Color color, World world) {
		this.setPreferredSize(size);
		this.setBackground(color);
		this.world = world;

		this.addMouseListener(this);
		this.addMouseMotionListener(this);
		
		new Thread(this).start();
	}

	/**
	 * Draws the World and its Balls.
	 *
	 * @param g the Graphics object onto which to draw.
	 */
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);

		Graphics2D graphics = (Graphics2D) g;

		this.drawWorld(graphics);
		this.drawBalls(graphics);
	}

	/*
	 * Draws the World onto the given graphics object.
	 */
	private void drawWorld(Graphics2D graphics) {
		graphics.setColor(this.world.getColor());
		graphics.fill(this.world.getShape());
	}

	/*
	 * Draws the Balls onto the given graphics object.
	 */
	private void drawBalls(Graphics2D graphics) {
		this.world.drawBalls(graphics, this.selectedBall);
	}

	/**
	 * Sets the selected Ball to the Ball nearest the mouse point.
	 *
	 * @param event The MouseEvent that caused this method to be invoked.
	 */
	public void mousePressed(MouseEvent event) {
		this.selectedBall = this.world.nearestBall(event.getPoint());

		if (event.getButton() != WorldPanel.LEFT_MOUSE_BUTTON && this.selectedBall != null) {
			this.selectedBall.die();
		}
	}

	/**
	 * Sets the selected Ball to null (i.e., no Ball is selected).
	 *
	 * @param event The MouseEvent that caused this method to be invoked.
	 */
	public void mouseReleased(MouseEvent event) {
		if (event.getButton() == WorldPanel.LEFT_MOUSE_BUTTON) {
			this.selectedBall = null;
		}
	}

	/**
	 * Does nothing.
	 *
	 * @param event The MouseEvent that caused this method to be invoked.
	 */
	public void mouseEntered(MouseEvent event) {
		// Does nothing
	}

	/**
	 * Does nothing.
	 *
	 * @param event The MouseEvent that caused this method to be invoked.
	 */
	public void mouseExited(MouseEvent event) {
		// Does nothing
	}

	/**
	 * Toggles the nearest Ball between the paused and not-paused state.
	 *
	 * @param event The MouseEvent that caused this method to be invoked.
	 */
	public void mouseClicked(MouseEvent event) {
		Ball nearestBall = this.world.nearestBall(event.getPoint());
		if (nearestBall != null && event.getButton() == WorldPanel.LEFT_MOUSE_BUTTON) {
			nearestBall.pauseOrResume();
		}
	}

	/**
	 * For the selected Ball (if any), moves the Ball to the current mouse point.
	 *
	 * @param event The MouseEvent that caused this method to be invoked.
	 */
	public void mouseDragged(MouseEvent event) {
		if (this.selectedBall != null) {
			this.selectedBall.moveTo(event.getPoint());
		}
	}

	/**
	 * Does nothing.
	 *
	 * @param event The MouseEvent that caused this method to be invoked.
	 */
	public void mouseMoved(MouseEvent event) {
		// Does nothing
	}
	
	/**
	 * Repeatedly repaints this panel.
	 */
	public void run() {
		while (true) {
			try {
				Thread.sleep(this.timeToSleep);
				this.repaint();
			} catch (InterruptedException exception) {
				// If you can't sleep, no problem -- just continue.
			}
		}
	}
}