/*
 * $Id: AbstractOpImpl.java 4483 2008-11-16 18:15:32Z uckelman $
 *
 * Copyright (c) 2007-2008 by Joel Uckelman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */

package VASSAL.tools.imageop;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import VASSAL.tools.ErrorDialog;
import VASSAL.tools.opcache.OpCache;

/**
 * An abstract representation of an operation which may be applied to an
 * {@link Image}. <code>ImageOp</code> is the base class for all such
 * operations. The results of all operations are memoized (using a
 * memory-sensitive cache), so retrieving results is both fast and
 * memory-efficient.
 * 
 * <p><b>Warning:</b> For efficiency reasons, the methods {@link #getImage}
 * and {@link #getTile} do <em>not</em> return <code>Image</code>s
 * defensively, nor do the {@code Future<Image>}s returned by
 * {@link #getFutureImage} and {@link #getFutureTile}. That is, the
 * <code>Image</code>  returned is possibly the one retained internally by
 * the <code>ImageOp</code>. Therefore, <code>Image</code>s obtained from
 * an <code>ImageOp</code> <em>must not</em> be altered, as this might
 * interfere with image caching. If an <code>Image</code> obtained this way
 * needs to be modified, copy the <code>Image</code> first and alter the
 * copy.</p>
 *
 * @since 3.1.0
 * @author Joel Uckelman
 */
public abstract class AbstractOpImpl
  extends VASSAL.tools.opcache.AbstractOpImpl<BufferedImage>
  implements ImageOp
{
  /** The cached size of this operation's resulting <code>Image</code>. */
  protected Dimension size;

  /** The cache which contains calculated <code>Image</code>s. */
  protected static final OpCache cache = new OpCache();

  public static void clearCache() {
    cache.clear();  
  }

  public AbstractOpImpl() {
    super(cache);
  }

  /** {@inheritDoc} */
  public abstract BufferedImage eval() throws Exception;

  /** {@inheritDoc} */
  public BufferedImage getImage() {
    try {
      return getImage(null);
    }
    catch (CancellationException e) {
      // FIXME: bug until we permit cancellation 
      ErrorDialog.bug(e);
    }
    catch (InterruptedException e) {
      ErrorDialog.bug(e);
    }
    catch (ExecutionException e) {
      if (!Op.handleException(e)) ErrorDialog.bug(e);
    }

    return null;
  }

  /** {@inheritDoc} */
  public BufferedImage getImage(ImageOpObserver obs)
    throws CancellationException, InterruptedException, ExecutionException
  {
    return get(obs);
  }

  /** {@inheritDoc} */
  public Future<BufferedImage> getFutureImage(ImageOpObserver obs)
                                                    throws ExecutionException {
    return getFuture(obs);
  }

  /**
   * A utility method for retrieving the size of the computed
   * <code>Image</code> from the cache if the <code>Image</code>
   * is cached.
   *
   * @return the size of the cached <code>Image</code>, or
   * <code>null</code> if the <code>Image</code> isn't cached
   */
  protected Dimension getSizeFromCache() {
    final BufferedImage im = cache.getIfDone(newKey());
    return im == null ? null : new Dimension(im.getWidth(), im.getHeight());
  }

  /**
   * Sets the <code>size</code> which is used by {@link getSize},
   * {@link getHeight}, and {@link getWidth}.
   */
  protected abstract void fixSize();

  /** {@inheritDoc} */
  public Dimension getSize() {
    if (size == null) fixSize();
    return new Dimension(size);
  }

  /** {@inheritDoc} */
  public int getWidth() {
    if (size == null) fixSize();
    return size.width;
  }

  /** {@inheritDoc} */
  public int getHeight() {
    if (size == null) fixSize();
    return size.height;
  }

  /** {@inheritDoc} */
  public abstract Dimension getTileSize();

  /** {@inheritDoc} */
  public abstract int getTileHeight();

  /** {@inheritDoc} */
  public abstract int getTileWidth();

  /** {@inheritDoc} */
  public abstract int getNumXTiles();

  /** {@inheritDoc} */
  public abstract int getNumYTiles();

  /** {@inheritDoc} */
  public BufferedImage getTile(Point p, ImageOpObserver obs)
    throws CancellationException, InterruptedException, ExecutionException
  {
    return getTile(p.x, p.y, obs);
  }

  /** {@inheritDoc} */
  public abstract BufferedImage getTile(int tileX, int tileY,
                                        ImageOpObserver obs)
    throws CancellationException, InterruptedException, ExecutionException;

  /** {@inheritDoc} */
  public Future<BufferedImage> getFutureTile(Point p, ImageOpObserver obs)
    throws ExecutionException
  {
    return getFutureTile(p.x, p.y, obs);
  }

  /** {@inheritDoc} */
  public abstract Future<BufferedImage> getFutureTile(
    int tileX, int tileY, ImageOpObserver obs) throws ExecutionException;

  /** {@inheritDoc} */
  public ImageOp getTileOp(Point p) {
    return getTileOp(p.x, p.y);
  }

  /** {@inheritDoc} */
  public abstract ImageOp getTileOp(int tileX, int tileY);

  /** {@inheritDoc} */
  public abstract Point[] getTileIndices(Rectangle rect);
}
