/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
 *
 *    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;

import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.measure.unit.Unit;

import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.util.InternationalString;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;

import org.geotools.factory.Hints;
import org.geotools.factory.AbstractFactory;
import org.geotools.factory.OptionalFactory;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.CRS;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;


/**
 * An authority factory which delegates {@linkplain CoordinateReferenceSystem CRS},
 * {@linkplain CoordinateSystem CS} or {@linkplain Datum datum} objects creation to
 * some other factory implementations.
 * <p>
 * All constructors are protected because this class must be subclassed in order to determine
 * which of the {@link DatumAuthorityFactory}, {@link CSAuthorityFactory} and
 * {@link CRSAuthorityFactory} interfaces to implement.
 *
 * @since 2.2
 *
 * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/library/referencing/src/main/java/org/geotools/referencing/factory/AuthorityFactoryAdapter.java $
 * @version $Id: AuthorityFactoryAdapter.java 37299 2011-05-25 05:21:24Z mbedward $
 * @author Martin Desruisseaux (IRD)
 */
public class AuthorityFactoryAdapter extends AbstractAuthorityFactory implements OptionalFactory {

    /**
     * The underlying {@linkplain Datum datum} authority factory,
     * or {@code null} if none.
     */
    final DatumAuthorityFactory datumFactory;

    /**
     * The underlying {@linkplain CoordinateSystem coordinate system} authority factory,
     * or {@code null} if none.
     */
    final CSAuthorityFactory csFactory;

    /**
     * The underlying {@linkplain CoordinateReferenceSystem coordinate reference system}
     * authority factory, or {@code null} if none.
     */
    final CRSAuthorityFactory crsFactory;

    /**
     * The underlying {@linkplain CoordinateOperation coordinate operation} authority factory,
     * or {@code null} if none.
     */
    final CoordinateOperationAuthorityFactory operationFactory;

    /**
     * Creates a wrapper around no factory. This constructor should never be used except by
     * subclasses overriding the <code>get</code><var>Foo</var><code>AuthorityFactory</code>
     * methods.
     *
     * @param priority The priority for this factory, as a number between
     *        {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
     *        {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
     */
    AuthorityFactoryAdapter(final int priority) {
        super(priority);
        datumFactory     = null;
        csFactory        = null;
        crsFactory       = null;
        operationFactory = null;
    }

    /**
     * Creates a wrapper around the specified factory. The {@link #priority priority} field
     * will be set to the same value than the specified factory. Subclasses should override
     * the {@link #getPriority() getPriority()} method if they want to set a higher or lower
     * priority for this instance.
     *
     * @param factory The factory to wrap.
     */
    protected AuthorityFactoryAdapter(final AuthorityFactory factory) {
        this(factory, null);
    }

    /**
     * For {@link FallbackAuthorityFactory} constructor only.
     */
    AuthorityFactoryAdapter(final AuthorityFactory factory, final AuthorityFactory fallback) {
        this((factory  instanceof   CRSAuthorityFactory) ?   (CRSAuthorityFactory) factory  :
             (fallback instanceof   CRSAuthorityFactory) ?   (CRSAuthorityFactory) fallback : null,
             (factory  instanceof    CSAuthorityFactory) ?    (CSAuthorityFactory) factory  :
             (fallback instanceof    CSAuthorityFactory) ?    (CSAuthorityFactory) fallback : null,
             (factory  instanceof DatumAuthorityFactory) ? (DatumAuthorityFactory) factory  :
             (fallback instanceof DatumAuthorityFactory) ? (DatumAuthorityFactory) fallback : null,
             (factory  instanceof CoordinateOperationAuthorityFactory) ?
                    (CoordinateOperationAuthorityFactory) factory :
             (fallback instanceof CoordinateOperationAuthorityFactory) ?
                    (CoordinateOperationAuthorityFactory) fallback : null);
    }

