/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2006, GeoTools Project Managment Committee (PMC)
 *    (C) 2006, Geomatys
 *
 *    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.Iterator;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.LogRecord;

// OpenGIS dependencies
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.cs.CSAuthorityFactory;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.datum.DatumAuthorityFactory;

// Geotools dependencies
import org.geotools.util.Version;
import org.geotools.factory.Hints;
import org.geotools.factory.Factory;
import org.geotools.resources.Utilities;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Logging;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.metadata.iso.citation.CitationImpl;


/**
 * Split a URN into its {@link #type} and {@link #version} parts for {@link URN_AuthorityFactory}.
 * This class must be immutable in order to avoid the need for synchronization in the authority
 * factory.
 *
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/URN_Parser.java $
 * @version $Id: URN_Parser.java 24435 2007-02-16 03:32:10Z desruisseaux $
 * @author Martin Desruisseaux
 */
final class URN_Parser {
    /**
     * The begining parts of the URN, typically {@code "urn:ogc:def:"} and {@code "urn:x-ogc:def:"}.
     * All elements in the array are treated as synonymous. Those parts are up to, but do not
     * include, de type part ({@code "crs"}, {@code "cs"}, {@code "datum"}, <cite>etc.</cite>).
     * They must include a trailing (@value #SEPARATOR} character.
     */
    private static final String[] URN_BASES = new String[] {
        "urn:ogc:def:",
        "urn:x-ogc:def:"
    };

    /**
     * The parts separator in the URN.
     */
    private static final char SEPARATOR = ':';

    /**
     * The parsed code as full URN.
     */
    public final String urn;

    /**
     * The type part of the URN ({@code "crs"}, {@code "cs"}, {@code "datum"}, <cite>etc</cite>).
     */
    public final URN_Type type;

    /**
     * The authority part of the URN (typically {@code "EPSG"}).
     */
    public final String authority;

    /**
     * The version part of the URN, or {@code null} if none.
     */
    public final Version version;

    /**
     * The code part of the URN.
     */
    public final String code;

    /**
     * Parses the specified URN.
     *
     * @param urn The URN to parse.
     * @throws NoSuchAuthorityCodeException if the URN syntax is invalid.
     *
     * @todo Implementation should be replaced by some mechanism using {@code GenericName}
     *       (at least the call to {@code String.regionMatches}) otherwise this method will
     *       fails if there is spaces around the separator.
     */
    public URN_Parser(final String urn) throws NoSuchAuthorityCodeException {
        this.urn = urn;
        final String code = urn.trim();
        String type = urn; // To be really assigned later.
        for (int i=0; i<URN_BASES.length; i++) {
            final String urnBase = URN_BASES[i];
            final int typeStart = urnBase.length();
            if (code.regionMatches(true, 0, urnBase, 0, typeStart)) {
                final int typeEnd = code.indexOf(SEPARATOR, typeStart);
                if (typeEnd >= 0) {
                    type = code.substring(typeStart, typeEnd).trim();
                    final URN_Type candidate = URN_Type.get(type);
                    if (candidate != null) {
                        final int nameEnd = code.indexOf(SEPARATOR, typeEnd + 1);
                        if (nameEnd >= 0) {
                            final int lastEnd = code.lastIndexOf(SEPARATOR);
                            this.version   = (lastEnd <= nameEnd) ? null
                                           : new Version(code.substring(nameEnd + 1, lastEnd));
                            this.authority = code.substring(typeEnd + 1, nameEnd).trim();
                            this.code      = code.substring(lastEnd + 1).trim();
                            this.type      = candidate;
                            return;
                        }
                    }
                }
            }
        }
        throw new NoSuchAuthorityCodeException(
                Errors.format(ErrorKeys.ILLEGAL_IDENTIFIER_$1, type), "urn:ogc:def", type);
    }

    /**
     * Returns the concatenation of the {@linkplain #authority} and the {@linkplain #code}.
     */
    public String getAuthorityCode() {
        return authority + SEPARATOR + code;
    }

    /**
     * Checks if the type is compatible with the expected one. This method is used as a safety
     * by {@code getFooAuthorityFactory(String)} methods in {@link URN_AuthorityFactory}. If a
     * mismatch is found, a warning is logged but no exception is thrown since it doesn't prevent
     * the class to work in a predicable way. It is just an indication for the user that his URN
     * may be wrong.
     */
    final void logWarningIfTypeMismatch(final Class/*<? extends AuthorityFactory>*/ expected) {
        if (!expected.isAssignableFrom(type.type)) {
            // Build a simplified URN, omitting "urn:ogc:def" and version number.
            final String urn = "..." + SEPARATOR + type + SEPARATOR + authority + SEPARATOR + code;
            final LogRecord record = Logging.format(Level.WARNING,
                    LoggingKeys.MISMATCHED_URN_TYPE_$1, urn);
            // Set the source to the public or protected method.
            record.setSourceClassName(URN_AuthorityFactory.class.getName());
            record.setSourceMethodName("get" + Utilities.getShortName(expected));
            AbstractAuthorityFactory.LOGGER.log(record);
        }
    }

    /**
     * Returns the URN.
     */
    public String toString() {
        return urn;
    }
}
