import java.util.*;

/**
 * The current state of a game.
 *
 * @author Jim Glenn
 * @version 0.1 1/19/2009 from LiberationGame of 1/8/2009
 */

public class GameModel extends Observable
{
    /**
     * The list of sprites in this game.
     */

    private List< Sprite > sprites;

    /**
     * The list of sprites to be removed at the next update.  This is
     * necessary because as we iterate through sprites they may want to
     * remove other sprites, but we can't modify the list of sprites
     * as we iterate through it.
     */

    private Set< Sprite > toRemove;

    /**
     * The time of the last update to this game.
     */

    private long lastUpdate;

    /**
     * The number of milliseconds in a second.
     */

    private static final double MILLIS_PER_SECOND = 1000.0;

    /**
     * Creates a new game.
     */

    public GameModel()
    {
	sprites = new ArrayList< Sprite >();
	toRemove = new HashSet< Sprite >();

	lastUpdate = -1;
    }

    /**
     * Returns a list of the active sprites in this game.
     *
     * @return a list of sprites in this game
     */

    public List< Sprite > getSprites()
    {
	List< Sprite > l = new LinkedList< Sprite >(sprites);

	l.removeAll(toRemove);

	return l;
    }

    /**
     * Adds the given sprite to this game.
     *
     * @param s the sprite to add
     */

    public void addSprite(Sprite s)
    {
	sprites.add(s);
    }

    /**
     * Removes the given sprite from this game.
     *
     * @param s the sprite to remove
     */

    public void removeSprite(Sprite s)
    {
	toRemove.add(s);
    }

    /**
     * Returns the width of the playable area of this game.
     *
     * @return the width of this game
     */

    public double getWidth()
    {
	return 1.0;
    }

    /**
     * Returns the height of the playable area of this game.
     *
     * @return the height of this game
     */

    public double getHeight()
    {
	return 1.0;
    }

    /**
     * Removes sprites that are currently on the to-be-removed list.
     */

    private void processRemovedSprites()
    {
	for (Sprite s : toRemove)
	    {
		sprites.remove(s);
	    }

	toRemove.clear();
    }

    /**
     * Updates all the sprites in this world.
     */

    public void update()
    {
	// update sprites

	long currTime = System.currentTimeMillis();

	if (lastUpdate != -1)
	    {
		for (Sprite s : sprites)
		    {
			s.update((currTime - lastUpdate) / MILLIS_PER_SECOND,
				 this);
		    }
	    }

	lastUpdate = currTime;

	// check for and handle collisions

	checkCollisions();

	// process removed list

	processRemovedSprites();

	// notify view that is presumably waiting for updates from this model

	setChanged();
	notifyObservers();
    }

    /**
     * Checks for and handles collisions between sprites.  Collision
     * handling between a pair of sprites is delegated to the
     * <CODE>handleCollisions</CODE> method; subclasses should
     * override that method for actions appropriate to the particular
     * game.
     */

    protected void checkCollisions()
    {
	long start = System.currentTimeMillis();
	// iterate over the n(n-1)/2 distinct pairs of sprites

	for (int i = 0; i < sprites.size(); i++)
	    {
		Sprite s1 = sprites.get(i);

		if (!toRemove.contains(s1))
		    {
			for (int j = i + 1; j < sprites.size(); j++)
			    {
				Sprite s2 = sprites.get(j);

				if (!toRemove.contains(s2)
				    && close(s1, s2)
				    && s1.collidesWith(s2))
				    {
					handleCollision(s1, s2);
				    }
			    }
		    }
	    }
	long end = System.currentTimeMillis();
	System.out.println("checkCollisions: " + (end - start) + "ms");
    }

    /**
     * Checks if the two given sprites are close enough to collide.
     * This check is performed using the bounding radius: if the
     * distance between the control points of the sprites is less than
     * the sum of their radii then the sprites are close enough to
     * collide.  Because this is a very rough collision check, false
     * positives are possible: the return value may be <CODE>false</CODE>
     * even though the sprites are not in collision.  Therefore
     * a more precise collision test is necessary.
     * 
     *
     * @param s1 a sprite
     * @param s2 a sprite
     * @return false if the sprites are not in collision
     */

    private boolean close(Sprite s1, Sprite s2)
    {
	double sumRadii = s1.getBoundingRadius() + s2.getBoundingRadius();

	return (Math.sqrt(Math.pow(s1.getX() - s2.getX(), 2)
			  + Math.pow(s1.getY() - s2.getY(), 2)) < sumRadii);
    }

    /**
     * Handles a collision between the given sprites.  This implementation
     * does nothing; subclasses should override this method to implement
     * the rules of a game.
     *
     * @param s1 a sprite
     * @param s2 a sprite that has collided with <CODE>s2</CODE>
     */

    protected void handleCollision(Sprite s1, Sprite s2)
    {
	System.out.println("Oof: " + s1 + " " + s2);
    }
}

