/*
 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2023-2024 MicroEJ Corp. This file has been modified and/or created by MicroEJ Corp.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  * Neither the name of JSR-310 nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package java.time.zone;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Set;

import ej.annotation.Nullable;
import ej.bon.Constants;

/**
 * Provider of time-zone rules to the system.
 * <p>
 * This class manages the configuration of time-zone rules. The static methods provide the public API that can be used
 * to manage the timezone-rules provider. The abstract methods provide the SPI that allows rules to be provided.
 * <p>
 * If the constant {@code java.time.zone.DefaultZoneRulesProvider} is defined then it is taken to be the fully-qualified 
 * name of a concrete ZoneRulesProvider class to be loaded as the default provider. 
 * If this constant is not defined, the system-default provider will be loaded to serve as the default provider.
 * It provides rules only for the "GMT" time zone and throws a {@link ZoneRulesException} for any attempt to query
 * another zone ID.
 * <p>
 * Rules are looked up primarily by zone ID, as used by {@link ZoneId}. Only zone region IDs may be used, zone offset
 * IDs are not used here.
 * <p>
 * Time-zone rules are political, thus the data can change at any time. Each provider will provide the latest rules for
 * each zone ID, but they may also provide the history of how the rules changed.
 * <p> This interface is a service provider that can be called by multiple threads. Implementations must be
 * immutable and thread-safe.
 * <p>
 * Providers must ensure that once a rule has been seen by the application, the rule must continue to be available.
 * <p>
 * Providers are encouraged to implement a meaningful {@code toString} method.
 * <p>
 * Many systems would like to update time-zone rules dynamically without stopping the JVM. When examined in detail, this
 * is a complex problem. Providers may choose to handle dynamic updates, however the default provider does not.
 */
public abstract class ZoneRulesProvider {

	private static final String ZONE_RULES_PROVIDER_CONSTANT = "java.time.zone.DefaultZoneRulesProvider";

	private static final ZoneRulesProvider ZONE_RULES_PROVIDER;

	static{
		try {
			Class<?> clazz = Constants.getClass(ZONE_RULES_PROVIDER_CONSTANT);
			ZONE_RULES_PROVIDER = (ZoneRulesProvider) clazz.newInstance();
		} catch (Exception e) {
			throw new IllegalStateException(e);
		}
	}


	// -------------------------------------------------------------------------

	/**
	 * Gets the rules for the zone ID.
	 * <p>
	 * This returns the latest available rules for the zone ID.
	 * <p>
	 * This method relies on time-zone data provider files that are configured. These are loaded using a
	 * {@code ServiceLoader}.
	 * <p>
	 * The caching flag is designed to allow provider implementations to prevent the rules being cached in
	 * {@code ZoneId}. Under normal circumstances, the caching of zone rules is highly desirable as it will provide
	 * greater performance. However, there is a use case where the caching would not be desirable, see
	 * {@link #provideRules}.
	 *
	 * @param zoneId
	 *            the zone ID as defined by {@code ZoneId}, not null
	 * @param forCaching
	 *            whether the rules are being queried for caching, true if the returned rules will be cached by
	 *            {@code ZoneId}, false if they will be returned to the user without being cached in {@code ZoneId}
	 * @return the rules, null if {@code forCaching} is true and this is a dynamic provider that wants to prevent
	 *         caching in {@code ZoneId}, otherwise not null
	 * @throws ZoneRulesException
	 *             if rules cannot be obtained for the zone ID
	 */
	@Nullable
	public static ZoneRules getRules(String zoneId, boolean forCaching) {
		Objects.requireNonNull(zoneId, "zoneId");
		return ZONE_RULES_PROVIDER.provideRules(zoneId, forCaching);
	}
	
	 /**
     * Gets the history of rules for the zone ID.
     * <p>
     * Time-zones are defined by governments and change frequently.
     * This method allows applications to find the history of changes to the
     * rules for a single zone ID. The map is keyed by a string, which is the
     * version string associated with the rules.
     * <p>
     * The exact meaning and format of the version is provider specific.
     * The version must follow lexicographical order, thus the returned map will
     * be ordered from the oldest known rules to the newest available rules.
     * <p>
     * Implementations must provide a result for each valid zone ID, however
     * they do not have to provide a history of rules.
     * Thus, the map will always contain one element, and will only contain more
     * than one element if historical rule information is available.
     *
     * @param zoneId  the zone ID as defined by {@code ZoneId}, not null
     * @return a modifiable copy of the history of the rules for the ID, sorted
     *  from oldest to newest, not null
     * @throws ZoneRulesException if history cannot be obtained for the zone ID
     */
    public static NavigableMap<String, ZoneRules> getVersions(String zoneId) {
        return ZONE_RULES_PROVIDER.provideVersions(zoneId);
    }


	/**
	 * Gets the set of available zone IDs.
	 * <p>
	 * These IDs are the string form of a {@link ZoneId}.
	 *
	 * @return a modifiable copy of the set of zone IDs, not null
	 */
	public static Set<String> getAvailableZoneIds() {
		return ZONE_RULES_PROVIDER.provideZoneIds();
	}

