/**********************************
 * Selim Mimaroglu
 *
 * CS680 Object-Oriented Programming
 * Marble Applet Game
 *
 * Spring 2002
 *
 *
 ************************************/

import javax.swing.*;
import java.awt.*;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.net.URL;

/**
 * Implementation of marbles are done here
 *
 *
 * @author Selim Mimaroglu
 * @version 1.0
 */
public class Ball {

     
    /**
     * radius of each marble
     */
    protected static final int RADIUS = 5;

    /**
     * terminal velocity of a marble
     */
    protected static final int TERMINAL_VELOCITY = 10 * RADIUS;

    /**
     * friction applied
     */
    protected static final double FRICTION = 0.20;

    // some constants for location, x and y are center of gravity

    /**
     * initial x position of a marble
     */
    protected int x = 0;

    /**
     * initial y position of a marble
     */
    protected int y = 0;

    /**
     * initial velocity of a marble
     */
    protected double velocity = 0.0;

    /**
     * initial direction of a marble
     */
    private double direction = 0.0;

    //folowing will be limit of the play area
    //we have a square and inside square there is a circle
    private int xlow, ylow, xhigh, yhigh;

    // keep track of number of movements
    private int moveCounter;

    // it holds the name of gif image file
    private String imageName;

    // this is the Icon image
    private ImageIcon marble;

    // is this ball active? (or is it in the circle)
    private boolean active = false;
    



    /**
     * Construct a new ball with specified image and boundaries
     *
     *
     * @param str file name of the image
     * @param xlow the upper left corner of the ball's boundary area
     * @param ylow the upper left corner of the ball's boundary area
     * @param widht the widht of the ball's boundary area
     * @param height the height of the ball's boundary area
     * @param codebase the codebase of this JApplet
     */

    public Ball (String str, int xlow, int ylow, int width, int height, URL codebase) {
	this.imageName = str;
	this.xlow = xlow;
	this.ylow = ylow;
	this.xhigh = xlow + width;
	this.yhigh = ylow + height;
	moveCounter = 0;
	// create marble ImageIcon
	//marble = new ImageIcon(str);
	URL url = null;

	try {
		url = new URL(codebase, str);
	}
	catch(java.net.MalformedURLException e) {
		System.err.println("couldn't create image: " + "badly specified URL");
	}
	marble = new ImageIcon(url, "a marble");

    }

    /**
     * Activate or deactivate a player marble by hand
     *
     * @param bool true for activation
     */
    protected void setActive(boolean bool) {
	this.active = bool;
    }


    /**
     * Activate or deactivate a player marble by boundary check
     * Boundary is the circle
     *
     * @param center center of the circle
     * @param radius radius of the circle
     */
    protected void setActive(Point center, int radius) {
	Point p = this.getVertex();
	if ( MotionUtils.distance( center.x, center.y, p.x, p.y ) > radius ) {
	    this.active = false;
	}
	else {
	    this.active = true;
	}
    }


    /**
     * Is this ball active (is it in the circle)
     * @return true if the marble is in the circle
     **/
    protected boolean isActive() {
	return this.active;
    }



    /**
     * Get the radius of the ball
     * @return radius of the marble
     */
    final public static int getRadius() {
	return RADIUS;
    }


    /**
     * Get the friction of the ball
     * @return friction applied to a marble
     */
    public static double getFriction() {
	return FRICTION;
    }


    /**
     * Return the location of the ball
     *
     * @return location of the marble as a Point object
     */
    final public Point getVertex() {
	return new Point(x,y);
    }


    /**
     * Get the file name of the image
     *
     * @return image name if the marble "redball.gif" for example
     */
    final public String getImageName() {
	return this.imageName;
    }



    /**
     * Set the location of this ball
     *
     * It can't be outside of the viewing area, make sure!
     *
     * @param x the x coordinate of the vertex
     * @param y the y coordinate of the vertex
     *
     */
    final public void setVertex(int x, int y) {

	if( x < xlow + RADIUS) {
	    x = xlow + RADIUS;
	}
	else if ( x >= xhigh - RADIUS) {
	    x = xhigh - RADIUS - 1;
	}

	if ( y < ylow + RADIUS ) {
	    y = ylow + RADIUS;
	}

	else if ( y >= yhigh - RADIUS) {
	    y = yhigh - RADIUS - 1;
	}

	this.x = x;
	this.y = y;

	// lower the velocity
	if (this.velocity > 0 ) {
	    velocity -= FRICTION;
	}
    }


