/*******************************************************************************
 * Copyright (c) 2006, 2017 Cerner Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Brad Reynolds - initial API and implementation
 *     Matt Carter - bug 212518 (constantObservableValue)
 *     Matthew Hall - bugs 208332, 212518, 219909, 184830, 237718, 245647,
 *         226289
 *     Marko Topolnik - bug 184830
 *     Stefan Xenos <sxenos@gmail.com> - Bug 335792
 ******************************************************************************/

package org.eclipse.core.databinding.observable;

import java.util.List;
import java.util.Set;

import org.eclipse.core.databinding.observable.list.DecoratingObservableList;
import org.eclipse.core.databinding.observable.list.IListChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ObservableList;
import org.eclipse.core.databinding.observable.map.DecoratingObservableMap;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
import org.eclipse.core.databinding.observable.set.DecoratingObservableSet;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.ObservableSet;
import org.eclipse.core.databinding.observable.value.DecoratingObservableValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IVetoableValue;
import org.eclipse.core.databinding.observable.value.ValueChangingEvent;
import org.eclipse.core.internal.databinding.observable.ConstantObservableValue;
import org.eclipse.core.internal.databinding.observable.DelayedObservableValue;
import org.eclipse.core.internal.databinding.observable.EmptyObservableList;
import org.eclipse.core.internal.databinding.observable.EmptyObservableSet;
import org.eclipse.core.internal.databinding.observable.MapEntryObservableValue;
import org.eclipse.core.internal.databinding.observable.StalenessObservableValue;
import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableList;
import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableMap;
import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableSet;
import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableValue;
import org.eclipse.core.runtime.Assert;

/**
 * Contains static methods to operate on or return {@link IObservable
 * Observables}.
 *
 * @since 1.0
 */
public class Observables {
	/**
	 * Returns an observable which delays notification of value change events
	 * from <code>observable</code> until <code>delay</code> milliseconds have
	 * elapsed since the last change event. This observable helps to boost
	 * performance in situations where an observable has computationally
	 * expensive listeners or many dependencies. A common use of this observable
	 * is to delay validation of user input until the user stops typing in a UI
	 * field.
	 * <p>
	 * To notify about pending changes, the returned observable fires a stale
	 * event when the wrapped observable value fires a change event, and remains
	 * stale until the delay has elapsed and the value change is fired. A call
	 * to {@link IObservableValue#getValue() getValue()} while a value change is
	 * pending will fire the value change immediately, short-circuiting the
	 * delay.
	 * <p>
	 * <b>Note:</b>
	 * <ul>
	 * <li>Use SWTObservables.observeDelayedValue() instead when the target
	 * observable is observing a SWT Control, or
	 * ViewersObservables.observeDelayedValue() when the target observable is
	 * observing a JFace Viewer. These observables ensure that pending value
	 * changes are fired when the underlying control loses focus. (Otherwise, it
	 * is possible for pending changes to be lost if a window is closed before
	 * the delay has elapsed.)
	 * <li>This observable does not forward {@link ValueChangingEvent} events
	 * from a wrapped {@link IVetoableValue}.
	 * </ul>
	 *
	 * @param <T>
	 *            the value type
	 *
	 * @param delay
	 *            the delay in milliseconds
	 * @param observable
	 *            the observable being delayed
	 * @return an observable which delays notification of value change events
	 *         from <code>observable</code> until <code>delay</code>
	 *         milliseconds have elapsed since the last change event.
	 *
	 * @since 1.2
	 */
	public static <T> IObservableValue<T> observeDelayedValue(int delay,
			IObservableValue<T> observable) {
		return new DelayedObservableValue<T>(delay, observable);
	}

	/**
	 * Returns an unmodifiable observable value backed by the given observable
	 * value.
	 *
	 * @param <T>
	 *            the value type
	 * @param value
	 *            the value to wrap in an unmodifiable value
	 * @return an unmodifiable observable value backed by the given observable
	 *         value
	 * @since 1.1
	 */
	public static <T> IObservableValue<T> unmodifiableObservableValue(
			IObservableValue<T> value) {
		Assert.isNotNull(value, "Argument 'value' cannot be null"); //$NON-NLS-1$
		return new UnmodifiableObservableValue<T>(value);
	}