    /**
     * Creates a wrapper around the specified factories. The {@link #priority priority} field will
     * be set to the highest priority found in the specified factories. Subclasses should override
     * the {@link #getPriority() getPriority()} method if they want to set a higher or lower
     * priority for this instance.
     *
     * @param crsFactory   The {@linkplain CoordinateReferenceSystem coordinate reference system}
     *                     authority factory, or {@code null}.
     * @param csFactory    The {@linkplain CoordinateSystem coordinate system} authority factory,
     *                     or {@code null}.
     * @param datumFactory The {@linkplain Datum datum} authority factory, or {@code null}.
     * @param opFactory    The {@linkplain CoordinateOperation coordinate operation} authority
     *                     factory, or {@code null}.
     */
    protected AuthorityFactoryAdapter(final CRSAuthorityFactory                crsFactory,
                                      final CSAuthorityFactory                  csFactory,
                                      final DatumAuthorityFactory            datumFactory,
                                      final CoordinateOperationAuthorityFactory opFactory)
    {
        super(Math.max(getPriority(datumFactory),
              Math.max(getPriority(   csFactory),
              Math.max(getPriority(  crsFactory),
                       getPriority(   opFactory)))));

        if (this instanceof CRSAuthorityFactory) {
            ensureNonNull("crsFactory", crsFactory);
        }
        if (this instanceof CSAuthorityFactory) {
            ensureNonNull("csFactory", csFactory);
        }
        if (this instanceof DatumAuthorityFactory) {
            ensureNonNull("datumFactory", datumFactory);
        }
        if (this instanceof CoordinateOperationAuthorityFactory) {
            ensureNonNull("opFactory", opFactory);
        }
        store(Hints.               DATUM_AUTHORITY_FACTORY, this.    datumFactory = datumFactory);
        store(Hints.                  CS_AUTHORITY_FACTORY, this.       csFactory =    csFactory);
        store(Hints.                 CRS_AUTHORITY_FACTORY, this.      crsFactory =   crsFactory);
        store(Hints.COORDINATE_OPERATION_AUTHORITY_FACTORY, this.operationFactory =    opFactory);
    }

    /**
     * Returns the priority of the specified factory, or {@link #NORMAL_PRIORITY} if unknown.
     */
    private static int getPriority(final AuthorityFactory factory) {
        return (factory instanceof AbstractFactory) ?
            ((AbstractFactory) factory).getPriority() : NORMAL_PRIORITY;
    }

    /**
     * Adds the specified factory to the set of hints, if non null.
     */
    private void store(final Hints.Key key, final AuthorityFactory factory) {
        if (factory != null) {
            if (hints.put(key, factory) != null) {
                // Should never happen since 'hints' should be initially empty.
                throw new AssertionError(key);
            }
        }
    }

    /**
     * Returns the direct dependencies. The returned list contains the backing store specified
     * at construction time, or the exception if the backing store can't be obtained.
     */
    @Override
    Collection<? super AuthorityFactory> dependencies() {
        final List<Object> dep = new ArrayList<Object>(4);
        Object factory;
        try {
            factory = getAuthorityFactory(null);
        } catch (FactoryException e) {
            factory = e;
        }
        dep.add(factory);
        return dep;
    }

    /**
     * If this factory is a wrapper for the specified factory that do not add any additional
     * {@linkplain #getAuthorityCodes authority codes}, returns {@code true}. This method is
     * for {@link FallbackAuthorityFactory} internal use only and should not be public. We
     * expect only a simple check, so we don't invoke the {@code getFooAuthorityFactory(...)}
     * methods.
     */
    @Override
    boolean sameAuthorityCodes(final AuthorityFactory factory) {
        if (!isCodeMethodOverriden()) {
            /*
             * Tests wrapped factories only if the 'toBackingFactoryCode(String)' method is not
             * overwritten, otherwise we can't assume that the authority codes are the same. The
             * impact on the main subclasses are usually as below:
             *
             *     URN_AuthorityFactory           - excluded
             *     HTTP_AuthorityFactory          - excluded
             *     OrderedAxisAuthorityFactory    - make the test below
             *     FallbackAuthorityFactory       - make the test below
             *
             * Note: in the particular case of FallbackAuthorityFactory, we test the
             *       primary factory only, not the fallback. This behavior matches the
             *       FallbackAuthorityFactory.create(boolean,int,Iterator) need, which
             *       will process this case in a special way.
             */
            if (sameAuthorityCodes(crsFactory,       factory) &&
                sameAuthorityCodes(csFactory,        factory) &&
                sameAuthorityCodes(datumFactory,     factory) &&
                sameAuthorityCodes(operationFactory, factory))
            {
                return true;
            }
        }
        return super.sameAuthorityCodes(factory);
    }