	/**
	 * Set the vertex of the given ball after rounding x and y
	 *
	 *
	 * @param x the new x-coordinate
	 * @param y the new y-coordinate
	 *
   	 */
    final public void setVertex(double x, double y) {
	this.setVertex( ((int) Math.round(x)), ((int) Math.round(y)));
    }


    /**
     * Get this ball's direction of the travel (in radians)
     *
     *
     * @return the direction in range (0, 2pi), or -1 if the ball isn't moving
     * @see MotionUtils
     *
     */
    final public double getDirection() {
	if( !this.isMoving() ) {
	    return -1;
	}
	return direction;
    }



    /**
     * Get the ball's current velocity (the number of pixels between redraws)
     *
     * @return the velocity of the ball
     *
     */
    final public double getVelocity() {
	return this.velocity;

    }



    /**
     * is this ball moving ?
     *
     * @return true if ball is moving
     */
    final public boolean isMoving() {
	if ( this.velocity > 0 ) {
	    return true;
	}
	return false;
    }


    /**
     * return true if the given ball is touching this ball
     *
     * two balls are touching if the distance between their vertices is
     * less than the sum of their radii
     *
     * @param other the other ball
     *
     *
     */
    final public boolean isTouching(Ball other) {
	Point ourVertex = this.getVertex();
	Point theirVertex = other.getVertex();
	double distance = MotionUtils.distance(ourVertex, theirVertex);

	if( distance <= ( Ball.getRadius() * 2)) {
	    return true;
	}
	return false;
    }


    /**
     * Is this ball touching to given point from this distance
     *
     * @param p the point to test
     * @param distance the distance around the point
     *
     * @return true if the ball is within distance of the given point
     */
    final public boolean isTouching( Point p, double distance) {
	Point ourVertex= this.getVertex();
	double betweenPoints = MotionUtils.distance(ourVertex, p);

	if( betweenPoints <= Ball.getRadius() + distance ) {
	    return true;
	}
	return false;
    }



    /**
     * Is this ball touching the given point 
     *
     * @param p the point to test
     * @return true if a ball's touching the given point
     */
    final public boolean isTouching(Point p) {
	return isTouching( p, 0.0);
    }


    /**
     * Give information about this ball
     * @return A string describing the balls image, position, velocity and direction
     *
     */
    public String toString() {
	return "marble: image=" + imageName
	    + ", position=(" + x + ", " + y + " ), "
	    + "velocity=" + velocity
	    + ", direction=" + MotionUtils.radiansToDegrees(direction);
    }



    /**
     * move this ball, by having it recompute it's coordinates adjusting
     * speed and velocity as necessary; also rebounding around the balls
     * baoundary
     *
     * 
     * @return true is the ball's position is changed
     */
    public boolean move() {

	if ( !this.isMoving() ) {
	    return false;
	}

	/* first find the new coordinates */
	HiResPoint nextLoc = MotionUtils.move(velocity, direction);


	/**
	 * following is an attemp to guard against truncating near the
	 * perpendiculars
	 *
	 */
	if( nextLoc.x > -0.5 && nextLoc.x < 0.5 && nextLoc.x != 0.0) {
	    /* no integer change in x put we are not moving starigh
	     * up or down
	     */
	    moveCounter++;
	    int collatarel = (int) (1.0 / nextLoc.x);

	    if ( moveCounter % collatarel == 0) {
		nextLoc.x = (nextLoc.x > 0.0 ? nextLoc.x + 1 : nextLoc.x -1);
	    }
	}


	if ( nextLoc.y > -0.5 && nextLoc.y < 0.5 && nextLoc.y != 0 ) {
	    /* no integer change in y but we are not travelling straight
	     * left or right
	     */
	    moveCounter++;
	    int collatarel = (int) (1.0 / nextLoc.y);
	    if( moveCounter % collatarel == 0) {
		nextLoc.y = (nextLoc.y > 0.0 ? nextLoc.y + 1 : nextLoc.y - 1);
	    }
	}

	this.x += Math.round(nextLoc.x);
	this.y += Math.round(nextLoc.y);
	velocity -= FRICTION;

	// modify velocity
	if( velocity < 0.2 ) {
	    moveCounter = 0;
	    velocity = 0.0;

	}


	/* move ball, and slow down a little */
	int r = getRadius();

	// hold the ball in the boundareis
	if( x < xlow + r) {
	    velocity = 0;
	    direction = 0;
	    x = xlow + r;
	}
	else if ( x >= xhigh - r) {
	    velocity = 0;
	    direction = 0;
	    x = xhigh - r - 1;
	}

	if ( y < ylow + r ) {
	    velocity = 0;
	    direction = 0;
	    y = ylow + r;
	}
	else if( y >= yhigh - r) {
	    velocity = 0;
	    direction = 0;
	    y = yhigh - r - 1;
	}

	return true;
    }