	// -------------------------------------------------------------------------

	/**
	 * Refreshes the rules from the underlying data provider.
	 * <p>
	 * This method allows an application to request that the providers check for any updates to the provided rules.
	 * After calling this method, the offset stored in any {@link ZonedDateTime} may be invalid for the zone ID.
	 * <p>
	 * Dynamic update of rules is a complex problem and most applications should not use this method or dynamic rules.
	 * To achieve dynamic rules, a provider implementation will have to be written as per the specification of this
	 * class. In addition, instances of {@code ZoneRules} must not be cached in the application as they will become
	 * stale. However, the boolean flag on {@link #provideRules(String, boolean)} allows provider implementations to
	 * control the caching of {@code ZoneId}, potentially ensuring that all objects in the system see the new rules.
	 * Note that there is likely to be a cost in performance of a dynamic rules provider. Note also that no dynamic
	 * rules provider is in this specification.
	 *
	 * @return true if the rules were updated
	 * @throws ZoneRulesException
	 *             if an error occurs during the refresh
	 */
	public static boolean refresh() {
		return ZONE_RULES_PROVIDER.provideRefresh();
	}

	/**
	 * Constructor.
	 */
	protected ZoneRulesProvider() {
	}

	// -----------------------------------------------------------------------
	/**
	 * SPI method to get the available zone IDs.
	 * <p>
	 * This obtains the IDs that this {@code ZoneRulesProvider} provides. A provider should provide data for at least
	 * one zone ID.
	 * <p>
	 * The returned zone IDs remain available and valid for the lifetime of the application. A dynamic provider may
	 * increase the set of IDs as more data becomes available.
	 *
	 * @return the set of zone IDs being provided, not null
	 * @throws ZoneRulesException
	 *             if a problem occurs while providing the IDs
	 */
	protected abstract Set<String> provideZoneIds();

	/**
	 * SPI method to get the rules for the zone ID.
	 * <p>
	 * This loads the rules for the specified zone ID. The provider implementation must validate that the zone ID is
	 * valid and available, throwing a {@code ZoneRulesException} if it is not. The result of the method in the valid
	 * case depends on the caching flag.
	 * <p>
	 * If the provider implementation is not dynamic, then the result of the method must be the non-null set of rules
	 * selected by the ID.
	 * <p>
	 * If the provider implementation is dynamic, then the flag gives the option of preventing the returned rules from
	 * being cached in {@link ZoneId}. When the flag is true, the provider is permitted to return null, where null will
	 * prevent the rules from being cached in {@code ZoneId}. When the flag is false, the provider must return non-null
	 * rules.
	 *
	 * @param zoneId
	 *            the zone ID as defined by {@code ZoneId}, not null
	 * @param forCaching
	 *            whether the rules are being queried for caching, true if the returned rules will be cached by
	 *            {@code ZoneId}, false if they will be returned to the user without being cached in {@code ZoneId}
	 * @return the rules, null if {@code forCaching} is true and this is a dynamic provider that wants to prevent
	 *         caching in {@code ZoneId}, otherwise not null
	 * @throws ZoneRulesException
	 *             if rules cannot be obtained for the zone ID
	 */
	@Nullable
	protected abstract ZoneRules provideRules(String zoneId, boolean forCaching);

	
	/**
     * SPI method to get the history of rules for the zone ID.
     * <p>
     * This returns a map of historical rules keyed by a version string.
     * The exact meaning and format of the version is provider specific.
     * The version must follow lexicographical order, thus the returned map will
     * be ordered from the oldest known rules to the newest available rules.
     * <p>
     * Implementations must provide a result for each valid zone ID, however
     * they do not have to provide a history of rules.
     * Thus, the map will contain at least one element, and will only contain
     * more than one element if historical rule information is available.
     * <p>
     * The returned versions remain available and valid for the lifetime of the application.
     * A dynamic provider may increase the set of versions as more data becomes available.
     *
     * @param zoneId  the zone ID as defined by {@code ZoneId}, not null
     * @return a modifiable copy of the history of the rules for the ID, sorted
     *  from oldest to newest, not null
     * @throws ZoneRulesException if history cannot be obtained for the zone ID
     */
    protected abstract NavigableMap<String, ZoneRules> provideVersions(String zoneId);
	
	/**
	 * SPI method to refresh the rules from the underlying data provider.
	 * <p>
	 * This method provides the opportunity for a provider to dynamically recheck the underlying data provider to find
	 * the latest rules. This could be used to load new rules without stopping the JVM. Dynamic behavior is entirely
	 * optional and most providers do not support it.
	 * <p>
	 * This implementation returns false.
	 *
	 * @return true if the rules were updated
	 * @throws ZoneRulesException
	 *             if an error occurs during the refresh
	 */
	protected boolean provideRefresh() {
		return false;
	}


}
