/*******************************************************************************
 * Copyright (c) 2008, 2015 Matthew Hall 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:
 *     Matthew Hall - initial API and implementation (bug 194734)
 *     Matthew Hall - bugs 195222, 263868, 264954
 *     Stefan Xenos <sxenos@gmail.com> - Bug 335792
 ******************************************************************************/

package org.eclipse.core.databinding.property;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.list.IListProperty;
import org.eclipse.core.databinding.property.map.IMapProperty;
import org.eclipse.core.databinding.property.set.ISetProperty;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.internal.databinding.property.list.SelfListProperty;
import org.eclipse.core.internal.databinding.property.map.SelfMapProperty;
import org.eclipse.core.internal.databinding.property.set.SelfSetProperty;
import org.eclipse.core.internal.databinding.property.value.ConvertedValueProperty;
import org.eclipse.core.internal.databinding.property.value.ObservableValueProperty;
import org.eclipse.core.internal.databinding.property.value.SelfValueProperty;

/**
 * Contains static methods to operate on or return IProperty objects.
 *
 * @since 1.2
 */
public class Properties {
	/**
	 * Returns an array of observable maps where each map observes the
	 * corresponding value property on all elements in the given domain set, for
	 * each property in the given array.
	 *
	 * @param domainSet
	 *            the set of elements whose properties will be observed
	 * @param properties
	 *            array of value properties to observe on each element in the
	 *            domain map's {@link Map#values() values} collection
	 * @return an array of observable maps where each map observes the
	 *         corresponding value property of the given domain set
	 */
	@SafeVarargs
	public static <E, P> IObservableMap<E, ? extends P>[] observeEach(IObservableSet<E> domainSet,
			IValueProperty<? super E, ? extends P>... properties) {
		@SuppressWarnings("unchecked")
		IObservableMap<E, ? extends P>[] maps = new IObservableMap[properties.length];
		for (int i = 0; i < maps.length; i++) {
			maps[i] = properties[i].observeDetail(domainSet);
		}
		return maps;
	}

	/**
	 * Returns an array of observable maps where each maps observes the
	 * corresponding value property on all elements in the given domain map's
	 * {@link Map#values() values} collection, for each property in the given
	 * array.
	 *
	 * @param domainMap
	 *            the map of elements whose properties will be observed
	 * @param properties
	 *            array of value properties to observe on each element in the
	 *            domain map's {@link Map#values() values} collection
	 * @return a list of observable maps where each maps observes the
	 *         corresponding value property on all elements in the given domain
	 *         map's {@link Map#values() values} collection, for each property
	 *         in the given array
	 * @since 1.6
	 */
	@SafeVarargs
	public static <K, V, P> IObservableMap<K, ? extends P>[] observeEach(IObservableMap<K, V> domainMap,
			IValueProperty<? super V, ? extends P>... properties) {
		@SuppressWarnings("unchecked")
		IObservableMap<K, ? extends P>[] maps = new IObservableMap[properties.length];
		for (int i = 0; i < maps.length; i++) {
			maps[i] = properties[i].observeDetail(domainMap);
		}
		return maps;
	}

	/**
	 * Returns a value property which takes the source object itself as the
	 * property value. This property may be used to wrap an object in an
	 * unmodifiable {@link IObservableValue}.
	 *
	 * @param valueType
	 *            the value type of the property
	 * @return a value property which takes the source object itself as the
	 *         property value.
	 */
	public static <T> IValueProperty<T, T> selfValue(Object valueType) {
		return new SelfValueProperty<>(valueType);
	}

	/**
	 * Returns a list property which takes the source object (a {@link List}) as
	 * the property list. This property may be used to wrap an arbitrary List
	 * instance in an {@link IObservableList}.
	 *
	 * @param elementType
	 *            the element type of the property
	 * @return a list property which takes the source object (a {@link List}) as
	 *         the property list.
	 */
	public static <E> IListProperty<List<E>, E> selfList(Object elementType) {
		return new SelfListProperty<>(elementType);
	}

	/**
	 * Returns a set property which takes the source object (a {@link Set}) as
	 * the property set. This property may be used to wrap an arbitrary Set
	 * instance in an {@link IObservableSet}.
	 *
	 * @param elementType
	 *            the element type of the property
	 * @return a set property which takes the source object (a {@link Set}) as
	 *         the property set.
	 */
	public static <E> ISetProperty<Set<E>, E> selfSet(Object elementType) {
		return new SelfSetProperty<>(elementType);
	}

	/**
	 * Returns a map property which takes the source object (a {@link Map}) as
	 * the property map. This property may be used to wrap an arbitrary Map
	 * instance in an {@link IObservableMap}.
	 *
	 * @param keyType
	 *            the key type of the property
	 * @param valueType
	 *            the value type of the property
	 * @return a map property which takes the source object (a {@link Map} as
	 *         the property map.
	 */
	public static <K, V> IMapProperty<Map<K, V>, K, V> selfMap(Object keyType, Object valueType) {
		return new SelfMapProperty<>(keyType, valueType);
	}

	/**
	 * Returns a value property which observes the value of an
	 * {@link IObservableValue}. This property may be used e.g. for observing
	 * the respective values of an {@link IObservableList} &lt;
	 * {@link IObservableValue} &gt;.
	 * <p>
	 * Calls to {@link IValueProperty#observe(Object)} or
	 * {@link IValueProperty#observe(Realm, Object)} just cast the argument to
	 * {@link IObservableValue} and return it (the realm argument is ignored).
	 *
	 * @param valueType
	 *            the value type of the property
	 * @return a value property which observes the value of an
	 *         {@link IObservableValue}.
	 */
	public static <T> IValueProperty<IObservableValue<T>, T> observableValue(Object valueType) {
		return new ObservableValueProperty<>(valueType);
	}

	/**
	 * Returns an {@link IValueProperty} whose value results from applying the given
	 * conversion function on the source object of the value property. Setting a
	 * value on the property is not supported.
	 *
	 * @param valueType value type of the property (after conversion); null if
	 *                  untyped
	 * @param converter converter to apply to the source object of the value
	 *                  property; not null
	 * @return new instance of a value property, whose value is the result of
	 *         applying the given converter to the source object
	 * @since 1.8
	 */
	public static <S, T> IValueProperty<S, T> convertedValue(Object valueType,
			Function<? super S, ? extends T> converter) {
		Objects.requireNonNull(converter);
		return new ConvertedValueProperty<>(valueType, converter);
	}

	/**
	 * Returns an untyped {@link IValueProperty}. Equivalent to
	 * {@code convertedValue(null, converter)}.
	 *
	 * @param converter see {@link #convertedValue(Object, Function)}
	 * @return see {@link #convertedValue(Object, Function)}
	 * @since 1.8
	 */
	public static <S, T> IValueProperty<S, T> convertedValue(Function<? super S, ? extends T> converter) {
		return convertedValue(null, converter);
	}
}
