/*******************************************************************************
 * Copyright (c) 2006, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal.forms.widgets;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.Bundle;

public class BusyIndicator extends Canvas {
	private static final int MARGIN = 0;

	private ImageData[] progressData;

	protected ImageLoader loader;

	protected Image image;

	protected Image animationImage;

	protected Thread busyThread;

	protected boolean stop;

	/**
	 * BusyWidget constructor comment.
	 * 
	 * @param parent
	 *            org.eclipse.swt.widgets.Composite
	 * @param style
	 *            int
	 */
	public BusyIndicator(Composite parent, int style) {
		super(parent, style);

		loadProgressImage();

		addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent event) {
				onPaint(event);
			}
		});
	}

	private void loadProgressImage() {
		InputStream is = null;
		Bundle bundle = Platform.getBundle("org.eclipse.ui.forms"); //$NON-NLS-1$
	    URL url = FileLocator.find(bundle, new Path("$nl$/icons/progress/ani/progress.gif"),null); //$NON-NLS-1$
		if (url != null) {
			try {
				url = FileLocator.resolve(url);
				is = url.openStream();
			} catch (IOException e) {
			    is = null;
			}
		}
		if (is != null) {
			loader = new ImageLoader();
			try {
				progressData = loader.load(is);
			} catch (IllegalArgumentException e) {
			}
			try {
				is.close();
			} catch (IOException e) {
			}
		}
	}

	public Point computeSize(int wHint, int hHint, boolean changed) {
		Point size = new Point(0, 0);
		if (image != null) {
			Rectangle ibounds = image.getBounds();
			size.x = ibounds.width;
			size.y = ibounds.height;
		}
		if (loader != null && isBusy()) {
			size.x = Math.max(size.x, loader.logicalScreenWidth);
			size.y = Math.max(size.y, loader.logicalScreenHeight);
		}
		size.x += MARGIN + MARGIN;
		size.y += MARGIN + MARGIN;
		return size;
	}

	/**
	 * Creates a thread to animate the image.
	 */
	protected synchronized void createBusyThread() {
		if (busyThread != null)
			return;

		stop = false;
		final Display display = getDisplay();
		final Image offScreenImage = new Image(display,
				loader.logicalScreenWidth,
				loader.logicalScreenHeight);
		final GC offScreenImageGC = new GC(offScreenImage);
		busyThread = new Thread() {
			private Image timage;

			public void run() {
				try {
					/*
					 * Create an off-screen image to draw on, and fill it with
					 * the shell background.
					 */
					FormUtil.setAntialias(offScreenImageGC, SWT.ON);
					display.syncExec(new Runnable() {
						public void run() {
							if (!isDisposed())
								drawBackground(offScreenImageGC, 0, 0,
										loader.logicalScreenWidth,
										loader.logicalScreenHeight);
						}
					});
					if (isDisposed())
						return;

					/*
					 * Create the first image and draw it on the off-screen
					 * image.
					 */
					int imageDataIndex = 0;
					ImageData imageData = progressData[imageDataIndex];
					if (timage != null && !timage.isDisposed())
						timage.dispose();
					timage = new Image(display, imageData);
					offScreenImageGC.drawImage(timage, 0, 0,
							imageData.width, imageData.height, imageData.x,
							imageData.y, imageData.width, imageData.height);

					/*
					 * Now loop through the images, creating and drawing
					 * each one on the off-screen image before drawing it on
					 * the shell.
					 */
					int repeatCount = loader.repeatCount;
					while (loader.repeatCount == 0 || repeatCount > 0) {
						if (stop || isDisposed())
							break;
						switch (imageData.disposalMethod) {
						case SWT.DM_FILL_BACKGROUND:
							/*
							 * Fill with the background color before
							 * drawing.
							 */
							/*
							 * offScreenImageGC.fillRectangle(imageData.x,
							 * imageData.y, imageData.width,
							 * imageData.height);
							 */
							final ImageData fimageData = imageData;
							display.syncExec(new Runnable() {
								public void run() {
									if (!isDisposed()) {
										drawBackground(offScreenImageGC, fimageData.x,
												fimageData.y, fimageData.width,
												fimageData.height);
									}
								}
							});
							break;
						case SWT.DM_FILL_PREVIOUS:
							/* Restore the previous image before drawing. */
							offScreenImageGC.drawImage(timage, 0, 0,
									imageData.width, imageData.height,
									imageData.x, imageData.y,
									imageData.width, imageData.height);
							break;
						}

						imageDataIndex = (imageDataIndex + 1)
								% progressData.length;
						imageData = progressData[imageDataIndex];
						timage.dispose();
						timage = new Image(display, imageData);
						offScreenImageGC.drawImage(timage, 0, 0,
								imageData.width, imageData.height,
								imageData.x, imageData.y, imageData.width,
								imageData.height);

						/* Draw the off-screen image to the shell. */
						animationImage = offScreenImage;
						display.syncExec(new Runnable() {
							public void run() {
								if (!isDisposed())
									redraw();
							}
						});
						/*
						 * Sleep for the specified delay time (adding
						 * commonly-used slow-down fudge factors).
						 */
						try {
							int ms = imageData.delayTime * 10;
							if (ms < 20)
								ms += 50;
							if (ms < 30)
								ms += 20;
							Thread.sleep(ms);
						} catch (InterruptedException e) {
						}

						/*
						 * If we have just drawn the last image, decrement
						 * the repeat count and start again.
						 */
						if (imageDataIndex == progressData.length - 1)
							repeatCount--;
					}
				} catch (Exception e) {
					// Trace.trace(Trace.WARNING, "Busy error", e);
					// //$NON-NLS-1$
				} finally {
					display.syncExec(new Runnable() {
						public void run() {
							if (offScreenImage != null
									&& !offScreenImage.isDisposed())
								offScreenImage.dispose();
							if (offScreenImageGC != null
									&& !offScreenImageGC.isDisposed())
								offScreenImageGC.dispose();
						}
					});
					if (timage != null && !timage.isDisposed())
						timage.dispose();
				}
				if (busyThread == null)
					display.syncExec(new Runnable() {
						public void run() {
							animationImage = null;
							if (!isDisposed())
								redraw();
						}
					});
			}
		};
		busyThread.setPriority(Thread.NORM_PRIORITY + 2);
		busyThread.setDaemon(true);
		busyThread.start();
	}

	public void dispose() {
		stop = true;
		busyThread = null;
		super.dispose();
	}

	/**
	 * Return the image or <code>null</code>.
	 */
	public Image getImage() {
		return image;
	}

	/**
	 * Returns true if it is currently busy.
	 * 
	 * @return boolean
	 */
	public boolean isBusy() {
		return (busyThread != null);
	}

	/*
	 * Process the paint event
	 */
	protected void onPaint(PaintEvent event) {
		if (animationImage != null && animationImage.isDisposed()) {
			animationImage = null;
		}
		Rectangle rect = getClientArea();
		if (rect.width == 0 || rect.height == 0)
			return;

		GC gc = event.gc;
		Image activeImage = animationImage != null ? animationImage : image;
		if (activeImage != null) {
			Rectangle ibounds = activeImage.getBounds();
			gc.drawImage(activeImage, rect.width / 2 - ibounds.width / 2,
					rect.height / 2 - ibounds.height / 2);
		}
	}

	/**
	 * Sets the indicators busy count up (true) or down (false) one.
	 * 
	 * @param busy
	 *            boolean
	 */
	public synchronized void setBusy(boolean busy) {
		if (busy) {
			if (busyThread == null)
				createBusyThread();
		} else {
			if (busyThread != null) {
				stop = true;
				busyThread = null;
			}
		}
	}

	/**
	 * Set the image. The value <code>null</code> clears it.
	 */
	public void setImage(Image image) {
		if (image != this.image && !isDisposed()) {
			this.image = image;
			redraw();
		}
	}
}
