/*******************************************************************************
*   Copyright 2014 Analog Devices, Inc.
*
*   Licensed 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 com.analog.lyric.options;

import java.io.Serializable;
import java.util.Comparator;

import net.jcip.annotations.Immutable;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
 * A unique key for looking up or setting a value from a {@link IOptionHolder}.
 * <p>
 * @param <T> is the type of the value that will be associated with the key. The type
 * should either be {@link String}, a wrapped primitive type (e.g. {@link Double}, {@link Boolean}),
 * or an immutable concrete implementation of {@link IOptionValue}.
 * <p>
 * Concrete instances should be public static final fields, with given {@link #name()}, preferably
 * in a subclass of {@link OptionKeyDeclarer}.
 * <p>
 * {@link OptionKey} contains useful static methods for loading option keys, whether or not
 * they are implemented as subclasses of that class.
 * <p>
 * @see OptionKey#forCanonicalName
 * @see OptionKey#inClass(Class, String)
 * @see OptionKey#canonicalName(IOptionKey)
 * @see OptionKey#qualifiedName(IOptionKey)
 */
@Immutable
public interface IOptionKey<T extends Serializable> extends Serializable
{
	/**
	 * Comparator that orders keys by name.
	 * 
	 * @since 0.07
	 */
	public static enum CompareByName implements Comparator<IOptionKey<?>>
	{
		INSTANCE;
		
		@NonNullByDefault(false)
		@Override
		public int compare(IOptionKey<?> key1, IOptionKey<?> key2)
		{
			return key1.name().compareTo(key2.name());
		}
	}
	
	/**
	 * Identifies the option lookup method used for an option key.
	 * 
	 * @since 0.08
	 */
	public static enum Lookup
	{
		/**
		 * Look up option values be searching delegates. This is the standard method.
		 */
		NONLOCAL,
		
		/**
		 * Look up option values only locally.
		 */
		LOCAL;
	}
	
	/**
	 * Attempts to convert value to appropriate key value type.
	 * <p>
	 * This method should be able to convert values produced by {@link #convertToExternal}.
	 * <p>
	 * @param value a non-null value to be converted to the option's {@link #type}.
	 * @throws RuntimeException if value cannot be converted
	 * @since 0.07
	 */
	public abstract T convertToValue(@Nullable Object value);
	
	/**
	 * Converts value to an external representation
	 * <p>
	 * Converts value to representation suitable for use in external environments
	 * such as MATLAB or Python. Acceptable output types include boxed primitive
	 * types, Strings, and arrays of the those.
	 * <p>
	 * It is expected that the returned value can be converted back to the original
	 * typed value using {@link #convertToValue}.
	 * <p>
	 * @param value
	 * @since 0.07
	 */
	public abstract @Nullable Object convertToExternal(T value);
	
	/**
	 * Should be the class that contains the declaration of this instance.
	 */
	public abstract Class<?> getDeclaringClass();
	
	/**
	 * Indicates whether this is a local option.
	 * <p>
	 * Local options are only looked up locally in the option holder and not its delegates.
	 * True if {@link #lookupMethod} is {@code LOCAL}.
	 * @since 0.08
	 */
	public abstract boolean local();
	
	/**
	 * Identifies the lookup method used to retrieve option values for this key.
	 * @since 0.08
	 * @see #local
	 */
	public abstract Lookup lookupMethod();
	
	/**
	 * The unqualified name of the option.
	 * <p>
	 * Should be a valid Java identifier, and should be the same as the name
	 * of the field that holds this instance.
	 */
	public abstract String name();
	
	/**
	 * The type of the option's value, which should be the same as the type parameter {@code T}.
	 */
	public abstract Class<? extends T> type();
	
	/**
	 * The default value of the option if not set. This must not be null!
	 */
	public abstract T defaultValue();
	
	/**
	 * Returns the value of the option for the given {@code holder} or else its default value, if not set.
	 * <p>
	 * This should be implemented by invoking {@linkplain IOptionHolder#getOptionOrDefault getOptionOrDefault}
	 * on {@code holder}.
	 */
	public abstract T getOrDefault(IOptionHolder holder);
	
	/**
	 * Returns the value of the option for the given {@code holder} or null if not set.
	 * <p>
	 * This should be implemented by invoking {@linkplain IOptionHolder#getOption getOption}
	 * on {@code holder}.
	 */
	public abstract @Nullable T get(IOptionHolder holder);
	
	/**
	 * Set the option value locally on the given {@code holder}.
	 * <p>
	 * This should be implemented by invoking {@linkplain IOptionHolder#setOption(IOptionKey,Serializable) setOption}
	 * on {@code holder}.
	 */
	public abstract void set(IOptionHolder holder, T value);
	
	/**
	 * Unset the option locally on the given {@code holder}.
	 * <p>
	 * This should be implemented by invoking {@linkplain IOptionHolder#unsetOption(IOptionKey) unsetOption}
	 * on {@code holder}.
	 */
	public abstract void unset(IOptionHolder holder);
	
	/**
	 * Determine if value obtained locally from an option holder can be used by another option holder.
	 * <p>
	 * This method is called by implementations of {@link IOptionHolder#getOption(IOptionKey)} and
	 * related methods when determining whether or not to skip over a value found in a delegate option
	 * holder.
	 * <p>
	 * @param value is non-null value that has been set locally on some option holder
	 * @param delegator is the option holder from which the lookup originates.
	 * @return true if value can be used by {@code delegator}. Most implemtnations will simply return true.
	 * @since 0.08
	 */
	public boolean validForDelegator(T value, IOptionHolder delegator);
	
	/**
	 * Validates value for this key.
	 * <p>
	 * This is invoked by implementations of {@link IOptionHolder#setOption} and can be
	 * overridden to put extra constraints on the value.
	 * <p>
	 * @param value is the value to be set on this option.
	 * @param optionHolder is the option holder on which the option is being set, if available.
	 * @return the {@code value} itself if valid otherwise throws an exception.
	 * @throws ClassCastException if {@code value} has the wrong type.
	 * @throws OptionValidationException if value is invalid.
	 * @since 0.07
	 */
	public T validate(T value, @Nullable IOptionHolder optionHolder);
}
