//
// $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;

import java.util.List;

import java.awt.EventQueue;
import java.awt.Rectangle;

import com.google.common.collect.Lists;

import static com.threerings.media.Log.log;

/**
 * Manages regions (rectangles) that are invalidated in the process of ticking animations and
 * sprites and generally doing other display related business.
 */
public class RegionManager
{
    /**
     * Invalidates the specified region.
     */
    public void invalidateRegion (int x, int y, int width, int height)
    {
        if (isValidSize(width, height)) {
            addDirtyRegion(new Rectangle(x, y, width, height));
        }
    }

    /**
     * Invalidates the specified region (the supplied rectangle will be cloned as the region
     * manager fiddles with the rectangles it uses internally).
     */
    public void invalidateRegion (Rectangle rect)
    {
        if (isValidSize(rect.width, rect.height)) {
            addDirtyRegion((Rectangle)rect.clone());
        }
    }

    /**
     * Adds the supplied rectangle to the dirty regions. Control of the rectangle is given to the
     * region manager as it may choose to bend, fold or mutilate it later. If you don't want the
     * region manager messing with your rectangle, use {@link #invalidateRegion}.
     */
    public void addDirtyRegion (Rectangle rect)
    {
        // make sure we're on an AWT thread
        if (!EventQueue.isDispatchThread()) {
            log.warning("Oi! Region dirtied on non-AWT thread", "rect", rect, new Exception());
        }

        // sanity check
        if (rect == null) {
            log.warning("Attempt to dirty a null rect!?", new Exception());
            return;
        }

        // more sanity checking
        long x = rect.x, y = rect.y;
        if ((Math.abs(x) > Integer.MAX_VALUE/2) || (Math.abs(y) > Integer.MAX_VALUE/2)) {
            log.warning("Requested to dirty questionable region", "rect", rect);
            return; // Let's not do it!
        }

        if (isValidSize(rect.width, rect.height)) {
            // Log.info("Invalidating " + StringUtil.toString(rect));
            _dirty.add(rect);
        }
    }

    /** Used to ensure our dirty regions are not invalid. */
    protected final boolean isValidSize (int width, int height)
    {
        if (width < 0 || height < 0) {
            log.warning("Attempt to add invalid dirty region?!",
                        "size", (width + "x" + height), new Exception());
            return false;

        } else if (width == 0 || height == 0) {
            // no need to complain about zero sized rectangles, just ignore them
            return false;

        } else {
            return true;
        }
    }

    /**
     * Returns true if dirty regions have been accumulated since the last call to {@link
     * #getDirtyRegions}.
     */
    public boolean haveDirtyRegions ()
    {
        return (_dirty.size() > 0);
    }

    /**
     * Returns our unmerged list of dirty regions. <em>Do not</em> modify the returned list. It's
     * just for peeking. Unlike {@link #getDirtyRegions}, this does not clear out the list of dirty
     * regions and prepare for the next frame.
     */
    public List<Rectangle> peekDirtyRegions ()
    {
        return _dirty;
    }

    /**
     * Merges all outstanding dirty regions into a single list of rectangles and returns that to
     * the caller. Internally, the list of accumulated dirty regions is cleared out and prepared
     * for the next frame.
     */
    public Rectangle[] getDirtyRegions ()
    {
        List<Rectangle> merged = Lists.newArrayList();

        for (int ii = _dirty.size() - 1; ii >= 0; ii--) {
            // pop the next rectangle from the dirty list
            Rectangle mr = _dirty.remove(ii);

            // merge in any overlapping rectangles
            for (int jj = ii - 1; jj >= 0; jj--) {
                Rectangle r = _dirty.get(jj);
                if (mr.intersects(r)) {
                    // remove the overlapping rectangle from the list
                    _dirty.remove(jj);
                    ii--;
                    // grow the merged dirty rectangle
                    mr.add(r);
                }
            }

            // add the merged rectangle to the list
            merged.add(mr);
        }

        return merged.toArray(new Rectangle[merged.size()]);
    }

    /** A list of dirty rectangles. */
    protected List<Rectangle> _dirty = Lists.newArrayList();
}
