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

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import com.threerings.media.image.Mirage;
import com.threerings.media.util.LinearTimeFunction;
import com.threerings.media.util.TimeFunction;

/**
 * Animates an image changing size about its center point.
 */
public class ScaleAnimation extends Animation
{
    /**
     * Creates a scale animation with the supplied image. If the image's size would ever be 0 or
     * less, it is not drawn.
     *
     * @param image The image to paint.
     *
     * @param center The screen coordinates of the pixel upon which the image's center should
     * always be rendered.
     *
     * @param startScale The amount to scale the image when it is rendered at time 0.
     *
     * @param endScale The amount to scale the image at the final frame of animation.
     *
     * @param duration The time in milliseconds the anim takes to complete.
     */
    public ScaleAnimation (
        Mirage image, Point center, float startScale, float endScale, int duration)
    {
        super(getBounds(Math.max(startScale, endScale), center, image));

        // Save inputted variables
        _image = image;
        _bufferedImage = _image.getSnapshot();
        _center = new Point(center);
        _startScale = startScale;
        _endScale = endScale;

        // Hack the LinearTimeFunction to use fixed point rationals
        //
        // FIXME: This class doesn't seem to be saving me a lot of work, since I have to repackage
        // the outputs into floats anyway. Find some way to make the LinearTimeFunction do more of
        // this work for us, or write a new class that does. Maybe IntLinearTimeFunction and
        // FloatLinearTimeFunction classes would be useful.
        _scaleFunc = new LinearTimeFunction(0, 10000, duration);
    }

    /**
     * Java wants the first call in a constructor to be super() if it exists at all, so we have to
     * trick it with this function.
     *
     * Oh, and this function computes how big the bounding box needs to be to bound the inputted
     * image scaled to the inputted size centered around the inputted center point.
     */
    public static Rectangle getBounds (float scale, Point center, Mirage image)
    {
        Point size = getSize(scale, image);
        Point corner = getCorner(center, size);
        return new Rectangle(corner.x, corner.y, size.x, size.y);
    }

    @Override
    public Rectangle getBounds ()
    {
        return getBounds(_scale, _center, _image);
    }

    /** Computes the width and height to which an image should be scaled. */
    public static Point getSize (float scale, Mirage image)
    {
        int width  = Math.max(0, Math.round(image.getWidth()  * scale));
        int height = Math.max(0, Math.round(image.getHeight() * scale));
        return new Point(width, height);
    }

    /**
     * Computes the upper left corner where the image should be drawn, given the center and
     * dimensions to which the image should be scaled.
     */
    public static Point getCorner (Point center, Point size)
    {
        return new Point(center.x - size.x/2, center.y - size.y/2);
    }

    @Override
    public void tick (long tickStamp)
    {
        // Compute the new scaling value
        float weight = _scaleFunc.getValue(tickStamp) / 10000.0f;
        float scale = ((1.0f - weight) * _startScale) +
                      ((       weight) *   _endScale);

        // Update the animation if the scaling changes
        if (_scale != scale) {
            _scale = scale;
            invalidate();
        }

        // Check if the animation completed
        if (weight >= 1.0f) {
            _finished = true;
        }
    }

    @Override
    public void fastForward (long timeDelta)
    {
        _scaleFunc.fastForward(timeDelta);
    }

    @Override
    public void paint (Graphics2D gfx)
    {
        // Compute the bounding box to render this image
        Rectangle bounds = getBounds();

        // Paint nothing if the image was scaled to nothing
        if (bounds.width <= 0 || bounds.height <= 0) {
            return;
        }

        // Smooth out the image scaling
        //
        // FIXME: Should this be turned off when the painting is done?
        gfx.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                             RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        // Paint the image scaled to this location
        gfx.drawImage(_bufferedImage,
                      bounds.x,
                      bounds.y,
                      bounds.x + bounds.width,
                      bounds.y + bounds.height,
                      0,
                      0,
                      _bufferedImage.getWidth(),
                      _bufferedImage.getHeight(),
                      null);
    }

    /** The image to scale. */
    protected Mirage _image;

    /** The image converted to a format Graphics2D likes, and cached. */
    protected BufferedImage _bufferedImage;

    /** The center pixel to render the image around. */
    protected Point _center;

    /** The amount to scale the image at the start of the animation. */
    protected float _startScale;

    /** The amount to scale the image at the end of the animation. */
    protected float _endScale;

    /** The current amount of scaling to render. */
    protected float _scale;

    /** Computes the image scaling to use at the specified time. */
    protected TimeFunction _scaleFunc;
}
