package gizmoball.game;

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

/**
 * The flipper gizmo.
 *
 * @author <a href="mailto:amdragon@mit.edu">Austin Clements</a>
 * @version $Id: FlipperGizmo.java,v 1.5 2004/04/27 20:51:36 amthrax Exp $
 */
public class FlipperGizmo extends AbstractGizmo {
    private static double RADIUS = 0.25;
    private static double ROTATION_RATE = Math.toRadians(1080);
    private static double ANGLE = Math.toRadians(90);
    private static double LENGTH = 2;

    private Angle orientation;
    private boolean handedness;
    private Angle curOrientation;
    private double remainingRotation;
    private boolean direction;

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

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

    /**
     * Get the value of handedness
     *
     * @return the value of handedness
     */
    public boolean getHandedness() {
        return handedness;
    }

    /**
     * Set the value of handedness.  false makes this a "left
     * flipper".  true makes this a "right flipper".
     *
     * @param newHandedness Value to assign handedness
     */
    public void setHandedness(boolean newHandedness) {
        System.out.println("Set handedness " + newHandedness);
        
        this.handedness = newHandedness;
        this.direction = !newHandedness;
        this.remainingRotation = 0;

        firePropertyObservers("Handedness", new Boolean(newHandedness));
    }

    /**
     * Get the orientation of this gizmo
     *
     * @return the orientation of this gizmo
     */
    public Angle getOrientation() {
        return orientation;
    }

    /**
     * Set the orientation of this gizmo.  This also updates the gizmo
     * geometry to reflect the new orientation.
     *
     * @param newOrientation the new orientation of this gizmo
     */
    public void setOrientation(Angle newOrientation) {
        orientation = newOrientation;
        curOrientation = orientation;

        firePropertyObservers("Orientation", newOrientation);
    }

    /**
     * Set up the properties of the flipper gizmo
     *
     * @modifies properties
     * @effects adds the Orientation and Handedness properties
     */
    protected void setUpProperties() {
        super.setUpProperties();
        addProperty("Orientation");
        addProperty("Handedness");
    }

    /**
     * Get the geometric circle representing the pivot endpoint of
     * this flipper.  This is the endpoint that does not move under
     * rotation.
     *
     * @return a Circle representing one endpoint
     */
    public Circle getEndpoint1() {
        Circle base;
        if (handedness) {
            // Right flipper
            base = new Circle(getPosition().plus(new Vect(RADIUS, RADIUS)),
                              RADIUS);
        } else {
            // Left flipper
            base = new Circle(getPosition().plus(new Vect(LENGTH-RADIUS,
                                                          RADIUS)),
                              RADIUS);
        }
        // Account for orientation
        return Geometry.rotateAround(base, new Vect(1, 1), getOrientation());
    }

    /**
     * Get the geometric circle representing the pivoting endpoint of
     * this flipper.  This is the endpoint that does move under
     * rotation.
     *
     * @return a Circle representing one endpoint
     */
    public Circle getEndpoint2() {
        Vect extrusion =
            Vect.Y_HAT.times(LENGTH-2*RADIUS).rotateBy(curOrientation);
        return new Circle(getEndpoint1().getCenter().plus(extrusion), RADIUS);
    }

    /**
     * Rotate this flipper if it is currently flippering.
     */
    public boolean tick(double time) {
        double delta = ROTATION_RATE*time;
        if (delta > remainingRotation) {
            delta = remainingRotation;
        }
        remainingRotation -= delta;
        
        if (direction) {
            // Rotating CCW
            curOrientation =
                curOrientation.plus(new Angle(-delta));
        } else {
            // Rotating CW
            curOrientation =
                curOrientation.plus(new Angle(delta));
        }

//         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 flipper's collidability with other gizmos.  This
     * can collide with balls and never with other gizmos.
     */
    public Collidability canCollideWith(AbstractGizmo other) {
        if (other instanceof BallGizmo) {
            // FIXME Add ball-flipper collisions
            return Collidability.NEVER_COLLIDE;
        } else {
            return Collidability.NEVER_COLLIDE;
        }
    }

    /**
     * Determine the time until a collision will occur with a ball.
     * This is estimated using the current velocity of the ball, and,
     * if applicable, the current rotation rate of the flipper.
     */
    public double timeUntilCollisionWith(AbstractGizmo other) {
        // FIXME Add ball-flipper collisions
        return Double.POSITIVE_INFINITY;
    }

    /**
     * Deal with collisions with a ball.
     */
    public void collide(AbstractGizmo other) {
        // FIXME Add ball-flipper collisions
        return;
    }

    /**
     * Fire the flippers's trigger.  This changes the direction of
     * flippering.
     */
    public void fireTrigger(AbstractTrigger trigger) {
        direction = !direction;
        remainingRotation = ANGLE - remainingRotation;
    }
}

