/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002, Geotools Project Managment Committee (PMC)
 *
 *    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;
 *    version 2.1 of the License.
 *
 *    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.
 *
 */
package org.geotools.coverage.grid.io;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.geotools.factory.FactoryCreator;
import org.geotools.factory.FactoryRegistry;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverage;

/**
 * Enable programs to find all available grid format implementations.
 * 
 * <p>
 * In order to be located by this finder datasources must provide an
 * implementation of the {@link GridFormatFactorySpi} interface.
 * </p>
 * 
 * <p>
 * In addition to implementing this interface datasouces should have a services
 * file:<br/><code>META-INF/services/org.geotools.data.GridFormatFactorySpi</code>
 * </p>
 * 
 * <p>
 * The file should contain a single line which gives the full name of the
 * implementing class.
 * </p>
 * 
 * <p>
 * Example:<br/><code>org.geotools.data.mytype.MyTypeDataStoreFacotry</code>
 * </p>
 * 
 * @source $URL:
 *         http://svn.geotools.org/geotools/trunk/gt/module/main/src/org/geotools/data/coverage/grid/GridFormatFinder.java $
 */
public final class GridFormatFinder {
	/**
	 * The service registry for this manager. Will be initialized only when
	 * first needed.
	 */
	private static FactoryRegistry registry;

	/**
	 * Do not allows any instantiation of this class.
	 */
	private GridFormatFinder() {
		// singleton
	}

	/**
	 * Finds all avalaible implementations of {@link GridFormatFactorySpi} which
	 * have registered using the services mechanism, and that have the
	 * appropriate libraries on the classpath.
	 * 
	 * @return An unmodifiable {@link Set} of all discovered datastores which
	 *         have registered factories, and whose available method returns
	 *         true.
	 */
	public static synchronized Set getAvailableFormats() {
		// get all GridFormatFactorySpi implementations
		scanForPlugins();
		Iterator it = getServiceRegistry().
			getServiceProviders(GridFormatFactorySpi.class, true);
		final Set formats= new HashSet(5);
		GridFormatFactorySpi spi;
		while (it.hasNext()) {
			spi = (GridFormatFactorySpi) it.next();
			if (spi.isAvailable())
				formats.add(spi);

		}
		return Collections.unmodifiableSet(formats);
	}

	/**
	 * Returns the service registry. The registry will be created the first time
	 * this method is invoked.
	 */
	private static FactoryRegistry getServiceRegistry() {
		assert Thread.holdsLock(GridFormatFinder.class);
		if (registry == null) {
			registry = new FactoryCreator(Arrays
					.asList(new Class[] { GridFormatFactorySpi.class }));
		}
		return registry;
	}

	/**
	 * Scans for factory plug-ins on the application class path. This method is
	 * needed because the application class path can theoretically change, or
	 * additional plug-ins may become available. Rather than re-scanning the
	 * classpath on every invocation of the API, the class path is scanned
	 * automatically only on the first invocation. Clients can call this method
	 * to prompt a re-scan. Thus this method need only be invoked by
	 * sophisticated applications which dynamically make new plug-ins available
	 * at runtime.
	 */
	public static synchronized void scanForPlugins() {
		
		getServiceRegistry().scanForPlugins();
		
	}

	/**
	 * Returns an array with all available {@link GridFormatFactorySpi}
	 * implementations.
	 * 
	 * <p>
	 * It can be used toget basic information about all the available
	 * {@link GridCoverage} plugins. Note that this method finds all the
	 * implemented plugins but returns only the availaible one.
	 * 
	 * <p>
	 * A plugin could be implemented but not availaible due to missing
	 * dependencies.
	 * 
	 * 
	 * @return an array with all available {@link GridFormatFactorySpi}
	 *         implementations.
	 */
	public static Format[] getFormatArray() {
		GridFormatFactorySpi element;
		Set formats = GridFormatFinder.getAvailableFormats();
		List formatSet = new ArrayList(formats.size());
		for (Iterator iter = formats.iterator(); iter.hasNext();) {
			element = (GridFormatFactorySpi) iter.next();
			formatSet.add(element.createFormat());
		}
		return (Format[]) formatSet.toArray(new Format[formatSet.size()]);
	}

	/**
	 * Returns all the {@link Format}s that can read the supplied
	 * {@link Object} o.
	 * 
	 * @param o
	 *            is the object to search a {@link Format} that is able to read
	 * @return an unmodifiable {@link Set} comprising all the {@link Format}
	 *         that can read the {@link Object} o.
	 */
	public static synchronized Set findFormats(Object o) {
		final Set availaibleFormats = getAvailableFormats();
		final Set formats=new HashSet();
		final Iterator it = availaibleFormats.iterator();
		Format retVal = null;
		GridFormatFactorySpi spi = null;
		while (it.hasNext()) {
			// get the factory
			spi = (GridFormatFactorySpi) it.next();
			// create a format for it
			retVal = spi.createFormat();
			// check if we can accept it
			if (retVal instanceof AbstractGridFormat) {
				if (((AbstractGridFormat) retVal).accepts(o))
					formats.add(retVal);
			}

		}

		return Collections.unmodifiableSet(formats);
	}

	/**
	 * Returns a {@link Format} that is able to read a certain object. If no
	 * {@link Format} is able to read such an {@link Object} we return an
	 * {@link UnknownFormat} object.
	 * 
	 * <p>
	 * It is worth to point out that this method will try to convert each format
	 * implementation to {@link AbstractGridFormat} because the original
	 * {@link Format} interface did not allow for an accept method hence we had
	 * to subclass the interface to add such method and we did so by the
	 * {@link AbstractGridFormat} abstract class.
	 * 
	 * @param o
	 *            the object to check for acceptance.
	 * @return an {@link AbstractGridFormat} that has stated to accept this
	 *         {@link Object} o or <code>null</code> in no plugins was able to
	 *         accept it.
	 */
	public static synchronized Format findFormat(Object o) {
		final Set formats = findFormats(o);
		final Iterator it = formats.iterator();
		if (it.hasNext())
			return (Format) it.next();
		return new UnknownFormat();

	}
}