    /**
     * Helper methods for {@link #sameAuthorityCodes(AuthorityFactory)} and
     * {@link FallbackAuthorityFactory#create(boolean,int,Iterator)} implementations. If there is no
     * backing store, returns {@code true} in order to take in account only the backing stores that
     * are assigned. This behavior match the need of the above-cited implementations.
     */
    static boolean sameAuthorityCodes(final AuthorityFactory backingStore,
                                      final AuthorityFactory factory)
    {
        if (backingStore instanceof AbstractAuthorityFactory) {
            if (((AbstractAuthorityFactory) backingStore).sameAuthorityCodes(factory)) {
                return true;
            }
        }
        return (factory == backingStore) || (backingStore == null);
    }

    /**
     * Returns {@code true} if this factory is ready for use. This default implementation
     * checks the availability of CRS, CS, datum and operation authority factories specified
     * at construction time.
     *
     * @return {@code true} if this factory is ready for use.
     */
    @Override
    public boolean isAvailable() {
        return isAvailable(      crsFactory) &&
               isAvailable(       csFactory) &&
               isAvailable(    datumFactory) &&
               isAvailable(operationFactory);
    }

    /**
     * Checks the availability of the specified factory.
     */
    private static boolean isAvailable(final AuthorityFactory factory) {
        return !(factory instanceof OptionalFactory) || ((OptionalFactory) factory).isAvailable();
    }

    /**
     * Replaces the specified unit, if applicable.
     * To be overridden with {@code protected} access by {@link TransformedAuthorityFactory}.
     */
    Unit<?> replace(Unit<?> units) throws FactoryException {
        return units;
    }

    /**
     * Replaces (if needed) the specified axis by a new one.
     * To be overridden with {@code protected} access by {@link TransformedAuthorityFactory}.
     */
    CoordinateSystemAxis replace(CoordinateSystemAxis axis) throws FactoryException {
        return axis;
    }

    /**
     * Replaces (if needed) the specified coordinate system by a new one.
     * To be overridden with {@code protected} access by {@link TransformedAuthorityFactory}.
     */
    CoordinateSystem replace(CoordinateSystem cs) throws FactoryException {
        return cs;
    }

    /**
     * Replaces (if needed) the specified datum by a new one.
     * To be overridden with {@code protected} access by {@link TransformedAuthorityFactory}.
     */
    Datum replace(Datum datum) throws FactoryException {
        return datum;
    }

    /**
     * Replaces (if needed) the specified coordinate reference system.
     * To be overridden with {@code protected} access by {@link TransformedAuthorityFactory}.
     */
    CoordinateReferenceSystem replace(CoordinateReferenceSystem crs) throws FactoryException {
        return crs;
    }

    /**
     * Replaces (if needed) the specified coordinate operation.
     * To be overridden with {@code protected} access by {@link TransformedAuthorityFactory}.
     */
    CoordinateOperation replace(CoordinateOperation operation) throws FactoryException {
        return operation;
    }

    /**
     * Delegates the work to an appropriate {@code replace} method for the given object.
     */
    private IdentifiedObject replaceObject(final IdentifiedObject object) throws FactoryException {
        if (object instanceof CoordinateReferenceSystem) {
            return replace((CoordinateReferenceSystem) object);
        }
        if (object instanceof CoordinateSystem) {
            return replace((CoordinateSystem) object);
        }
        if (object instanceof CoordinateSystemAxis) {
            return replace((CoordinateSystemAxis) object);
        }
        if (object instanceof Datum) {
            return replace((Datum) object);
        }
        if (object instanceof CoordinateOperation) {
            return replace((CoordinateOperation) object);
        }
        return object;
    }

    /**
     * Returns one of the underlying factories as an instance of the GeoTools implementation. If
     * there is none of them, then returns {@code null} or throws an exception if {@code caller}
     * is not null.
     */
    private AbstractAuthorityFactory getGeotoolsFactory(final String caller, final String code)
            throws FactoryException
    {
        final AuthorityFactory candidate = getAuthorityFactory(code);
        if (candidate instanceof AbstractAuthorityFactory) {
            return (AbstractAuthorityFactory) candidate;
        }
        if (caller == null) {
            return null;
        }
        throw new FactoryException(Errors.format(
                    ErrorKeys.GEOTOOLS_EXTENSION_REQUIRED_$1, caller));
    }

