/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.markup.html.image.resource;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.lang.ref.SoftReference;
import java.time.Instant;
import org.apache.wicket.request.resource.DynamicImageResource;


/**
 * A DynamicImageResource subclass that allows easy rendering of regeneratable (unbuffered) dynamic
 * images. A RenderedDynamicImageResource implements the abstract method render(Graphics2D) to
 * create/re-create a given image on-the-fly. When a RenderedDynamicImageResource is serialized, the
 * image state is transient, which means it will disappear when the resource is sent over the wire
 * and then will be recreated when required.
 * <p>
 * The format of the image (and therefore the resource's extension) can be specified with
 * setFormat(String). The default format is "PNG" because JPEG is lossy and makes generated images
 * look bad and GIF has patent issues.
 * 
 * @see org.apache.wicket.markup.html.image.resource.DefaultButtonImageResource
 * @see org.apache.wicket.markup.html.image.resource.DefaultButtonImageResourceFactory
 * @author Jonathan Locke
 * @author Gili Tzabari
 * @author Johan Compagner
 */
public abstract class RenderedDynamicImageResource extends DynamicImageResource
{
	private static final long serialVersionUID = 1L;

	/** Height of image */
	private int height = 100;

	/** Transient image data so that image only needs to be generated once per VM */
	private transient SoftReference<byte[]> imageData;

	/** Type of image (one of BufferedImage.TYPE_*) */
	private int type = BufferedImage.TYPE_INT_RGB;

	/** Width of image */
	private int width = 100;

	/**
	 * Constructor.
	 * 
	 * @param width
	 *            Width of image
	 * @param height
	 *            Height of image
	 */
	public RenderedDynamicImageResource(final int width, final int height)
	{
		this.width = width;
		this.height = height;
	}

	/**
	 * Constructor.
	 * 
	 * @param width
	 *            Width of image
	 * @param height
	 *            Height of image
	 * @param format
	 *            The format of the image (jpg, png or gif)
	 */
	public RenderedDynamicImageResource(final int width, final int height, String format)
	{
		super(format);
		this.width = width;
		this.height = height;
	}

	/**
	 * @return Returns the height.
	 */
	public synchronized int getHeight()
	{
		return height;
	}

	/**
	 * @return Returns the type (one of BufferedImage.TYPE_*).
	 */
	public synchronized int getType()
	{
		return type;
	}

	/**
	 * @return Returns the width.
	 */
	public synchronized int getWidth()
	{
		return width;
	}

	/**
	 * Causes the image to be redrawn the next time its requested.
	 */
	public synchronized void invalidate()
	{
		imageData = null;
	}

	/**
	 * @param height
	 *            The height to set.
	 */
	public synchronized void setHeight(int height)
	{
		this.height = height;
		invalidate();
	}

	/**
	 * @param type
	 *            The type to set (one of BufferedImage.TYPE_*).
	 */
	public synchronized void setType(int type)
	{
		this.type = type;
		invalidate();
	}

	/**
	 * @param width
	 *            The width to set.
	 */
	public synchronized void setWidth(int width)
	{
		this.width = width;
		invalidate();
	}

	@Override
	protected byte[] getImageData(Attributes attributes)
	{
		// get image data is always called in sync block
		byte[] data = null;
		if (imageData != null)
		{
			data = imageData.get();
		}
		if (data == null)
		{
			data = render(attributes);
			imageData = new SoftReference<byte[]>(data);
			setLastModifiedTime(Instant.now());
		}
		return data;
	}

	/**
	 * Renders this image
	 * 
	 * @param attributes
	 *            the current request attributes
	 * @return The image data
	 */
	protected byte[] render(final Attributes attributes)
	{
		while (true)
		{
			final BufferedImage image = new BufferedImage(getWidth(), getHeight(), getType());
			if (render((Graphics2D)image.getGraphics(), attributes))
			{
				return toImageData(image);
			}
		}
	}

	/**
	 * Override this method to provide your rendering code.
	 * 
	 * @param graphics
	 *            The graphics context to render on.
	 * @param attributes
	 *            the current request attributes
	 * @return {@code true} if the image was rendered. {@code false} if the image size was changed
	 *         by the rendering implementation and the image should be re-rendered at the new size.
	 */
	protected abstract boolean render(Graphics2D graphics, final Attributes attributes);
}
