//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.media.sprite;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import java.awt.Graphics2D;
import java.awt.Shape;

import com.google.common.base.Predicate;

import com.samskivert.util.SortableArrayList;

import com.threerings.media.AbstractMedia;
import com.threerings.media.AbstractMediaManager;

/**
 * The sprite manager manages the sprites running about in the game.
 */
public class SpriteManager extends AbstractMediaManager
{
    /**
     * When an animated view processes its dirty rectangles, it may require an expansion of the
     * dirty region which may in turn require the invalidation of more sprites than were originally
     * invalid. In such cases, the animated view can call back to the sprite manager, asking it to
     * append the sprites that intersect a particular region to the given list.
     *
     * @param list the list to fill with any intersecting sprites.
     * @param shape the shape in which we have interest.
     */
    public void getIntersectingSprites (List<Sprite> list, Shape shape)
    {
        int size = _sprites.size();
        for (int ii = 0; ii < size; ii++) {
            Sprite sprite = _sprites.get(ii);
            if (sprite.intersects(shape)) {
                list.add(sprite);
            }
        }
    }

    /**
     * When an animated view is determining what entity in its view is under the mouse pointer, it
     * may require a list of sprites that are "hit" by a particular pixel. The sprites' bounds are
     * first checked and sprites with bounds that contain the supplied point are further checked
     * for a non-transparent at the specified location.
     *
     * @param list the list to fill with any intersecting sprites, the sprites with the highest
     * render order provided first.
     * @param x the x (screen) coordinate to be checked.
     * @param y the y (screen) coordinate to be checked.
     */
    public void getHitSprites (List<Sprite> list, int x, int y)
    {
        for (int ii = _sprites.size() - 1; ii >= 0; ii--) {
            Sprite sprite = _sprites.get(ii);
            if (sprite.hitTest(x, y)) {
                list.add(sprite);
            }
        }
    }

    /**
     * Finds the sprite with the highest render order that hits the specified pixel.
     *
     * @param x the x (screen) coordinate to be checked
     * @param y the y (screen) coordinate to be checked
     * @return the highest sprite hit
     */
    public Sprite getHighestHitSprite (int x, int y)
    {
        // since they're stored in lowest -> highest order..
        for (int ii = _sprites.size() - 1; ii >= 0; ii--) {
            Sprite sprite = _sprites.get(ii);
            if (sprite.hitTest(x, y)) {
                return sprite;
            }
        }
        return null;
    }

    /**
     * Add a sprite to the set of sprites managed by this manager.
     *
     * @param sprite the sprite to add.
     */
    public void addSprite (Sprite sprite)
    {
        if (insertMedia(sprite)) {
            // and invalidate the sprite's original position
            sprite.invalidate();
        }
    }

    /**
     * Returns a list of all sprites registered with the sprite manager.  The returned list is
     * immutable, sprites should be added or removed using {@link #addSprite} or {@link
     * #removeSprite}.
     */
    public List<Sprite> getSprites ()
    {
        return Collections.unmodifiableList(_sprites);
    }

    /**
     * Returns an iterator over our managed sprites. Do not call {@link Iterator#remove}.
     */
    public Iterator<Sprite> enumerateSprites ()
    {
        return _sprites.iterator();
    }

    /**
     * Removes the specified sprite from the set of sprites managed by this manager.
     *
     * @param sprite the sprite to remove.
     */
    public void removeSprite (Sprite sprite)
    {
        removeMedia(sprite);
    }

    /**
     * Removes all sprites that match the supplied predicate.
     */
    public void removeSprites (Predicate<Sprite> pred)
    {
        int idxoff = 0;
        for (int ii = 0, ll = _sprites.size(); ii < ll; ii++) {
            Sprite sprite = _sprites.get(ii-idxoff);
            if (pred.apply(sprite)) {
                _sprites.remove(sprite);
                sprite.invalidate();
                sprite.shutdown();
                // we need to preserve the original "index" relative to the current tick position,
                // so we don't decrement ii directly
                idxoff++;
                if (ii <= _tickpos) {
                    _tickpos--;
                }
            }
        }
    }

    /**
     * Render the sprite paths to the given graphics context.
     *
     * @param gfx the graphics context.
     */
    public void renderSpritePaths (Graphics2D gfx)
    {
        for (int ii=0, nn=_sprites.size(); ii < nn; ii++) {
            Sprite sprite = _sprites.get(ii);
            sprite.paintPath(gfx);
        }
    }

// NOTE- collision handling code is turned off for now. To re-implement,
// a new array should be kept with sprites sorted in some sort of x/y order
//
//    /**
//     * Check all sprites for collisions with others and inform any
//     * sprite observers.
//     */
//    protected void handleCollisions ()
//    {
//        // gather a list of all sprite collisions
//        int size = _sprites.size();
//        for (int ii = 0; ii < size; ii++) {
//          Sprite sprite = _sprites.get(ii);
//            checkCollisions(ii, size, sprite);
//        }
//    }
//
//    /**
//     * Check a sprite for collision with any other sprites in the
//     * sprite list and notify the sprite observers associated with any
//     * sprites that do indeed collide.
//     *
//     * @param idx the starting sprite index.
//     * @param size the total number of sprites.
//     * @param sprite the sprite to check against other sprites for
//     * collisions.
//     */
//    protected void checkCollisions (int idx, int size, Sprite sprite)
//    {
//        // TODO: make this handle quickly moving objects that may pass
//        // through each other.
//
//        // if we're the last sprite we know we've already handled any
//        // collisions
//        if (idx == (size - 1)) {
//            return;
//        }
//
//        // calculate the x-position of the right edge of the sprite we're
//        // checking for collisions
//        Rectangle bounds = sprite.getBounds();
//        int edgeX = bounds.x + bounds.width;
//
//        for (int ii = (idx + 1); ii < size; ii++) {
//            Sprite other = _sprites.get(ii);
//            Rectangle obounds = other.getBounds();
//            if (obounds.x > edgeX) {
//                // since sprites are stored in the list sorted by
//                // ascending x-position, we know this sprite and any
//                // other sprites farther on in the list can't possibly
//                // intersect with the sprite we're checking, so we're
//                // done.
//                return;
//
//            } else if (obounds.intersects(bounds)) {
//                sprite.notifyObservers(new CollisionEvent(sprite, other));
//            }
//        }
//    }
//    /** The comparator used to sort sprites by horizontal position. */
//    protected static final Comparator SPRITE_COMP = new SpriteComparator();
//
//    /** Used to sort sprites. */
//    protected static class SpriteComparator<Sprite> implements Comparator
//    {
//        public int compare (Sprite s1, Sprite s2)
//        {
//            return (s2.getX() - s1.getX());
//        }
//    }

    @Override
    protected SortableArrayList<? extends AbstractMedia> createMediaList ()
    {
        return (_sprites = new SortableArrayList<Sprite>());
    }

    protected SortableArrayList<Sprite> _sprites;
}