	/**
	 * Returns an observable value with the given constant value.
	 *
	 * @param <T>
	 *            the value type
	 *
	 * @param realm
	 *            the observable's realm
	 * @param value
	 *            the observable's constant value
	 * @param valueType
	 *            the observable's value type
	 * @return an immutable observable value with the given constant value
	 * @since 1.1
	 */
	public static <T> IObservableValue<T> constantObservableValue(Realm realm,
			T value, Object valueType) {
		return new ConstantObservableValue<T>(realm, value, valueType);
	}

	/**
	 * Returns an observable value with the given constant value.
	 *
	 * @param <T>
	 *
	 * @param realm
	 *            the observable's realm
	 * @param value
	 *            the observable's constant value
	 * @return an immutable observable value with the given constant value
	 * @since 1.1
	 */
	public static <T> IObservableValue<T> constantObservableValue(Realm realm,
			T value) {
		return constantObservableValue(realm, value, null);
	}

	/**
	 * Returns an observable value with the given constant value.
	 *
	 * @param <T>
	 *            the value type
	 *
	 * @param value
	 *            the observable's constant value
	 * @param valueType
	 *            the observable's value type
	 * @return an immutable observable value with the given constant value
	 * @since 1.1
	 */
	public static <T> IObservableValue<T> constantObservableValue(T value,
			T valueType) {
		return constantObservableValue(Realm.getDefault(), value, valueType);
	}

	/**
	 * Returns an observable value with the given constant value.
	 *
	 * @param <T>
	 *
	 * @param value
	 *            the observable's constant value
	 * @return an immutable observable value with the given constant value
	 * @since 1.1
	 */
	public static <T> IObservableValue<T> constantObservableValue(T value) {
		return constantObservableValue(Realm.getDefault(), value, null);
	}

	/**
	 * Returns an unmodifiable observable list backed by the given observable
	 * list.
	 *
	 * @param <E>
	 *            the element type
	 *
	 * @param list
	 *            the list to wrap in an unmodifiable list
	 * @return an unmodifiable observable list backed by the given observable
	 *         list
	 */
	public static <E> IObservableList<E> unmodifiableObservableList(
			IObservableList<E> list) {
		if (list == null) {
			throw new IllegalArgumentException("List parameter cannot be null."); //$NON-NLS-1$
		}

		return new UnmodifiableObservableList<E>(list);
	}

	/**
	 * Returns an unmodifiable observable set backed by the given observable
	 * set.
	 *
	 * @param <E>
	 *
	 * @param set
	 *            the set to wrap in an unmodifiable set
	 * @return an unmodifiable observable set backed by the given observable set
	 * @since 1.1
	 */
	public static <E> IObservableSet<E> unmodifiableObservableSet(
			IObservableSet<E> set) {
		if (set == null) {
			throw new IllegalArgumentException("Set parameter cannot be null"); //$NON-NLS-1$
		}

		return new UnmodifiableObservableSet<E>(set);
	}

	/**
	 * Returns an unmodifiable observable map backed by the given observable
	 * map.
	 *
	 * @param <K>
	 *            map key type
	 * @param <V>
	 *            map value type
	 *
	 * @param map
	 *            the map to wrap in an unmodifiable map
	 * @return an unmodifiable observable map backed by the given observable
	 *         map.
	 * @since 1.2
	 */
	public static <K, V> IObservableMap<K, V> unmodifiableObservableMap(
			IObservableMap<K, V> map) {
		if (map == null) {
			throw new IllegalArgumentException("Map parameter cannot be null"); //$NON-NLS-1$
		}

		return new UnmodifiableObservableMap<K, V>(map);
	}

	/**
	 * Returns an empty observable list. The returned list continues to work
	 * after it has been disposed of and can be disposed of multiple times.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @return an empty observable list.
	 */
	public static <E> IObservableList<E> emptyObservableList() {
		return emptyObservableList(Realm.getDefault(), null);
	}

	/**
	 * Returns an empty observable list of the given element type. The returned
	 * list continues to work after it has been disposed of and can be disposed
	 * of multiple times.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param elementType
	 *            the element type of the returned list
	 * @return an empty observable list
	 * @since 1.1
	 */
	public static <E> IObservableList<E> emptyObservableList(Object elementType) {
		return emptyObservableList(Realm.getDefault(), elementType);
	}

	/**
	 * Returns an empty observable list belonging to the given realm. The
	 * returned list continues to work after it has been disposed of and can be
	 * disposed of multiple times.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param realm
	 *            the realm of the returned list
	 * @return an empty observable list.
	 */
	public static <E> IObservableList<E> emptyObservableList(Realm realm) {
		return emptyObservableList(realm, null);
	}

