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

import java.lang.reflect.*;

import org.apache.juneau.reflect.*;

/**
 * Represents a simple settable value.
 *
 * <p>
 * This object is not thread safe.
 *
 * @param <T> The value type.
 */
public class Value<T> {

	/**
	 * Returns the generic parameter type of the Value parameter at the specified position.
	 *
	 * <h5 class='figure'>Example:</h5>
	 * <p class='bcode w800'>
	 * 	<jk>public class</jk> A {
	 * 		<jk>public void</jk> doX(Value&lt;Foo&gt; foo) {...}
	 * 	}
	 * </p>
	 * <p class='bcode w800'>
	 * 	Class&lt;?&gt; t = Value.<jsm>getValueType</jsm>(A.<jk>class</jk>.getMethod(<js>"doX"</js>, Value.<jk>class</jk>));
	 * 	<jsm>assertTrue</jsm>(t == Foo.<jk>class</jk>);
	 * </p>
	 *
	 * @param m The method containing the parameter.
	 * @param i The index of the parameter.
	 * @return The parameter type of the value, or <jk>null</jk> if the method parameter is not of type <c>Value</c>.
	 */
	public static Type getParameterType(Method m, int i) {
		return getParameterType(m.getGenericParameterTypes()[i]);
	}

	/**
	 * Returns the generic parameter type of the Value type.
	 *
	 * @param t The type to find the parameter type of.
	 * @return The parameter type of the value, or <jk>null</jk> if the type is not a subclass of <c>Value</c>.
	 */
	public static Type getParameterType(Type t) {
		if (t instanceof ParameterizedType) {
			ParameterizedType pt = (ParameterizedType)t;
			if (pt.getRawType() == Value.class) {
				Type[] ta = pt.getActualTypeArguments();
				if (ta.length > 0)
					return ta[0];
			}
		} else if (t instanceof Class) {
			Class<?> c = (Class<?>)t;
			if (Value.class.isAssignableFrom(c)) {
				return ClassInfo.of(c).getParameterType(0, Value.class);
			}
		}

		return null;
	}

	/**
	 * Convenience method for checking if the specified type is this class.
	 *
	 * @param t The type to check.
	 * @return <jk>true</jk> if the specified type is this class.
	 */
	public static boolean isType(Type t) {
		return
			(t instanceof ParameterizedType && ((ParameterizedType)t).getRawType() == Value.class)
			|| (t instanceof Class && Value.class.isAssignableFrom((Class<?>)t));
	}

	private T t;
	private ValueListener<T> listener;

	/**
	 * Constructor.
	 */
	public Value() {
	}

	/**
	 * Constructor.
	 *
	 * @param t Initial value.
	 */
	public Value(T t) {
		set(t);
	}

	/**
	 * Adds a listener for this value.
	 *
	 * @param listener The new listener for this value.
	 * @return This object (for method chaining).
	 */
	public Value<T> listener(ValueListener<T> listener) {
		this.listener = listener;
		return this;
	}

	/**
	 * Sets the value.
	 *
	 * @param t The new value.
	 * @return This object (for method chaining).
	 */
	public Value<T> set(T t) {
		this.t = t;
		if (listener != null)
			listener.onSet(t);
		return this;
	}

	/**
	 * Returns the value.
	 *
	 * @return The value, or <jk>null</jk> if it is not set.
	 */
	public T get() {
		return t;
	}

	/**
	 * Returns <jk>true</jk> if the value is set.
	 *
	 * @return <jk>true</jk> if the value is set.
	 */
	public boolean isSet() {
		return get() != null;
	}
}