    /**
     * apply a force to this ball (causing it to move)
     *
     * Note that balls have a "TERMINAL VELOCITY", which it will not
     * exceed, regardless of the amount of force applied.
     *
     * @param magnitude how much force apply
     * @param direction the direction in which to apply it
     *
     */
    public void applyForce(double magnitude, double direction) {
	if( !this.isMoving() ) {
	    this.direction = direction;
	    this.velocity = (magnitude > TERMINAL_VELOCITY ? TERMINAL_VELOCITY : magnitude);
	    return;
	}

	else {
	    VectorQuantity vq = new VectorQuantity();
	    vq = MotionUtils.resultant(getVelocity(), getDirection(), magnitude, direction);
	    this.velocity = (vq.magnitude > TERMINAL_VELOCITY ? TERMINAL_VELOCITY : vq.magnitude);
	    this.direction = vq.direction;
	}
	return;
    }



    /**
     * return an object that will enumaerate the Points though which the ball
     * will pass during it's next move. PPoints will be given at intervals
     * equal to the balls radius, uoa to and including the distance that will
     * be covered by move()
     *
     * the purpose of the enumerator is to provide a finer degree of detail
     * about the path taken by a ball. Namely, if the ball is traveling at a
     * very high velocity, the distance covered by move() might be great
     * enough that a ball would "pass through" something
     * directly in it's path.
     *
     * @return An object that implements the Enumeration interface
     *
     */

    public final Enumeration getPathEnumerator() {
	return (Enumeration) new BallPathEnumerator(this);
    }



    /**
     * An inner class to enumerate the Points traversed by a ball
     * between it's current position, and the position that the ball
     * will occupy the next time move() is called.
     *
     * Results of calling nextElement after moving the ball are not
     * defined, and probably not what you'd like them to be
     *
     *
     *
     */

    private class BallPathEnumerator implements Enumeration {
	private double distance;     // max distance
	private double direction;    // direction of travel
	private double increment;    //distance between points returned
	private double current;      //currents distance from base
	Point base;


	protected BallPathEnumerator( Ball b ) {
	    this.distance = b.getVelocity();
	    this.increment = Ball.getRadius();
	    this.direction = b.getDirection();
	    this.current = 0.0;
	    this.base = b.getVertex();
	}

	public boolean hasMoreElements() {
	    if( current < distance ) {
		return true;
	    }
	    return false;
	}


	public Object nextElement() {
	    if( this.current >= this.distance) {
		throw new NoSuchElementException("No more points in path");
	    }

	    this.current += this.increment;

	    if( this.current > this.distance ) {
		this.current = this.distance;
	    }
	    Point delta = MotionUtils.move(this.current, this.direction).pointValue();
	    Point nextPoint = new Point(base.x + delta.x, base.y + delta.y);
	    return (Object) nextPoint;
	}
    }



    /**
     * draw the ball on the screen
     *
     * @param g Graphics object
     **/
    public void draw(Graphics g) {
	// x, y is the center of mass
	marble.paintIcon( null, g, x - getRadius(), y - getRadius());
    }




















}


