import greenfoot.*;  // (World, Actor, GreenfootImage, and Greenfoot)

/**
 * Actors that inherit this class have methods available to them to rotate around any
 * given point in the world. They also maintain double precision for their co-ordinates.
 * 
 * <br><br>
 * 
 * Double precision code taken from Poul's SmoothMover class.
 * 
 * @author Michael Berry (mjrb4)
 * @author Poul Henriksen
 * @version 17/02/09
 */
public abstract class Rotator extends Actor
{
    
    private double x = 0;
    private double y = 0;
    
    /**
     * Get exactly where we are in the world
     * @return the x value of our position
     */
    public double getExactX()
    {
        return x;
    }
    
    /**
     * Get exactly where we are in the world
     * @return the y value of our position
     */
    public double getExactY()
    {
        return y;
    }
    
    /**
     * Providing 'setLocation' based on doubles. This will eventually call
     * the built-in 'setLocation' method in Actor.
     */
    public void setLocation(double x, double y) 
    {
        this.x = x;
        this.y = y;
        super.setLocation((int) x, (int) y);
    }
    
    /**
     * Override the default 'setLocation' method to keep our own coordinates
     * up-to-date.
     */
    public void setLocation(int x, int y) 
    {
        setLocation((double) x, (double) y);
    }
    
    /**
     * Get the rotation around a specific point in the world.
     * @param x the x co-ordinate of the point
     * @param y the y co-ordinate of the point
     * @return the rotation in degrees around the point
     * (0 degrees is to the left of the point)
     */
    public double getRotation(double x, double y)
    {
        double xDistance = getExactX()-x;
        double yDistance = getExactY()-y;
        double angle = Math.toDegrees(Math.atan2(yDistance, xDistance))+180;
        
        return angle;
    }

    /**
     * Set the rotation of this object to a value around 
     * a specific point.
     * @param degrees the degrees to set the rotation to
     * (0 degrees is to the left of the point)
     * @param x the x co-ordinate of the point
     * @param y the y co-ordinate of the point
     */
    public void setRotation(double degrees, double x, double y)
    {
        //Convert degrees to between 180 and -180
        degrees = ((degrees+180) % 360)-180;
        
        /*
         * We need to deal with 180 and -180 especially, but this is
         * pretty simple - just put the actor the other side of the 
         * point of rotation.
         * 
         * HACK: Until it deals with 180 properly, it'll just call the
         * method with 179.9. This works but isn't very nice, so I'll
         * update this at some point to work properly...
         */
        if(Math.abs(degrees)==180) {
            setRotation(179.9, x, y);
            return;
        }
        
        /*
         * Otherwise, we've got a bit more maths on our hands...
         * Fair bit of trig going on here, including the sine rule.
         */
        else {
            /*
             * Let's get the distance between this point and the
             * point we're rotating around.
             */
            double xDistance = getExactX()-x;
            double yDistance = getExactY()-y;
            double distance = Math.sqrt((xDistance*xDistance + yDistance*yDistance));
            
            /*
             * Bit of a hack, but first set the location to 0 
             * degrees around the point.
             * Then we can just work from there.
             */
            setLocation(x-distance, y);
    
            /*
             * Next is a bit trickier. Imagine a triangle formed 
             * from a line that starts at the point we're rotating
             * around at 0 degrees and another line at *degrees*
             * degrees. Both those lines are length *distance*, 
             * and the third line joins those two. The triangle is
             * therefore isocoles.
             * 
             * One angle is degrees, the other two (identical)
             * angles are theta.
             * 
             * c is the length of the side that isn't necessarily
             * the same length of distance. (Sine rule is needed
             * to calculate this.
             * 
             * Finally, imagine a second triangle formed by a 
             * vertical line drawn from the point in the triangle
             * furthest away in the y direction from the point of
             * rotation, to intersect with the side of the 
             * triangle drawn at 0 degrees to the point of rotation.
             * This line then forms a triangle by splitting the
             * triangle in two - the triangle we're interested
             * in is the one that doesn't necessarily have an 
             * angle the size of *degrees* in it. This triangle
             * is a right angled triangle, so we can use basic 
             * trig to find what we need (the offsets of x and y.)
             * 
             * The line that we initially drew to form this 
             * second triangle is the magnitude of the y offset
             * needed, and the other side (not the hypotenuse)
             * is the x offset.
             * 
             * So phew, we're done - simply add the x offset to the 
             * x position and likewise with the y offset, and we 
             * have our circle rotation!
             */
            double theta = (180-degrees)/2;
            double c = (distance*sin(degrees))/(sin(theta)); //Sine rule
            double dx = c*cos(theta);
            double dy = c*sin(theta);
            
            setLocation(getExactX()+dx, getExactY()-dy);
        }
    }
    
    /**
     * Get the sine of a number in degrees.
     * @param a the number to manipulate
     * @return the sine of a in degrees
     */
    private double sin(double a)
    {
        return Math.sin(Math.toRadians(a));
    }
    
    /**
     * Get the cosine of a number in degrees.
     * @param a the number to manipulate
     * @return the cosine of a in degrees
     */
    private double cos(double a)
    {
        return Math.cos(Math.toRadians(a));
    }
    
    /**
     * Get the tan of a number in degrees.
     * @param a the number to manipulate
     * @return the tan of a in degrees
     */
    private double tan(double a)
    {
        return Math.tan(Math.toRadians(a));
    }
}

