import java.util.*;
import java.awt.Color;
import java.awt.geom.*;

/**
 * A moving object in a game.  Sprites have positions, velocities, and
 * sizes.  The size is given as the radius of a circle that surrounds the
 * sprite.  Such a size is intended only for an approximate determination
 * if two sprites collide.
 *
 * @author Jim Glenn
 * @version 0.1 1/19/2009 from Liberation's Sprite.java of 1/8/2009
 */

public class Sprite
{
    /**
     * The x-coordinate of this sprite.
     */

    protected double x;

    /**
     * The y-coordinate of this sprite.
     */

    protected double y;

    /**
     * The x-component of this sprite's velocity.
     */

    protected double vx;

    /**
     * The y-component of this sprite's velocity.
     */

    protected double vy;

    /**
     * The size of a circle that encloses this sprite.
     */

    protected double radius;

    /**
     * The color of this sprite.
     */

    protected Color color;

    /**
     * The state of this sprite.
     */

    protected int state;

    /**
     * The normal state of a sprite.  This constant is defined to be 0.
     */

    public static final int STATE_NORMAL = 0;

    /**
     * The amount of time this sprite has spent in its current state.
     * Measured in seconds.
     */

    private double stateTime;

    /**
     * Creates a new sprite at the given position.
     * The sprite will be black and stationary.
     *
     * @param initX the x-coordinate of the new sprite
     * @param initY the y-coordinate of the new sprite
     * @param r the radius of a circle that bounds the new sprite
     */

    public Sprite(double initX, double initY, double r)
    {
	this(initX, initY, r, 0.0, 0.0, Color.BLACK);
    }

    /**
     * Creates a new sprite at the given position.
     * The sprite will be stationary.
     *
     * @param initX the x-coordinate of the new sprite
     * @param initY the y-coordinate of the new sprite
     * @param r the radius of a circle that bounds the new sprite
     * @param c the color of the new sprite
     */

    public Sprite(double initX, double initY, double r, Color c)
    {
	this(initX, initY, r, 0.0, 0.0, c);
    }

    /**
     * Creates a new sprite at the given position.
     *
     * @param initX the x-coordinate of the new sprite
     * @param initY the y-coordinate of the new sprite
     * @param r the radius of a circle that bounds the new sprite
     * @param initVx the x-component of the velocity of the new sprite
     * @param initVy the y-component of the velocity of the new sprite
     * @param c the color of the new sprite
     */

    public Sprite(double initX, double initY, double r, double initVx, double initVy, Color c)
    {
	x = initX;
	y = initY;
	radius = r;
	vx = initVx;
	vy = initVy;
	color = c;
    }

    /**
     * Returns the x-coordinate of this sprite.
     *
     * @return the x-coordinate of this sprite
     */

    public double getX()
    {
	return x;
    }

    /**
     * Returns the y-coordinate of this sprite.
     *
     * @return the y-coordinate of this sprite
     */

    public double getY()
    {
	return y;
    }

    /**
     * Returns the color of this sprite.
     *
     * @return the color of this sprite
     */

    public Color getColor()
    {
	return color;
    }

    /**
     * Returns the maximum speed of this sprite.  This implementation
     * returns positive infinity, indicating no limit.  Subclasses should
     * override this method so that <CODE>update</CODE> will take
     * a maximum speed into account.
     */

    public double getMaximumSpeed()
    {
	return Double.POSITIVE_INFINITY;
    }

    /**
     * Sets the velocity of this sprite.  If the given velocity exceeds the
     * maximum, it will be reduced so that it equals the maximum.  In such
     * a case the direction will remain the same.
     *
     * @param vx the new velocity in the horizontal direction
     * @param vy the new velocity in the vertical direction
     */

    public void setVelocity(double vxNew, double vyNew)
    {
	vx = vxNew;
	vy = vyNew;

	// limit speed

	double v = Math.sqrt(vx * vx + vy * vy);
	double maxV = getMaximumSpeed();

	if (v > maxV)
	    {
		vx /= v / maxV;
		vy /= v / maxV;
	    }
    }

    /**
     * Returns the radius of a circle that encloses this sprite.
     *
     * @return the bounding radius of this sprite
     */

    public double getBoundingRadius()
    {
	return radius;
    }

