/*
 * ImageLoader
 *
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2006 Marco Schmidt.
 * All rights reserved.
 */

package net.sourceforge.jiu.codecs;

import java.awt.Frame;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import net.sourceforge.jiu.codecs.InvalidFileStructureException;
import net.sourceforge.jiu.codecs.InvalidImageIndexException;
import net.sourceforge.jiu.codecs.WrongFileFormatException;
import net.sourceforge.jiu.codecs.BMPCodec;
import net.sourceforge.jiu.codecs.IFFCodec;
import net.sourceforge.jiu.codecs.PCDCodec;
import net.sourceforge.jiu.codecs.PNGCodec;
import net.sourceforge.jiu.codecs.PNMCodec;
import net.sourceforge.jiu.codecs.PSDCodec;
import net.sourceforge.jiu.codecs.RASCodec;
import net.sourceforge.jiu.codecs.tiff.TIFFCodec;
import net.sourceforge.jiu.data.PixelImage;
import net.sourceforge.jiu.gui.awt.ImageCreator;
import net.sourceforge.jiu.ops.MissingParameterException;
import net.sourceforge.jiu.ops.OperationFailedException;

/**
 * A convenience class with static methods to load images from files using JIU codecs.
 * The load methods of this class try to load an image with all codecs  registered with this class.
 * This includes almost every codec that resides in the <code>net.sourceforge.jiu.codecs</code> package.
 * You can register additional codecs with {@link #registerCodecClass} or remove the usage
 * of codecs with {@link #removeCodecClass}.
 * <p>
 * A Codec that cannot safely identify a file to be in the format that it supports must not be used with ImageLoader.
 * The failure to identify typically comes from the lack of magic byte sequences defined for the format.
 * In order to load such a file, use the codec manually.
 * Example: {@link PalmCodec}.
 * <p>
 * In order to load an image via {@link java.awt.Toolkit} (JPEG, PNG or GIF), use
 * {@link net.sourceforge.jiu.gui.awt.ToolkitLoader}.
 * It combines the loading features of java.awt.Toolkit and JIU's ImageLoader.
 * <h3>Usage example</h3>
 * <pre>
 * PixelImage image = null;
 * try
 * {
 *   image = ImageLoader.load("image.tif");
 * }
 * catch (Exception e)
 * {
 *   // handle exception
 * }
 * </pre>
 * @author Marco Schmidt
 */
public class ImageLoader
{
	// all elements of class String
	private static Vector fileExtensions;
	private static Vector imageCodecClasses;

	static
	{
		imageCodecClasses = new Vector();
		registerCodecClass(new BMPCodec());
		registerCodecClass(new IFFCodec());
		registerCodecClass(new PCDCodec());
		registerCodecClass(new PNGCodec());
		registerCodecClass(new PNMCodec());
		registerCodecClass(new PSDCodec());
		registerCodecClass(new RASCodec());
		registerCodecClass(new TIFFCodec());
	}

	private ImageLoader()
	{
	}

	/**
	 * Creates an instance of one of the codec classes known to ImageLoader.
	 * @param index 0-based index of codec number, maximum value is {@link #getNumCodecs}<code> - 1</code>
	 * @return new codec object or <code>null</code> if no object could be instantiated
	 */
	public static ImageCodec createCodec(int index)
	{
		ImageCodec result = null;
		if (index >= 0 && index < getNumCodecs())
		{
			Class c = (Class)imageCodecClasses.elementAt(index);
			try
			{
				Object obj = c.newInstance();
				if (obj != null && obj instanceof ImageCodec)
				{
					result = (ImageCodec)obj;
				}
			}
			catch (IllegalAccessException iae)
			{
				// ignore
			}
			catch (InstantiationException ie)
			{
				// ignore
			}
		}
		return result;
	}

	/**
	 * Returns a filename filter ({@link java.io.FilenameFilter}) that accepts files
	 * with name extensions typical for the image file formats known to ImageLoader.
	 * The filter could then be used in an file dialog like {@link java.awt.FileDialog}.
	 * <p>
	 * Note that this filter does not include file formats supported by the AWT 
	 * {@link java.awt.Toolkit} (GIF and JPEG, also PNG since Java 1.3).
	 * @return filter for image file names
	 */
	public static FilenameFilter createFilenameFilter()
	{
		return new FilenameFilter()
		{
			public boolean accept(File dir, String name)
			{
				if (name == null)
				{
					return false;
				}
				if (fileExtensions == null)
				{
					updateFileExtensions();
				}
				name = name.toLowerCase();
				int index = 0;
				while (index < fileExtensions.size())
				{
					String ext = (String)fileExtensions.elementAt(index++);
					if (name.endsWith(ext))
					{
						return true;
					}
				}
				return false;
			}
		};
	}

	/**
	 * Returns the number of codec classes currently known to ImageLoader.
	 * This number can be changed by registering additional codecs
	 * ({@link #registerCodecClass})
	 * or by removing codec classes ({@link #removeCodecClass}).
	 * @return number of known codec classes
	 */
	public static int getNumCodecs()
	{
		return imageCodecClasses.size();
	}

	/**
	 * Attempts to load an image from a file.
	 * @param file the file from which an image is to be loaded
	 * @return the image on success or <code>null</code> on failure
	 */
	public static PixelImage load(File file) throws
		IOException, 
		InvalidFileStructureException,
		InvalidImageIndexException,
		UnsupportedTypeException
	{
		return load(file, null);
	}