	/**
	 * Returns an empty observable list of the given element type and belonging
	 * to the given realm. The returned list continues to work after it has been
	 * disposed of and can be disposed of multiple times.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param realm
	 *            the realm of the returned list
	 * @param elementType
	 *            the element type of the returned list
	 * @return an empty observable list
	 * @since 1.1
	 */
	public static <E> IObservableList<E> emptyObservableList(Realm realm,
			Object elementType) {
		return new EmptyObservableList<E>(realm, elementType);
	}

	/**
	 * Returns an empty observable set. The returned set continues to work after
	 * it has been disposed of and can be disposed of multiple times.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @return an empty observable set.
	 */
	public static <E> IObservableSet<E> emptyObservableSet() {
		return emptyObservableSet(Realm.getDefault(), null);
	}

	/**
	 * Returns an empty observable set of the given element type. The returned
	 * set continues to work after it has been disposed of and can be disposed
	 * of multiple times.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @param elementType
	 *            the element type of the returned set
	 * @return an empty observable set
	 * @since 1.1
	 */
	public static <E> IObservableSet<E> emptyObservableSet(Object elementType) {
		return emptyObservableSet(Realm.getDefault(), elementType);
	}

	/**
	 * Returns an empty observable set belonging to the given realm. The
	 * returned set continues to work after it has been disposed of and can be
	 * disposed of multiple times.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @param realm
	 *            the realm of the returned set
	 * @return an empty observable set.
	 */
	public static <E> IObservableSet<E> emptyObservableSet(Realm realm) {
		return emptyObservableSet(realm, null);
	}

	/**
	 * Returns an empty observable set of the given element type and belonging
	 * to the given realm. The returned set continues to work after it has been
	 * disposed of and can be disposed of multiple times.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @param realm
	 *            the realm of the returned set
	 * @param elementType
	 *            the element type of the returned set
	 * @return an empty observable set
	 * @since 1.1
	 */
	public static <E> IObservableSet<E> emptyObservableSet(Realm realm,
			Object elementType) {
		return new EmptyObservableSet<E>(realm, elementType);
	}

	/**
	 * Returns an observable set backed by the given set.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @param set
	 *            the set to wrap in an IObservableSet
	 * @return an observable set backed by the given set
	 */
	public static <E> IObservableSet<E> staticObservableSet(Set<E> set) {
		return staticObservableSet(Realm.getDefault(), set, Object.class);
	}

	/**
	 * Returns an observable set of the given element type, backed by the given
	 * set.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @param set
	 *            the set to wrap in an IObservableSet
	 * @param elementType
	 *            the element type of the returned set
	 * @return Returns an observable set backed by the given unchanging set
	 * @since 1.1
	 */
	public static <E> IObservableSet<E> staticObservableSet(Set<E> set,
			Object elementType) {
		return staticObservableSet(Realm.getDefault(), set, elementType);
	}

	/**
	 * Returns an observable set belonging to the given realm, backed by the
	 * given set.
	 *
	 * @param <E>
	 *
	 * @param realm
	 *            the realm of the returned set
	 * @param set
	 *            the set to wrap in an IObservableSet
	 * @return an observable set backed by the given unchanging set
	 */
	public static <E> IObservableSet<E> staticObservableSet(Realm realm,
			Set<E> set) {
		return staticObservableSet(realm, set, Object.class);
	}

	/**
	 * Returns an observable set of the given element type and belonging to the
	 * given realm, backed by the given set.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @param realm
	 *            the realm of the returned set
	 * @param set
	 *            the set to wrap in an IObservableSet
	 * @param elementType
	 *            the element type of the returned set
	 * @return an observable set backed by the given set
	 * @since 1.1
	 */
	public static <E> IObservableSet<E> staticObservableSet(Realm realm,
			Set<E> set, Object elementType) {
		return new ObservableSet<E>(realm, set, elementType) {
			@Override
			public synchronized void addChangeListener(IChangeListener listener) {
			}

			@Override
			public synchronized void addStaleListener(IStaleListener listener) {
			}

			@Override
			public synchronized void addSetChangeListener(
					ISetChangeListener<? super E> listener) {
			}
		};
	}

