/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sis.metadata.internal.shared;

import java.util.Map;
import java.util.HashMap;
import java.lang.reflect.Field;
import static java.util.logging.Logger.getLogger;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.system.Modules;


/**
 * Constants for axis names specified by ISO 19111 and ISO 19162.
 * Contains also some extensions used in astronomy.
 *
 * @author  Martin Desruisseaux (Geomatys)
 */
public final class AxisNames {
    /**
     * The ISO 19162 <q>Longitude</q> name.
     */
    public static final String LONGITUDE = "Longitude";

    /**
     * The ISO 19162 <q>Latitude</q> name.
     */
    public static final String LATITUDE = "Latitude";

    /**
     * The ISO 19111 <q>geodetic longitude</q> name. Abbreviation is "λ" (lambda).
     */
    public static final String GEODETIC_LONGITUDE = "Geodetic longitude";

    /**
     * The ISO 19111 <q>geodetic latitude</q> name. Abbreviation is "φ" (phi).
     */
    public static final String GEODETIC_LATITUDE = "Geodetic latitude";

    /**
     * The <q>planetodetic longitude</q> name used in astronomy.
     */
    public static final String PLANETODETIC_LONGITUDE = "Planetodetic longitude";

    /**
     * The <q>planetodetic latitude</q> name used in astronomy.
     */
    public static final String PLANETODETIC_LATITUDE = "Planetodetic latitude";

    /**
     * The ISO 19111 <q>spherical longitude</q> name, also called <q>azimuthal angle</q>.
     * Abbreviation used by SIS is "θ" (theta) for consistency with ISO 19111 and ISO 19162
     * <i>Axis name and abbreviation</i> section, but some other conventions use φ or Ω instead.
     * Direction in the EPSG database is "East", but "counterClockwise" may also be used.
     *
     * <p>Axis name <q>Geocentric longitude</q> can also be used.
     * In astronomy, <q>Planetocentric longitude</q> is sometimes used.</p>
     *
     * @see <a href="https://en.wikipedia.org/wiki/Spherical_coordinate_system">Spherical coordinate system on Wikipedia</a>
     * @see <a href="https://mathworld.wolfram.com/SphericalCoordinates.html">Spherical coordinate system on MathWorld</a>
     */
    public static final String SPHERICAL_LONGITUDE = "Spherical longitude";

    /**
     * The ISO 19111 <q>spherical latitude</q> name, also called <q>polar (zenith) angle</q>.
     * Abbreviation used by SIS is "Ω" (omega) for consistency with ISO 19111
     * <i>Axis name and abbreviation</i> section, but some other conventions use θ, Ω or Ψ instead.
     * Direction in the EPSG database is "North", but the "Up" direction may also be used with a similar
     * axis named "elevation".
     *
     * <p>Axis name <q>Geocentric latitude</q> can also be used.
     * In astronomy, <q>Planetocentric latitude</q> is sometimes used.</p>
     */
    public static final String SPHERICAL_LATITUDE = "Spherical latitude";

    /**
     * The ISO 19111 <q>ellipsoidal height</q> name. Abbreviation is lower case <q>h</q>.
     */
    public static final String ELLIPSOIDAL_HEIGHT = "Ellipsoidal height";

    /**
     * The ISO 19111 <q>gravity-related height</q> name. Abbreviation is upper case <q>H</q>.
     */
    public static final String GRAVITY_RELATED_HEIGHT = "Gravity-related height";

    /**
     * The ISO 19111 <q>depth</q> name.
     */
    public static final String DEPTH = "Depth";

    /**
     * The ISO 19111 <q>Geocentric X</q> name. Abbreviation is upper case <q>X</q>.
     */
    public static final String GEOCENTRIC_X = "Geocentric X";

    /**
     * The ISO 19111 <q>Geocentric Y</q> name. Abbreviation is upper case <q>Y</q>.
     */
    public static final String GEOCENTRIC_Y = "Geocentric Y";

    /**
     * The ISO 19111 <q>Geocentric Z</q> name. Abbreviation is upper case <q>Z</q>.
     */
    public static final String GEOCENTRIC_Z = "Geocentric Z";

    /**
     * The ISO 19111 <q>geocentric radius</q> name. Abbreviation is lower case <q>r</q>
     * for consistency with ISO 19111.
     *
     * <div class="note"><b>Note:</b>Lower case <q>r</q> is also used for non-geocentric radius
     * or axes named "distance" with "awayFrom" direction.</div>
     */
    public static final String GEOCENTRIC_RADIUS = "Geocentric radius";

    /**
     * The ISO 19111 <q>easting</q> name. Abbreviation is upper case <q>E</q>.
     */
    public static final String EASTING = "Easting";

    /**
     * The ISO 19111 <q>westing</q> name. Abbreviation is upper case <q>W</q>.
     */
    public static final String WESTING = "Westing";

    /**
     * The ISO 19111 <q>northing</q> name. Abbreviation is upper case <q>N</q>.
     */
    public static final String NORTHING = "Northing";

    /**
     * The ISO 19111 <q>southing</q> name. Abbreviation is upper case <q>S</q>.
     */
    public static final String SOUTHING = "Southing";

    /**
     * A ISO 19162 frequently used name.
     */
    public static final String TIME = "Time";

    /**
     * The map of all of the above values, used for fixing the case.
     * Shall not be modified after construction.
     */
    private static final Map<String,String> VALUES;
    static {
        final Map<String,String> values = new HashMap<>(22);    // 18 elements + 25%, rounded to highest integer.
        final StringBuilder buffer = new StringBuilder(22);     // Length of the longuest string: "Gravity-related height"
        try {
            for (final Field f : AxisNames.class.getFields()) {
                final String name = (String) f.get(null);
                values.put(toUpperCase(name, buffer).intern(), name);
                buffer.setLength(0);
                /*
                 * The call to 'intern()' is because many upper-case strings match the field name
                 * (e.g. "LATITUDE", "NORTHING", "DEPTH", etc.), so we use the same String instance.
                 */
            }
        } catch (IllegalAccessException e) {
            /*
             * Should never happen. But if it happen anyway, do not kill the application for that.
             * We will take the values that we have been able to map so far. The other values will
             * just not have their case fixed.
             */
            Logging.unexpectedException(getLogger(Modules.REFERENCING), AxisNames.class, "<cinit>", e);
        }
        VALUES = values;
    }

    /**
     * Do not allow instantiation of this class.
     */
    private AxisNames() {
    }

    /**
     * Returns the given axis name in upper case without punctuation characters.
     *
     * @param  name    the axis name to return in upper-case.
     * @param  buffer  a temporary buffer to use. Must be initially empty.
     * @return the given name converted to upper-case.
     */
    private static String toUpperCase(final String name, final StringBuilder buffer) {
        for (int i=0; i<name.length(); i++) {
            final char c = name.charAt(i);
            if (Character.isLetterOrDigit(c)) {
                buffer.append(Character.toUpperCase(c));
            }
        }
        return buffer.toString();
    }

    /**
     * Returns the given name in camel case if it is one of the know names.
     * This method canonicalizes also the use of {@code '_'}, {@code '-'} and {@code ' '}.
     *
     * <h4>Rational</h4>
     * Axis names are not really free text. They are specified by ISO 19111 and ISO 19162.
     * SIS does not put restriction on axis names, but we nevertheless try to use a unique
     * name when we recognize it.
     *
     * @param  name  the name in any case.
     * @return the given name in camel case.
     */
    public static String toCamelCase(final String name) {
        return VALUES.getOrDefault(toUpperCase(name, new StringBuilder(name.length())), name);
    }
}