    /**
     * Returns a description of the underlying backing store, or {@code null} if unknow.
     *
     * @throws FactoryException if a failure occured while fetching the engine description.
     */
    @Override
    public String getBackingStoreDescription() throws FactoryException {
        final AbstractAuthorityFactory factory = getGeotoolsFactory(null, null);
        return (factory != null) ? factory.getBackingStoreDescription() : null;
    }

    /**
     * Returns the vendor responsible for creating this factory implementation.
     */
    @Override
    public Citation getVendor() {
        return getAuthorityFactory().getVendor();
    }

    /**
     * Returns the organization or party responsible for definition and maintenance of the
     * database.
     */
    public Citation getAuthority() {
        return getAuthorityFactory().getAuthority();
    }

    /**
     * Returns the set of authority code for the specified type.
     *
     * @todo We should returns the union of authority codes from all underlying factories.
     */
    public Set<String> getAuthorityCodes(final Class<? extends IdentifiedObject> type)
            throws FactoryException
    {
        return getAuthorityFactory(null).getAuthorityCodes(type);
    }

    /**
     * Returns a description for the object identified by the specified code.
     */
    public InternationalString getDescriptionText(final String code) throws FactoryException {
        return getAuthorityFactory(code).getDescriptionText(toBackingFactoryCode(code));
    }

    /**
     * Returns an arbitrary object from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createCoordinateReferenceSystem
     * @see #createDatum
     * @see #createEllipsoid
     * @see #createUnit
     */
    @Override
    public IdentifiedObject createObject(final String code) throws FactoryException {
        return replaceObject(getAuthorityFactory(code).createObject(toBackingFactoryCode(code)));
    }