    /**
     * Returns the polygonal outline of this Sprite.  This implementation
     * returns the square inscribed in the bounding circle as defined
     * by <CODE>getBoundingRadius</CODE> and that has sides parallel to the
     * axes of the coordinate system.  Subclasses should override this
     * method for sprites of other shapes.
     *
     * @return the outline of this sprite
     */

    public GeneralPath getOutline()
    {
	GeneralPath outline = new GeneralPath();

	outline.moveTo((float)(x + Math.cos(Math.PI / 4) * getBoundingRadius()),
		       (float)(y + Math.sin(Math.PI / 4) * getBoundingRadius()));
	outline.lineTo((float)(x + Math.cos(Math.PI / 4) * getBoundingRadius()),
		       (float)(y - Math.sin(Math.PI / 4) * getBoundingRadius()));
	outline.lineTo((float)(x - Math.cos(Math.PI / 4) * getBoundingRadius()),
		       (float)(y - Math.sin(Math.PI / 4) * getBoundingRadius()));
	outline.lineTo((float)(x - Math.cos(Math.PI / 4) * getBoundingRadius()),
		       (float)(y + Math.sin(Math.PI / 4) * getBoundingRadius()));
	outline.closePath();

	return outline;
    }

    /**
     * Determines if this sprite collides with the given sprite.
     * Two sprites are considered in collision if their outlines intersect.
     * (Note that this ignores the special case of one sprite completely
     * inside the other.)
     *
     * @return true if and only if the two sprites are in collision
     */

    public boolean collidesWith(Sprite s)
    {
	// first, get the points and outlines

	List< Point2D > p1 = getPointsList();
	List< Point2D > p2 = s.getPointsList();

	GeneralPath out1 = getOutline();
	GeneralPath out2 = s.getOutline();

	// iterate over each segment in this sprite's outline

	for (int i = 0; i < p1.size() - 1; i++)
	    {
		// test points for being inside or part of
		// a segment than intersects

		for (int j = 0; j < p2.size() - 1; j++)
		    {
			if (Line2D.linesIntersect(p1.get(i).getX(),
						  p1.get(i).getY(),
						  p1.get(i + 1).getX(),
						  p1.get(i + 1).getY(),
						  p2.get(j).getX(),
						  p2.get(j).getY(),
						  p2.get(j + 1).getX(),
						  p2.get(j + 1).getY())
			    )
			    return true;
		    }
	    }

	// we also need to test whether points of one polygon are
	// inside the other; we assume here that updates are often
	// enough so that there is an edge collision detected above
	// before such a case (but suppose the peanut randomly appears
	// inside the player or the elephant...)

	return false;
    }

    /**
     * Returns the list of points on the outline of this sprite.  The
     * starting point is on the list at the start and at the end.
     * This implementation currently returns an <CODE>ArrayList</CODE>
     * in order to facilitate random access to the points.
     *
     * @return a list of points on the outline of this sprite
     */

    private List< Point2D > getPointsList()
    {
	List< Point2D > l = new ArrayList< Point2D >();

	double[] coords = new double[6]; // for getting coords from iterator

	PathIterator i = getOutline().getPathIterator(new AffineTransform());
	while (!i.isDone())
	    {
		i.currentSegment(coords);
		l.add(new Point2D.Double(coords[0], coords[1]));
		i.next();
	    }

	// add first point as last

	l.add(l.get(0));

	return l;
    }

    /**
     * Updates this sprite's position and velocity.  The velocity will be
     * adjusted so that it does not exceed the maximum speed as specified by
     * the <CODE>getMaximumSpeed</CODE> method.
     *
     * @param t the time since the last update, in seconds
     * @param w the world this sprite belongs to
     */

    public void update(double t, GameModel m)
    {
	stateTime += t;

	// update position

	x += vx * t;
	y += vy * t;
    }

    /**
     * Returns the state of this sprite.
     *
     * @return the current state
     */

    public int getState()
    {
	return state;
    }

    /**
     * Sets the state of this sprite.  The state timer is reset to 0.
     *
     * @param s the new state
     */

    public void setState(int s)
    {
	state = s;
	stateTime = 0.0;
    }

    /**
     * Returns the amount of time this sprite has spent in its current state.
     *
     * @return the time in the current state
     */

    public double getStateTime()
    {
	return stateTime;
    }
}


