/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2005-2006, GeoTools Project Managment Committee (PMC)
 *    (C) 2005, Institut de Recherche pour le D�veloppement
 *   
 *    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.referencing.factory;

// J2SE dependencies
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

// OpenGIS dependencies
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.cs.CSAuthorityFactory;
import org.opengis.referencing.datum.DatumAuthorityFactory;
import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;

// Geotools dependencies
import org.geotools.factory.Hints;
import org.geotools.factory.FactoryRegistryException;
import org.geotools.referencing.ReferencingFactoryFinder;


/**
 * An authority factory that delegates the object creation to an other factory determined
 * from the authority name in the code. This is similar to {@link ManyAuthoritiesFactory}
 * except that the set of factories is determined by calls to
 * <code>ReferencingFactoryFinder.{@linkplain ReferencingFactoryFinder#getCRSAuthorityFactory
 * get<var>Foo</var>AuthorityFactory}(<var>authority</var>, {@linkplain #hints hints})</code>.
 * <p>
 * This class is not registered in {@link ReferencingFactoryFinder}. If this "authority" factory
 * is wanted, then users need to refer explicitly to the {@link #DEFAULT} constant or to create
 * their own instance.
 *
 * @since 2.2
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/AllAuthoritiesFactory.java $
 * @version $Id: AllAuthoritiesFactory.java 29058 2008-02-03 17:47:07Z desruisseaux $
 * @author Martin Desruisseaux
 */
public class AllAuthoritiesFactory extends ManyAuthoritiesFactory {
    /**
     * An instance of {@code AllAuthoritiesFactory} with the
     * {@linkplain GenericName#DEFAULT_SEPARATOR default name separator} and no hints.
     */
    public static AllAuthoritiesFactory DEFAULT = new AllAuthoritiesFactory(null);

    /**
     * The authority names. Used in order to detect changes in the set of registered factories.
     */
    private Collection/*<String>*/ authorityNames;

    /**
     * Creates a new factory using the specified hints.
     *
     * @param hints An optional set of hints, or {@code null} if none.
     */
    public AllAuthoritiesFactory(final Hints hints) {
        this(hints, null);
    }

    /**
     * Creates a new factory using the specified hints and a set of user factories.
     * If {@code factories} is not null, then any call to a {@code createFoo(code)} method will
     * first scan the supplied factories in their iteration order. The first factory implementing
     * the appropriate interface and having the expected {@linkplain AuthorityFactory#getAuthority
     * authority name} will be used. Only if no suitable factory is found, then this class delegates
     * to {@link ReferencingFactoryFinder}.
     * <p>
     * If the {@code factories} collection contains more than one factory for the same authority
     * and interface, then all additional factories will be {@linkplain FallbackAuthorityFactory
     * fallbacks}, to be tried in iteration order only if the first acceptable factory failed to
     * create the requested object.
     *
     * @param hints An optional set of hints, or {@code null} if none.
     * @param factories A set of user-specified factories to try before to delegate
     *        to {@link ReferencingFactoryFinder}, or {@code null} if none.
     *
     * @deprecated This constructor will be removed in GeoTools 2.5.
     */
    public AllAuthoritiesFactory(final Hints hints,
                                 final Collection/*<? extends AuthorityFactory>*/ factories)
    {
        this(hints, factories, (char) 0);
    }

    /**
     * Creates a new factory using the specified hints, user factories and name
     * separator. The optional {@code factories} collection is handled as in the
     * {@linkplain #AllAuthoritiesFactory(Hints, Collection) constructor above}.
     *
     * @param hints An optional set of hints, or {@code null} if none.
     * @param factories A set of user-specified factories to try before to delegate
     *        to {@link ReferencingFactoryFinder}, or {@code null} if none.
     * @param separator The separator between the authority name and the code.
     *
     * @deprecated This constructor will be removed in GeoTools 2.5.
     */
    public AllAuthoritiesFactory(final Hints hints,
                                 final Collection/*<? extends AuthorityFactory>*/ factories,
                                 final char separator)
    {
        super(hints, factories, separator);
        if (hints != null) {
            this.hints.putAll(hints);
        }
    }

    /**
     * Returns the set of authority names.
     *
     * @since 2.4
     */
    //@Override
    public Set/*<String>*/ getAuthorityNames() {
        // Do not use 'authorityNames' since it may be out-of-date.
        return ReferencingFactoryFinder.getAuthorityNames();
    }