    /**
     * Returns an arbitrary {@linkplain Datum datum} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createGeodeticDatum
     * @see #createVerticalDatum
     * @see #createTemporalDatum
     */
    @Override
    public Datum createDatum(final String code) throws FactoryException {
        return replace(getDatumAuthorityFactory(code).createDatum(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain EngineeringDatum engineering datum} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createEngineeringCRS
     */
    @Override
    public EngineeringDatum createEngineeringDatum(final String code) throws FactoryException {
        return (EngineeringDatum) replace(getDatumAuthorityFactory(code).
                createEngineeringDatum(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain ImageDatum image datum} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createImageCRS
     */
    @Override
    public ImageDatum createImageDatum(final String code) throws FactoryException {
        return (ImageDatum) replace(getDatumAuthorityFactory(code).
                createImageDatum(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain VerticalDatum vertical datum} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createVerticalCRS
     */
    @Override
    public VerticalDatum createVerticalDatum(final String code) throws FactoryException {
        return (VerticalDatum) replace(getDatumAuthorityFactory(code).
                createVerticalDatum(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain TemporalDatum temporal datum} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createTemporalCRS
     */
    @Override
    public TemporalDatum createTemporalDatum(final String code) throws FactoryException {
        return (TemporalDatum) replace(getDatumAuthorityFactory(code).
                createTemporalDatum(toBackingFactoryCode(code)));
    }

    /**
     * Returns a {@linkplain GeodeticDatum geodetic datum} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createEllipsoid
     * @see #createPrimeMeridian
     * @see #createGeographicCRS
     * @see #createProjectedCRS
     */
    @Override
    public GeodeticDatum createGeodeticDatum(final String code) throws FactoryException {
        return (GeodeticDatum) replace(getDatumAuthorityFactory(code).
                createGeodeticDatum(toBackingFactoryCode(code)));
    }

    /**
     * Returns an {@linkplain Ellipsoid ellipsoid} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createGeodeticDatum
     */
    @Override
    public Ellipsoid createEllipsoid(final String code) throws FactoryException {
        return getDatumAuthorityFactory(code).createEllipsoid(toBackingFactoryCode(code));
    }

    /**
     * Returns a {@linkplain PrimeMeridian prime meridian} from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createGeodeticDatum
     */
    @Override
    public PrimeMeridian createPrimeMeridian(final String code) throws FactoryException {
        return getDatumAuthorityFactory(code).createPrimeMeridian(toBackingFactoryCode(code));
    }

    /**
     * Returns a {@linkplain Extent extent} (usually an area of validity) from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public Extent createExtent(final String code) throws FactoryException {
        return getGeotoolsFactory("createExtent", code).createExtent(toBackingFactoryCode(code));
    }

    /**
     * Returns an arbitrary {@linkplain CoordinateSystem coordinate system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public CoordinateSystem createCoordinateSystem(final String code) throws FactoryException {
        return replace(getCSAuthorityFactory(code).
                createCoordinateSystem(toBackingFactoryCode(code)));
    }

    /**
     * Creates a cartesian coordinate system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public CartesianCS createCartesianCS(final String code) throws FactoryException {
        return (CartesianCS) replace(getCSAuthorityFactory(code).
                createCartesianCS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a polar coordinate system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public PolarCS createPolarCS(final String code) throws FactoryException {
        return (PolarCS) replace(getCSAuthorityFactory(code).
                createPolarCS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a cylindrical coordinate system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public CylindricalCS createCylindricalCS(final String code) throws FactoryException {
        return (CylindricalCS) replace(getCSAuthorityFactory(code).
                createCylindricalCS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a spherical coordinate system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public SphericalCS createSphericalCS(final String code) throws FactoryException {
        return (SphericalCS) replace(getCSAuthorityFactory(code).
                createSphericalCS(toBackingFactoryCode(code)));
    }

    /**
     * Creates an ellipsoidal coordinate system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public EllipsoidalCS createEllipsoidalCS(final String code) throws FactoryException {
        return (EllipsoidalCS) replace(getCSAuthorityFactory(code).
                createEllipsoidalCS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a vertical coordinate system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public VerticalCS createVerticalCS(final String code) throws FactoryException {
        return (VerticalCS) replace(getCSAuthorityFactory(code).
                createVerticalCS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a temporal coordinate system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public TimeCS createTimeCS(final String code) throws FactoryException {
        return (TimeCS) replace(getCSAuthorityFactory(code).
                createTimeCS(toBackingFactoryCode(code)));
    }

    /**
     * Returns a {@linkplain CoordinateSystemAxis coordinate system axis} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public CoordinateSystemAxis createCoordinateSystemAxis(final String code)
            throws FactoryException
    {
        return replace(getCSAuthorityFactory(code).
                createCoordinateSystemAxis(toBackingFactoryCode(code)));
    }

    /**
     * Returns an {@linkplain Unit unit} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public Unit<?> createUnit(final String code) throws FactoryException {
        return replace(getCSAuthorityFactory(code).
                createUnit(toBackingFactoryCode(code)));
    }

    /**
     * Returns an arbitrary {@linkplain CoordinateReferenceSystem coordinate reference system}
     * from a code.
     *
     * @throws FactoryException if the object creation failed.
     *
     * @see #createGeographicCRS
     * @see #createProjectedCRS
     * @see #createVerticalCRS
     * @see #createTemporalCRS
     * @see #createCompoundCRS
     */
    @Override
    public CoordinateReferenceSystem createCoordinateReferenceSystem(final String code)
            throws FactoryException
    {
        return replace(getCRSAuthorityFactory(code).
                createCoordinateReferenceSystem(toBackingFactoryCode(code)));
    }

    /**
     * Creates a 3D coordinate reference system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public CompoundCRS createCompoundCRS(final String code) throws FactoryException {
        return (CompoundCRS) replace(getCRSAuthorityFactory(code).
                createCompoundCRS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a derived coordinate reference system from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public DerivedCRS createDerivedCRS(final String code) throws FactoryException {
        return (DerivedCRS) replace(getCRSAuthorityFactory(code).
                createDerivedCRS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain EngineeringCRS engineering coordinate reference system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public EngineeringCRS createEngineeringCRS(final String code) throws FactoryException {
        return (EngineeringCRS) replace(getCRSAuthorityFactory(code).
                createEngineeringCRS(toBackingFactoryCode(code)));
    }

    /**
     * Returns a {@linkplain GeographicCRS geographic coordinate reference system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public GeographicCRS createGeographicCRS(final String code) throws FactoryException {
        return (GeographicCRS) replace(getCRSAuthorityFactory(code).
                createGeographicCRS(toBackingFactoryCode(code)));
    }

    /**
     * Returns a {@linkplain GeocentricCRS geocentric coordinate reference system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public GeocentricCRS createGeocentricCRS(final String code) throws FactoryException {
        return (GeocentricCRS) replace(getCRSAuthorityFactory(code).
                createGeocentricCRS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain ImageCRS image coordinate reference system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public ImageCRS createImageCRS(final String code) throws FactoryException {
        return (ImageCRS) replace(getCRSAuthorityFactory(code).
                createImageCRS(toBackingFactoryCode(code)));
    }

    /**
     * Returns a {@linkplain ProjectedCRS projected coordinate reference system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public ProjectedCRS createProjectedCRS(final String code) throws FactoryException {
        return (ProjectedCRS) replace(getCRSAuthorityFactory(code).
                createProjectedCRS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain TemporalCRS temporal coordinate reference system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public TemporalCRS createTemporalCRS(final String code) throws FactoryException {
        return (TemporalCRS) replace(getCRSAuthorityFactory(code).
                createTemporalCRS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a {@linkplain VerticalCRS vertical coordinate reference system} from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public VerticalCRS createVerticalCRS(final String code) throws FactoryException {
        return (VerticalCRS) replace(getCRSAuthorityFactory(code).
                createVerticalCRS(toBackingFactoryCode(code)));
    }

    /**
     * Creates a parameter descriptor from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public ParameterDescriptor createParameterDescriptor(final String code) throws FactoryException {
        return getGeotoolsFactory("createParameterDescriptor", code).
                createParameterDescriptor(toBackingFactoryCode(code));
    }

    /**
     * Creates an operation method from a code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public OperationMethod createOperationMethod(final String code) throws FactoryException {
        return getGeotoolsFactory("createOperationMethod", code).
                createOperationMethod(toBackingFactoryCode(code));
    }

    /**
     * Creates an operation from a single operation code.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public CoordinateOperation createCoordinateOperation(final String code) throws FactoryException {
        return replace(getCoordinateOperationAuthorityFactory(code).
                createCoordinateOperation(toBackingFactoryCode(code)));
    }

    /**
     * Creates an operation from coordinate reference system codes.
     *
     * @throws FactoryException if the object creation failed.
     */
    @Override
    public Set<CoordinateOperation> createFromCoordinateReferenceSystemCodes(
            final String sourceCRS, final String targetCRS) throws FactoryException
    {
        final CoordinateOperationAuthorityFactory factory, check;
        factory = getCoordinateOperationAuthorityFactory(sourceCRS);
        check   = getCoordinateOperationAuthorityFactory(targetCRS);
        if (factory != check) {
            /*
             * No coordinate operation because of mismatched factories. This is not
             * illegal - the result is an empty set - but it is worth to notify the
             * user since this case has some chances to be an user error.
             */
            final LogRecord record = Loggings.format(Level.WARNING,
                    LoggingKeys.MISMATCHED_COORDINATE_OPERATION_FACTORIES_$2, sourceCRS, targetCRS);
            record.setSourceMethodName("createFromCoordinateReferenceSystemCodes");
            record.setSourceClassName(AuthorityFactoryAdapter.class.getName());
            record.setLoggerName(LOGGER.getName());
            LOGGER.log(record);
            return Collections.emptySet();
        }
        return factory.createFromCoordinateReferenceSystemCodes(
                toBackingFactoryCode(sourceCRS), toBackingFactoryCode(targetCRS));
    }

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

    /**
     * A {@link IdentifiedObjectFinder} which tests
     * {@linkplain AuthorityFactoryAdapter#replaceObject modified objects}
     * in addition of original object.
     */
    class Finder extends IdentifiedObjectFinder.Adapter {
        /**
         * Creates a finder for the underlying backing store.
         */
        protected Finder(final Class<? extends IdentifiedObject> type) throws FactoryException {
            super(getGeotoolsFactory("getIdentifiedObjectFinder", null).getIdentifiedObjectFinder(type));
        }

        /**
         * Returns {@code candidate}, or an object derived from {@code candidate}, if it is
         * {@linkplain CRS#equalsIgnoreMetadata equals ignoring metadata} to the specified
         * model. Otherwise returns {@code null}.
         *
         * @throws FactoryException if an error occured while creating a derived object.
         */
        @Override
        protected IdentifiedObject deriveEquivalent(final IdentifiedObject candidate,
                                                    final IdentifiedObject model)
                throws FactoryException
        {
            final IdentifiedObject modified = replaceObject(candidate);
            if (modified != candidate) {
                if (CRS.equalsIgnoreMetadata(modified, model)) {
                    return modified;
                }
            }
            return super.deriveEquivalent(candidate, model);
        }
    }

    /**
     * Creates an exception for a missing factory. We actually returns an instance of
     * {@link NoSuchAuthorityCodeException} because this kind of exception is treated
     * especially by {@link FallbackAuthorityFactory}.
     */
    private FactoryException missingFactory(final Class category, final String code) {
        return new NoSuchAuthorityCodeException(Errors.format(ErrorKeys.FACTORY_NOT_FOUND_$1,
                category), Citations.getIdentifier(getAuthority()), trimAuthority(code));
    }

    /**
     * For internal use by {@link #getAuthority} and {@link #getVendor} only. Its only purpose
     * is to catch the {@link FactoryException} for methods that don't allow it. The protected
     * method should be used instead when this exception is allowed.
     */
    private AuthorityFactory getAuthorityFactory() {
        try {
            return getAuthorityFactory(null);
        } catch (FactoryException cause) {
            throw new IllegalStateException(Errors.format(ErrorKeys.UNDEFINED_PROPERTY), cause);
        }
    }

    /**
     * Returns an authority factory of the specified type. This method delegates to:
     * <ul>
     *   <li>{@link #getCRSAuthorityFactory} if {@code type} is
     *       {@code CRSAuthorityFactory.class};</li>
     *   <li>{@link #getCSAuthorityFactory} if {@code type} is
     *       {@code CSAuthorityFactory.class};</li>
     *   <li>{@link #getDatumAuthorityFactory} if {@code type} is
     *       {@code DatumAuthorityFactory.class};</li>
     *   <li>{@link #CoordinateOperationAuthorityFactory} if {@code type} is
     *       {@code CoordinateOperationAuthorityFactory.class};</li>
     * </ul>
     *
     * @throws IllegalArgumentException if the specified {@code type} is invalid.
     * @throws FactoryException if no suitable factory were found.
     */
    @SuppressWarnings("unchecked")
    <T extends AuthorityFactory> T getAuthorityFactory(final Class<T> type, final String code)
            throws FactoryException
    {
        final AuthorityFactory f;
        if (CRSAuthorityFactory.class.equals(type)) {
            f = getCRSAuthorityFactory(code);
        } else if (CSAuthorityFactory.class.equals(type)) {
            f = getCSAuthorityFactory(code);
        } else if (DatumAuthorityFactory.class.equals(type)) {
            f = getDatumAuthorityFactory(code);
        } else if (CoordinateOperationAuthorityFactory.class.equals(type)) {
            f = getCoordinateOperationAuthorityFactory(code);
        } else {
            throw new IllegalArgumentException(Errors.format(
                    ErrorKeys.ILLEGAL_ARGUMENT_$2, "type", type));
        }
        return type.cast(f);
    }

    /**
     * Returns a generic object factory to use for the specified code. The default implementation
     * returns one of the factory specified at construction time. Subclasses can override
     * this method in order to select a different factory implementation depending on the
     * code value.
     * <p>
     * <strong>Note:</strong> The value of the {@code code} argument given to this
     * method may be {@code null} when a factory is needed for some global task,
     * like {@link #getAuthorityCodes} method execution.
     *
     * @param  code The authority code given to this class. Note that the code to be given
     *         to the returned factory {@linkplain #toBackingFactoryCode may be different}.
     * @return A factory for the specified authority code (never {@code null}).
     * @throws FactoryException if no suitable factory were found.
     *
     * @since 2.4
     */
    protected AuthorityFactory getAuthorityFactory(final String code) throws FactoryException {
        if (      crsFactory != null) return       crsFactory;
        if (       csFactory != null) return        csFactory;
        if (    datumFactory != null) return     datumFactory;
        if (operationFactory != null) return operationFactory;
        throw missingFactory(AuthorityFactory.class, code);
    }

    /**
     * Returns the datum factory to use for the specified code. The default implementation
     * always returns the factory specified at construction time. Subclasses can override
     * this method in order to select a different factory implementation depending on the
     * code value.
     *
     * @param  code The authority code given to this class. Note that the code to be given
     *         to the returned factory {@linkplain #toBackingFactoryCode may be different}.
     * @return A factory for the specified authority code (never {@code null}).
     * @throws FactoryException if no datum factory were specified at construction time.
     *
     * @since 2.4
     */
    protected DatumAuthorityFactory getDatumAuthorityFactory(final String code)
            throws FactoryException
    {
        if (datumFactory == null) {
            throw missingFactory(DatumAuthorityFactory.class, code);
        }
        return datumFactory;
    }

    /**
     * Returns the coordinate system factory to use for the specified code. The default
     * implementation always returns the factory specified at construction time. Subclasses
     * can override this method in order to select a different factory implementation
     * depending on the code value.
     *
     * @param  code The authority code given to this class. Note that the code to be given
     *         to the returned factory {@linkplain #toBackingFactoryCode may be different}.
     * @return A factory for the specified authority code (never {@code null}).
     * @throws FactoryException if no coordinate system factory were specified at construction time.
     *
     * @since 2.4
     */
    protected CSAuthorityFactory getCSAuthorityFactory(final String code)
            throws FactoryException
    {
        if (csFactory == null) {
            throw missingFactory(CSAuthorityFactory.class, code);
        }
        return csFactory;
    }

    /**
     * Returns the coordinate reference system factory to use for the specified code. The default
     * implementation always returns the factory specified at construction time. Subclasses can
     * override this method in order to select a different factory implementation depending on
     * the code value.
     *
     * @param  code The authority code given to this class. Note that the code to be given
     *         to the returned factory {@linkplain #toBackingFactoryCode may be different}.
     * @return A factory for the specified authority code (never {@code null}).
     * @throws FactoryException if no coordinate reference system factory were specified
     *         at construction time.
     *
     * @since 2.4
     */
    protected CRSAuthorityFactory getCRSAuthorityFactory(final String code)
            throws FactoryException
    {
        if (crsFactory == null) {
            throw missingFactory(CRSAuthorityFactory.class, code);
        }
        return crsFactory;
    }

    /**
     * Returns the coordinate operation factory to use for the specified code. The default
     * implementation always returns the factory specified at construction time. Subclasses can
     * override this method in order to select a different factory implementation depending on
     * the code value.
     *
     * @param  code The authority code given to this class. Note that the code to be given
     *         to the returned factory {@linkplain #toBackingFactoryCode may be different}.
     * @return A factory for the specified authority code (never {@code null}).
     * @throws FactoryException if no coordinate operation factory were specified
     *         at construction time.
     *
     * @since 2.4
     */
    protected CoordinateOperationAuthorityFactory getCoordinateOperationAuthorityFactory(final String code)
            throws FactoryException
    {
        if (operationFactory == null) {
            throw missingFactory(CoordinateOperationAuthorityFactory.class, code);
        }
        return operationFactory;
    }

    /**
     * Returns the code to be given to the wrapped factories. This method is automatically
     * invoked by all {@code create} methods before to forward the code to the
     * {@linkplain #getCRSAuthorityFactory CRS}, {@linkplain #getCSAuthorityFactory CS},
     * {@linkplain #getDatumAuthorityFactory datum} or {@linkplain #operationFactory operation}
     * factory. The default implementation returns the {@code code} unchanged.
     *
     * @param  code The code given to this factory.
     * @return The code to give to the underlying factories.
     * @throws FactoryException if the code can't be converted.
     *
     * @since 2.4
     */
    protected String toBackingFactoryCode(final String code) throws FactoryException {
        return code;
    }

    /**
     * Returns {@code true} if the {@link #toBackingFactoryCode} method is overriden.
     */
    final boolean isCodeMethodOverriden() {
        final Class<?>[] arguments = new Class[] {String.class};
        for (Class<?> type=getClass(); !AuthorityFactoryAdapter.class.equals(type); type=type.getSuperclass()) {
            try {
                type.getDeclaredMethod("toBackingFactoryCode", arguments);
            } catch (NoSuchMethodException e) {
                // The method is not overriden in this class.
                // Checks in the super-class.
                continue;
            } catch (SecurityException e) {
                // We are not allowed to get this information.
                // Conservatively assumes that the method is overriden.
            }
            return true;
        }
        return false;
    }
    
    @Override
    public void dispose() throws FactoryException {
        super.dispose();
        disposeAbstractAuthorityFactory(datumFactory);
        disposeAbstractAuthorityFactory(csFactory);
        disposeAbstractAuthorityFactory(crsFactory);
        disposeAbstractAuthorityFactory(operationFactory);
    }
    
    private void disposeAbstractAuthorityFactory(Object factory) throws FactoryException {
        if(factory instanceof AbstractAuthorityFactory) {
            ((AbstractAuthorityFactory) factory).dispose();
        }
    }
}