	/**
	 * Returns an observable value that contains the same value as the given
	 * observable, and fires the same events as the given observable, but can be
	 * disposed of without disposing of the wrapped observable.
	 *
	 * @param <T>
	 *            the value type
	 *
	 * @param target
	 *            the observable value to wrap
	 * @return a disposable proxy for the given observable value.
	 * @since 1.2
	 */
	public static <T> IObservableValue<T> proxyObservableValue(
			IObservableValue<T> target) {
		return new DecoratingObservableValue<T>(target, false);
	}

	/**
	 * Returns an observable set that contains the same elements as the given
	 * set, and fires the same events as the given set, but can be disposed of
	 * without disposing of the wrapped set.
	 *
	 * @param <E>
	 *            the set element type
	 *
	 * @param target
	 *            the set to wrap
	 * @return a disposable proxy for the given observable set
	 */
	public static <E> IObservableSet<E> proxyObservableSet(
			IObservableSet<E> target) {
		return new DecoratingObservableSet<E>(target, false);
	}

	/**
	 * Returns an observable list that contains the same elements as the given
	 * list, and fires the same events as the given list, but can be disposed of
	 * without disposing of the wrapped list.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param target
	 *            the list to wrap
	 * @return a disposable proxy for the given observable list
	 * @since 1.1
	 */
	public static <E> IObservableList<E> proxyObservableList(
			IObservableList<E> target) {
		return new DecoratingObservableList<E>(target, false);
	}

	/**
	 * Returns an observable map that contains the same entries as the given
	 * map, and fires the same events as the given map, but can be disposed of
	 * without disposing of the wrapped map.
	 *
	 * @param <K>
	 *            the map key type
	 * @param <V>
	 *            the map value type
	 *
	 * @param target
	 *            the map to wrap
	 * @return a disposable proxy for the given observable map
	 * @since 1.2
	 */
	public static <K, V> IObservableMap<K, V> proxyObservableMap(
			IObservableMap<K, V> target) {
		return new DecoratingObservableMap<K, V>(target, false);
	}

	/**
	 * Returns an observable list backed by the given list.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param list
	 *            the list to wrap in an IObservableList
	 * @return an observable list backed by the given unchanging list
	 */
	public static <E> IObservableList<E> staticObservableList(List<E> list) {
		return staticObservableList(Realm.getDefault(), list, Object.class);
	}

	/**
	 * Returns an observable list of the given element type, backed by the given
	 * list.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param list
	 *            the list to wrap in an IObservableList
	 * @param elementType
	 *            the element type of the returned list
	 * @return an observable list backed by the given unchanging list
	 * @since 1.1
	 */
	public static <E> IObservableList<E> staticObservableList(List<E> list,
			Object elementType) {
		return staticObservableList(Realm.getDefault(), list, elementType);
	}

	/**
	 * Returns an observable list belonging to the given realm, backed by the
	 * given list.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param realm
	 *            the realm of the returned list
	 * @param list
	 *            the list to wrap in an IObservableList
	 * @return an observable list backed by the given unchanging list
	 */
	public static <E> IObservableList<E> staticObservableList(Realm realm,
			List<E> list) {
		return staticObservableList(realm, list, Object.class);
	}

	/**
	 * Returns an observable list of the given element type and belonging to the
	 * given realm, backed by the given list.
	 *
	 * @param <E>
	 *            the list element type
	 *
	 * @param realm
	 *            the realm of the returned list
	 * @param list
	 *            the list to wrap in an IObservableList
	 * @param elementType
	 *            the element type of the returned list
	 * @return an observable list backed by the given unchanging list
	 * @since 1.1
	 */
	public static <E> IObservableList<E> staticObservableList(Realm realm,
			List<E> list, Object elementType) {
		return new ObservableList<E>(realm, list, elementType) {
			@Override
			public synchronized void addChangeListener(IChangeListener listener) {
			}

			@Override
			public synchronized void addStaleListener(IStaleListener listener) {
			}

			@Override
			public synchronized void addListChangeListener(
					IListChangeListener<? super E> listener) {
			}
		};
	}

	/**
	 * Returns an observable value of type <code>Boolean.TYPE</code> which
	 * tracks whether the given observable is stale.
	 *
	 * @param observable
	 *            the observable to track
	 * @return an observable value which tracks whether the given observable is
	 *         stale
	 *
	 * @since 1.1
	 */
	public static IObservableValue<Boolean> observeStale(IObservable observable) {
		return new StalenessObservableValue(observable);
	}