	/**
	 * Attempts to load an image from a file, notifying the
	 * argument progress listeners.
	 * @param file the file to load an image from
	 * @param listeners a Vector of ProgressListener objects to be notified 
	 * @return an instance of a class implementing {@link PixelImage}
	 */
	public static PixelImage load(File file, Vector listeners) throws 
		IOException, 
		InvalidFileStructureException,
		InvalidImageIndexException,
		UnsupportedTypeException
	{
		for (int i = 0; i < getNumCodecs(); i++)
		{
			PixelImage result = null;
			ImageCodec codec = null;
			try
			{
				codec = createCodec(i);
				codec.setFile(file, CodecMode.LOAD);
				codec.addProgressListeners(listeners);
				codec.process();
				result = codec.getImage();
				if (result != null)
				{
					return result;
				}
			}
			catch (MissingParameterException mpe)
			{
				// ignore
			}
			catch (WrongFileFormatException wffe)
			{
				// ignore
			}
			catch (IOException ioe)
			{
				// ignore
			}
			catch (OperationFailedException ofe)
			{
				// ignore
				//System.out.println("codec: " + ofe);
			}
			finally
			{
				if (codec != null)
				{
					codec.close();
				}
			}
		}
		return null;
	}

	/**
	 * Load an image from a file given by its name.
	 * Simply calls load(fileName, null).
	 * @param fileName name of the file from which an image is to be loaded
	 * @return the loaded image on success, null on failure
	 */
	public static PixelImage load(String fileName) throws
		IOException,
		InvalidFileStructureException,
		InvalidImageIndexException,
		UnsupportedTypeException
	{
		return load(fileName, null);
	}

	/**
	 * Attempts to load an image from the file with the given name, 
	 * using the given list of progress listeners.
	 * @param fileName name of the file from which an image is to be loaded
	 * @param listeners a list of objects implementing ProgressListener
	 * @return the loaded image
	 */
	public static PixelImage load(String fileName, Vector listeners) throws
		IOException,
		InvalidFileStructureException,
		InvalidImageIndexException,
		UnsupportedTypeException
	{
		return load(new File(fileName), listeners);
	}

	public static PixelImage loadToolkitImageUri(String uri) throws 
	IOException, 
	InvalidFileStructureException,
	InvalidImageIndexException,
	UnsupportedTypeException
	{
		try
		{
			ImageLoader loader = new ImageLoader();
			InputStream in = loader.getClass().getResourceAsStream(uri);
			if (in == null)
			{
				return null;
			}
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int b;
			while ((b = in.read()) != -1)
			{
				out.write(b);
			}
			in.close();
			byte[] data = out.toByteArray();
			java.awt.Image awtImage = Toolkit.getDefaultToolkit().createImage(data);
			MediaTracker mediaTracker = new MediaTracker(new Frame());
			mediaTracker.addImage(awtImage, 0);
			try
			{
				mediaTracker.waitForID(0);
			}
			catch (InterruptedException ie)
			{
				return null;
			}
			PixelImage image = ImageCreator.convertImageToRGB24Image(awtImage);
			return image;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Registers a codec class with ImageLoader.
	 * The argument is an instance of the class to be registered.
	 * Note that the codec class must have an empty constructor.
	 * <p>
	 * Example: let's say you have written a new codec called ACMEImageCodec.
	 * Your codec supports loading images.
	 * Then you could register it like that:
	 * <pre>
	 * ImageLoader.registerCodecClass(new ACMEImageCodec());
	 * </pre>
	 * <p>
	 * @param codec an instance of the codec class to be registered
	 */
	public static void registerCodecClass(ImageCodec codec)
	{
		if (codec == null)
		{
			return;
		}
		if (imageCodecClasses.contains(codec.getClass()))
		{
			return;
		}
		if (!codec.isLoadingSupported())
		{
			throw new IllegalArgumentException("Codec does not support loading.");
		}
		imageCodecClasses.addElement(codec.getClass());
		updateFileExtensions();
	}

	/**
	 * Removes all codec classes from the internal list of codec classes.
	 * After a call to this method, ImageLoader will not load anything unless
	 * new codecs get registered.
	 */
	public static void removeAllCodecClasses()
	{
		imageCodecClasses = new Vector();
		updateFileExtensions();
	}

	/**
	 * Removes a codec class from the internal list of codec classes.
	 * @param codec an instance of the codec class to be removed
	 */
	public static void removeCodecClass(ImageCodec codec)
	{
		if (codec == null)
		{
			return;
		}
		int index = imageCodecClasses.indexOf(codec.getClass());
		if (index != -1)
		{
			imageCodecClasses.remove(index);
			updateFileExtensions();
		}
		else
		{
			throw new IllegalArgumentException("The argument codec's class " +
				"could not be found in the internal list of codec classes.");
		}
	}

	private static void updateFileExtensions()
	{
		fileExtensions = new Vector();
		int index = 0;
		while (index < getNumCodecs())
		{
			try
			{
				ImageCodec codec = createCodec(index++);
				String[] extArray = codec.getFileExtensions();
				if (extArray != null && extArray.length > 0)
				{
					for (int i = 0; i < extArray.length; i++)
					{
						fileExtensions.addElement(extArray[i].toLowerCase());
					}
				}
			}
			catch (Exception e)
			{
			}
		}
	}
}
