package gizmoball.game;

import org.apache.log4j.Logger;
import physics.*;

/**
 * The ball gizmo.  This represents a circular gizmo that moves around
 * the board in a ball-like manner.  As it moves, it is continually
 * affected by gravity and friction.  It can also bounce off bumper
 * gizmos in a manner that reflects their geometry.
 *
 * <p>Note that gravity and friction wreak havoc with collision
 * management due to the fact that the velocity is constantly
 * changing; however, as long as the tick length is sufficiently
 * short, the errors due to this should be minimized.
 *
 * <p>There are two things that are currently unimplemented (and are
 * not needed for the preliminary release):
 * <ul><li>A way to change the gravity and friction constants
 * <li>Collisions between two balls
 * </ul>
 *
 * @author <a href="mailto:amdragon@mit.edu">Austin Clements</a>
 * @version $Id: BallGizmo.java,v 1.7 2004/04/27 21:12:52 amthrax Exp $
 */
public class BallGizmo extends AbstractGizmo {
    // FIXME Make gravity and friction changeable
    private static Vect GRAVITY = new Vect(0, 25);
    private static double MU1 = 0.025;
    private static double MU2 = 0.025;

    private static double RADIUS = 0.25;

    private Vect velocity;

    private static Logger logger = Logger.getLogger(BallGizmo.class);

    /**
     * Creates a new <code>BallGizmo</code> instance.
     */
    public BallGizmo() {
        super("Ball", Vect.ZERO);
    }

    /**
     * Get the value of velocity
     *
     * @return the value of velocity
     */
    public Vect getVelocity() {
        return velocity;
    }

    /**
     * Set the value of velocity
     *
     * @param newVelocity Value to assign velocity
     */
    public void setVelocity(Vect newVelocity) {
        this.velocity = newVelocity;

        firePropertyObservers("Velocity", newVelocity);
    }

    /**
     * Set up the properties of the ball gizmo
     *
     * @modifies properties
     * @effects adds the Velocity property
     */
    protected void setUpProperties() {
        super.setUpProperties();
        addProperty("Velocity");
    }

    /**
     * Get the geometric circle representing the shape and position of
     * this ball.
     *
     * @return a Circle with the size and position of this ball
     */
    public Circle getCircle() {
        return new Circle(getPosition(), RADIUS);
    }

    /**
     * Move this ball at its current velocity, accounting for gravity
     * and friction.
     *
     * <p>The precise operation performed to tick the ball is: update
     * position, deal with gravity, deal with friction.  Different
     * orderings would produce similar, but slightly different
     * results.
     *
     * <p>Friction is computed using the formula:
     * <i>V<sub>new</sub></i> = <i>V<sub>old</sub></i> *
     * (1 - <i>mu</i> * <i>delta_t</i> -
     * <i>mu</i><sub>2</sub> * |<i>V<sub>old</sub></i>| * <i>delta_t</i>)
     */
    public boolean tick(double time) {
        Vect position = getPosition();
        Vect velocity = getVelocity();

        // Update position
        position = position.plus(velocity.times(time));

        // Update velocity for gravity
        velocity = velocity.plus(GRAVITY.times(time));

        // Update velocity for friction
        velocity = velocity.times(1-MU1*time-MU2*velocity.length()*time);

        setPosition(position);
        setVelocity(velocity);

        if (logger.isDebugEnabled()) {
            logger.debug("Tick: new p=<"+(int)position.x()+","+
                         (int)position.y()+"> new v=<"+(int)velocity.x()+
                         ","+(int)velocity.y()+">");
        }

        return true;
    }

    /**
     * Determine the ball's collidability with other gizmos.  This
     * defers collision handling for all gizmos except other balls.
     * This is done in order to simplify the process of adding new
     * gizmos by making it unnecessary to modify this class.
     */
    public Collidability canCollideWith(AbstractGizmo other) {
        if (other instanceof BallGizmo) {
            return Collidability.CAN_COLLIDE;
        } else {
            return Collidability.DEFER_COLLIDE;
        }
    }

    /**
     * Determine the time until a collision will occur with another
     * ball.  This is estimated using the current velocities of the
     * balls, thus not accounting for the effects of gravity and
     * friction.
     */
    public double timeUntilCollisionWith(AbstractGizmo other) {
        BallGizmo ball = (BallGizmo)other;
        Circle ballCircle = ball.getCircle();
        Vect ballVelocty = ball.getVelocity();

        return Geometry.timeUntilBallBallCollision(getCircle(),
                                                   getVelocity(),
                                                   ballCircle,
                                                   ballVelocty);
    }

    /**
     * Deal with collisions with another ball.
     */
    public void collide(AbstractGizmo other) {
        BallGizmo ball = (BallGizmo)other;
        Circle ballCircle = ball.getCircle();
        Vect ballVelocity = ball.getVelocity();

        Geometry.VectPair vels = Geometry.reflectBalls(getCircle().getCenter(),
                                                       1.0,
                                                       getVelocity(),
                                                       ballCircle.getCenter(),
                                                       1.0,
                                                       ballVelocity);
        setVelocity(vels.v1);
        ball.setVelocity(vels.v2);
    }

    /**
     * Fire the ball's trigger.  Because there is no trigger action
     * defined for a ball, this does nothing.
     */
    public void fireTrigger(AbstractTrigger trigger) {
        return;
    }
}

