/*
 * 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.util;

import java.util.Locale;
import java.util.Formatter;
import java.util.Formattable;
import java.util.FormattableFlags;
import org.opengis.util.InternationalString;
import org.apache.sis.util.internal.shared.Strings;


/**
 * Base class for character strings that has been internationalized into several locales.
 * The {@link InternationalString} interface is used instead of the {@link String} class
 * whenever an attribute needs to be internationalization capable.
 *
 * <p>The default value (as returned by {@link #toString()} and other {@link CharSequence} methods)
 * is the string in the current {@linkplain Locale#getDefault() system-wide default locale}.
 * The {@linkplain Comparable natural ordering} is defined by the value returned by {@link #toString()}.</p>
 *
 * <h2>Substituting a free text by a code list</h2>
 * The ISO standard allows to substitute some character strings in the <q>free text</q> domain
 * by a {@link org.opengis.util.CodeList} value. This can be done with:
 *
 * <ul>
 *   <li>{@link org.apache.sis.util.iso.Types#getCodeTitle(CodeList)} for getting
 *       the {@link InternationalString} instance to store in a metadata property.</li>
 *   <li>{@link org.apache.sis.util.iso.Types#forCodeTitle(CharSequence)} for retrieving the
 *       {@link org.opengis.util.CodeList} previously stored as an {@code InternationalString}.</li>
 * </ul>
 *
 * @author  Martin Desruisseaux (IRD, Geomatys)
 * @version 1.1
 * @since   1.1
 */
public abstract class AbstractInternationalString implements InternationalString, Formattable {
    /**
     * The string in the {@linkplain Locale#getDefault() system default} locale, or {@code null}
     * if this string has not yet been determined. This is the default string returned by
     * {@link #toString()} and others methods from the {@link CharSequence} interface.
     *
     * <h4>Thread safety</h4>
     * For thread safety this field shall either be read and written in a synchronized block,
     * or be fixed at construction time and never changed after than point. All other usages
     * are prohibited.
     *
     * <h4>Serialization</h4>
     * This field is not serialized because serialization is often used for data transmission
     * between a server and a client, and the client may not use the same locale as the server.
     * We want the locale to be examined again on the client side.
     */
    transient String defaultValue;

    /**
     * Constructs an international string.
     */
    protected AbstractInternationalString() {
    }

    /**
     * Returns the length of the string in the {@linkplain Locale#getDefault() default locale}.
     * This is the length of the string returned by {@link #toString()}.
     *
     * @return length of the string in the default locale.
     */
    @Override
    public int length() {
        return CharSequences.length(toString());
    }

    /**
     * Returns the character of the string in the {@linkplain Locale#getDefault() default locale}
     * at the specified index. This is a character of the string returned by {@link #toString()}.
     *
     * @param  index  the index of the character.
     * @return the character at the specified index.
     * @throws IndexOutOfBoundsException if the specified index is out of bounds.
     */
    @Override
    public char charAt(final int index) throws IndexOutOfBoundsException {
        return toString().charAt(index);
    }

    /**
     * Returns a subsequence of the string in the {@linkplain Locale#getDefault() default locale}.
     * The subsequence is a {@link String} object starting with the character value at the specified
     * index and ending with the character value at index {@code end - 1}.
     *
     * @param   start  the start index, inclusive.
     * @param   end    the end index, exclusive.
     * @return  the specified subsequence.
     * @throws  IndexOutOfBoundsException if {@code start} or {@code end} is out of range.
     */
    @Override
    public CharSequence subSequence(final int start, final int end) {
        return toString().substring(start, end);
    }

    /**
     * Returns this string in the given locale. If no string is available in the given locale,
     * then some fallback locale is used. The fallback locale is implementation-dependent, and
     * is not necessarily the same as the default locale used by the {@link #toString()} method.
     *
     * <h4>Handling of <code>Locale.ROOT</code> argument value</h4>
     * {@link Locale#ROOT} can be given to this method for requesting a "unlocalized" string,
     * typically some programmatic values like enumerations or identifiers. While identifiers
     * often look like English words, {@code Locale.ROOT} is not considered synonymous to
     * {@link Locale#ENGLISH} because the values may differ in the way numbers and dates are
     * formatted (e.g. using the ISO 8601 standard for dates instead of English conventions).
     *
     * <h4>Handling of <code>null</code> argument value</h4>
     * The {@code Locale.ROOT} constant is new in Java 6. Some other libraries designed for Java 5
     * use the {@code null} value for "unlocalized" strings. Apache SIS accepts {@code null} value
     * for inter-operability with those libraries. However, the behavior is implementation dependent:
     * some subclasses will take {@code null} as a synonymous of the system default locale, while
     * other subclasses will take {@code null} as a synonymous of the root locale. In order to
     * ensure determinist behavior, client code are encouraged to specify only non-null values.
     *
     * @param  locale  the desired locale for the string to be returned.
     * @return the string in the given locale if available, or in an
     *         implementation-dependent fallback locale otherwise.
     *
     * @see Locale#getDefault()
     * @see Locale#ROOT
     */
    @Override
    public abstract String toString(Locale locale);

    /**
     * Returns this string in the default locale. Invoking this method is equivalent to invoking
     * <code>{@linkplain #toString(Locale) toString}({@linkplain Locale#getDefault()})</code>.
     *
     * <p>All methods from {@link CharSequence} operate on this string.
     * This string is also used as the criterion for {@linkplain Comparable natural ordering}.</p>
     *
     * @return the string in the default locale.
     */
    @Override
    public synchronized String toString() {
        String text = defaultValue;
        if (text == null) {
            text = toString(Locale.getDefault());
            if (text == null) {
                return "";
            }
            defaultValue = text;
        }
        return text;
    }

    /**
     * Formats this international string using the given formatter.
     * This method appends the string obtained by:
     *
     * <blockquote><code>
     * {@linkplain #toString(Locale) toString}(formatter.{@linkplain Formatter#locale()})
     * </code></blockquote>
     *
     * @param formatter  the formatter to use for formatting this string.
     * @param flags      a bitmask of {@link FormattableFlags} values.
     * @param width      the minimum number of characters, or -1 if none.
     * @param precision  the maximum number of characters (before expanding to the {@code width}),
     *                   or -1 for no restriction.
     */
    @Override
    public void formatTo(final Formatter formatter, final int flags, final int width, final int precision) {
        Strings.formatTo(formatter, flags, width, precision, toString(formatter.locale()));
    }

    /**
     * Compares this string with the specified object for order. This method compares
     * the string in the {@linkplain Locale#getDefault() default locale}, as returned
     * by {@link #toString()}.
     *
     * @param  object  the string to compare with this string.
     * @return a negative number if this string is before the given string,
     *         a positive number if after, or 0 if equals.
     */
    @Override
    public int compareTo(final InternationalString object) {
        return toString().compareTo(object.toString());
    }
}