	/**
	 * Returns an observable value that tracks changes to the value of an
	 * observable map's entry specified by its key.
	 * <p>
	 * The state where the key does not exist in the map is equivalent to the
	 * state where the key exists and its value is <code>null</code>. The
	 * transition between these two states is not considered a value change and
	 * no event is fired.
	 *
	 * @param <K>
	 *            the map key type
	 * @param <V>
	 *            the map value type
	 *
	 * @param map
	 *            the observable map whose entry will be tracked.
	 * @param key
	 *            the key identifying the map entry to track.
	 * @return an observable value that tracks the value associated with the
	 *         specified key in the given map
	 * @since 1.2
	 */
	public static <K, V> IObservableValue<V> observeMapEntry(
			IObservableMap<K, V> map, K key) {
		return observeMapEntry(map, key, map.getValueType());
	}

	/**
	 * Returns an observable value that tracks changes to the value of an
	 * observable map's entry specified by its key.
	 * <p>
	 * The state where the key does not exist in the map is equivalent to the
	 * state where the key exists and its value is <code>null</code>. The
	 * transition between these two states is not considered a value change and
	 * no event is fired.
	 *
	 * @param <K>
	 *            the map key type
	 * @param <V>
	 *            the map value type
	 *
	 * @param map
	 *            the observable map whose entry will be tracked.
	 * @param key
	 *            the key identifying the map entry to track.
	 * @param valueType
	 *            the type of the value. May be <code>null</code>, meaning the
	 *            value is untyped.
	 * @return an observable value that tracks the value associated with the
	 *         specified key in the given map
	 * @since 1.1
	 */
	public static <K, V> IObservableValue<V> observeMapEntry(
			IObservableMap<K, V> map, K key, Object valueType) {
		if (valueType == null)
			valueType = map.getValueType();
		return new MapEntryObservableValue<K, V>(map, key, valueType);
	}

	/**
	 * Returns a factory for creating observable values tracking the value of
	 * the {@link IObservableMap observable map} entry identified by a
	 * particular key.
	 *
	 * @param <K>
	 *            the map key type
	 * @param <V>
	 *            the map value type
	 *
	 * @param map
	 *            the observable map whose entry will be tracked.
	 * @param valueType
	 *            the type of the value. May be <code>null</code>, meaning the
	 *            value is untyped.
	 * @return a factory for creating observable values tracking the value of
	 *         the observable map entry identified by a particular key object.
	 * @since 1.1
	 */
	public static <K, V> IObservableFactory<K, IObservableValue<V>> mapEntryValueFactory(
			final IObservableMap<K, V> map, final Object valueType) {
		return key -> observeMapEntry(map, key, valueType);
	}

	/**
	 * Helper method for <code>MasterDetailObservables.detailValue(master,
	 * mapEntryValueFactory(map, valueType), valueType)</code>.
	 *
	 * @param <K>
	 *            the map key type
	 * @param <V>
	 *            the map value type
	 *
	 * @param map
	 *            the observable map whose entry will be tracked.
	 * @param master
	 *            the observable value that identifies which map entry to track.
	 * @param valueType
	 *            the type of the value. May be <code>null</code>, meaning the
	 *            value is untyped.
	 * @return an observable value tracking the current value of the specified
	 *         key in the given map an observable value that tracks the current
	 *         value of the named property for the current value of the master
	 *         observable value
	 * @since 1.1
	 */
	public static <K, V> IObservableValue<V> observeDetailMapEntry(
			IObservableMap<K, V> map, IObservableValue<K> master,
			Object valueType) {
		return MasterDetailObservables.detailValue(master,
				mapEntryValueFactory(map, valueType), valueType);
	}

	/**
	 * Copies the current value of the source observable to the destination
	 * observable, and upon value change events fired by the source observable,
	 * updates the destination observable accordingly, until the source
	 * observable is disposed. This method assumes that both observables are on
	 * the same realm.
	 *
	 * @param <T>
	 *            the value type
	 *
	 * @param source
	 *            the source observable
	 * @param destination
	 *            the destination observable
	 * @since 1.2
	 */
	public static <T> void pipe(IObservableValue<T> source,
			final IObservableValue<? super T> destination) {
		destination.setValue(source.getValue());
		source.addValueChangeListener(event -> destination.setValue(event.diff.getNewValue()));
	}
}