    /**
     * Returns the factories to be used by {@link ManyAuthoritiesFactory}. If the registered
     * factories changed since the last time this method has been invoked, then this method
     * recreate the set.
     */
    //@Override
    synchronized Collection/*<AuthorityFactory>*/ getFactories() {
        final Collection/*<String>*/ authorities = ReferencingFactoryFinder.getAuthorityNames();
        if (authorities != authorityNames) {
            authorityNames = authorities;
            final Hints hints = getHints();
            final Set/*<AuthorityFactory>*/ factories = new LinkedHashSet();
            factories.addAll(ReferencingFactoryFinder.getCRSAuthorityFactories                (hints));
            factories.addAll(ReferencingFactoryFinder.getCSAuthorityFactories                 (hints));
            factories.addAll(ReferencingFactoryFinder.getDatumAuthorityFactories              (hints));
            factories.addAll(ReferencingFactoryFinder.getCoordinateOperationAuthorityFactories(hints));
            setFactories(factories);
        }
        return super.getFactories();
    }

    /**
     * Returns a factory for the specified authority and type.
     */
    //@Override
    final AuthorityFactory fromFactoryRegistry(final String authority,
                                               final Class/*<? extends AuthorityFactory>*/ type)
            throws FactoryRegistryException
    {
        if (CRSAuthorityFactory.class.equals(type)) {
            return ReferencingFactoryFinder.getCRSAuthorityFactory(authority, getHints());
        } else if (CSAuthorityFactory.class.equals(type)) {
            return ReferencingFactoryFinder.getCSAuthorityFactory(authority, getHints());
        } else if (DatumAuthorityFactory.class.equals(type)) {
            return ReferencingFactoryFinder.getDatumAuthorityFactory(authority, getHints());
        } else if (CoordinateOperationAuthorityFactory.class.equals(type)) {
            return ReferencingFactoryFinder.getCoordinateOperationAuthorityFactory(authority, getHints());
        } else {
            return super.fromFactoryRegistry(authority, type);
        }
    }

    /**
     * Returns a copy of the hints specified by the user at construction time.
     */
    private Hints getHints() {
        if (hints.isEmpty()) {
            return ReferencingFactoryFinder.EMPTY_HINTS;
        } else {
            // Clones EMPTY_HINTS as a trick for getting a StricHints instance.
            final Hints hints = (Hints) ReferencingFactoryFinder.EMPTY_HINTS.clone();
            hints.putAll(this.hints);
            return hints;
        }
    }

    /**
     * Returns a finder which can be used for looking up unidentified objects.
     * The default implementation delegates the lookups to the underlying factories.
     *
     * @since 2.4
     */
    //@Override
    public IdentifiedObjectFinder getIdentifiedObjectFinder(final Class/*<? extends IdentifiedObject>*/ type)
            throws FactoryException
    {
        return new Finder(this, type);
    }

    /**
     * A {@link IdentifiedObjectFinder} which tests every factories.
     */
    private static final class Finder extends ManyAuthoritiesFactory.Finder {
        /**
         * Creates a finder for the specified type.
         */
        protected Finder(final ManyAuthoritiesFactory factory,
                         final Class/*<? extends IdentifiedObject>*/ type)
        {
            super(factory, type);
        }

        /**
         * Returns all factories to try.
         */
        private Set/*<AuthorityFactory>*/ fromFactoryRegistry() {
            final ManyAuthoritiesFactory factory = (ManyAuthoritiesFactory) proxy.getAuthorityFactory();
            final Class/*<? extends AuthorityFactory>*/ type = proxy.getType();
            final Set factories = new LinkedHashSet();
            for (final Iterator it=ReferencingFactoryFinder.getAuthorityNames().iterator(); it.hasNext();) {
                final String authority = (String) it.next();
                factory.fromFactoryRegistry(authority, type, factories);
            }
            // Removes the factories already tried by super-class.
            final Collection done = getFactories();
            if (done != null) {
                factories.removeAll(done);
            }
            return factories;
        }

        /**
         * Lookups for the specified object.
         */
        //@Override
        public IdentifiedObject find(final IdentifiedObject object) throws FactoryException {
            IdentifiedObject candidate = super.find(object);
            if (candidate != null) {
                return candidate;
            }
            IdentifiedObjectFinder finder;
            final Iterator it = fromFactoryRegistry().iterator();
            while ((finder = next(it)) != null) {
                candidate = finder.find(object);
                if (candidate != null) {
                    break;
                }
            }
            return candidate;
        }

        /**
         * Returns the identifier of the specified object, or {@code null} if none.
         */
        //@Override
        public String findIdentifier(final IdentifiedObject object) throws FactoryException {
            String candidate = super.findIdentifier(object);
            if (candidate != null) {
                return candidate;
            }
            IdentifiedObjectFinder finder;
            final Iterator it = fromFactoryRegistry().iterator();
            while ((finder = next(it)) != null) {
                candidate = finder.findIdentifier(object);
                if (candidate != null) {
                    break;
                }
            }
            return candidate;
        }
    }
}
