/*******************************************************************************
 * Copyright (c) 2005, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Brad Reynolds - bug 159539
 *     Brad Reynolds - bug 140644
 *     Brad Reynolds - bug 159940
 *     Brad Reynolds - bug 116920, 159768
 *******************************************************************************/
package org.eclipse.core.databinding;

import java.util.Iterator;

import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.internal.databinding.ValidationStatusMap;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;

/**
 * A DataBindingContext is the point of contact for the creation and management of
 * {@link Binding bindings}.
 * <p>
 * A DataBindingContext provides the following abilities:
 * <ul>
 * <li>Ability to create bindings between
 * {@link IObservableValue observable values}.</li>
 * <li>Ability to create bindings between
 * {@link IObservableList observable lists}.</li>
 * <li>Access to the bindings created by the instance.</li>
 * <li>Access to the validation status of its bindings.</li>
 * </ul>
 * </p>
 * <p>
 * Multiple contexts can be used at any point in time. One strategy for the
 * management of contexts is the aggregation of validation statuses. For example
 * an <code>IWizardPage</code> could use a single context and the statuses
 * could be aggregated to set the page status and fulfillment. Each page in the
 * <code>IWizard</code> would have its own context instance.
 * </p>
 * 
 * @since 1.0
 */
public class DataBindingContext {
	private WritableList bindings;

	/**
	 * Unmodifiable version of {@link #bindings} for public exposure.
	 */
	private IObservableList unmodifiableBindings;

	private IObservableMap validationStatusMap;

	private Realm validationRealm;

	/**
	 * Creates a data binding context, using the current default realm for the
	 * validation observables.
	 * 
	 * @see Realm
	 */
	public DataBindingContext() {
		this(Realm.getDefault());
	}

	/**
	 * Creates a data binding context using the given realm for the validation
	 * observables.
	 * 
	 * @param validationRealm
	 *            the realm to be used for the validation observables
	 * 
	 * @see Realm
	 */
	public DataBindingContext(Realm validationRealm) {
		Assert.isNotNull(validationRealm);
		this.validationRealm = validationRealm;
		bindings = new WritableList(validationRealm);

		unmodifiableBindings = Observables.unmodifiableObservableList(bindings);
		validationStatusMap = new ValidationStatusMap(validationRealm,
				bindings);
	}

	/**
	 * Creates a {@link Binding} to synchronize the values of two
	 * {@link IObservableValue observable values}. During synchronization
	 * validation and conversion can be employed to customize the process. For
	 * specifics on the customization of the process see
	 * {@link UpdateValueStrategy}.
	 * 
	 * @param targetObservableValue
	 *            target value, commonly a UI widget
	 * @param modelObservableValue
	 *            model value
	 * @param targetToModel
	 *            strategy to employ when the target is the source of the change
	 *            and the model is the destination
	 * @param modelToTarget
	 *            strategy to employ when the model is the source of the change
	 *            and the target is the destination
	 * @return created binding
	 * 
	 * @see UpdateValueStrategy
	 */
	public final Binding bindValue(IObservableValue targetObservableValue,
			IObservableValue modelObservableValue,
			UpdateValueStrategy targetToModel, UpdateValueStrategy modelToTarget) {
		UpdateValueStrategy targetToModelStrategy = targetToModel != null ? targetToModel
						: createTargetToModelUpdateValueStrategy(targetObservableValue, modelObservableValue);
		UpdateValueStrategy modelToTargetStrategy = modelToTarget != null ? modelToTarget
				: createModelToTargetUpdateValueStrategy(modelObservableValue, targetObservableValue);
		targetToModelStrategy.fillDefaults(targetObservableValue, modelObservableValue);
		modelToTargetStrategy.fillDefaults(modelObservableValue, targetObservableValue);
		ValueBinding result = new ValueBinding(targetObservableValue,
				modelObservableValue, targetToModelStrategy,
				modelToTargetStrategy);
		result.init(this);
		return result;
	}

	/**
	 * Returns an update value strategy to be used for copying values from the
	 * from value to the to value. Clients may override.
	 * 
	 * @param fromValue
	 * @param toValue
	 * @return a update value strategy
	 */
	protected UpdateValueStrategy createModelToTargetUpdateValueStrategy(
			IObservableValue fromValue, IObservableValue toValue) {
		return new UpdateValueStrategy();
	}

	/**
	 * Returns an update value strategy to be used for copying values from the
	 * from value to the to value. Clients may override.
	 * 
	 * @param fromValue
	 * @param toValue
	 * @return a update value strategy
	 */
	protected UpdateValueStrategy createTargetToModelUpdateValueStrategy(
			IObservableValue fromValue, IObservableValue toValue) {
		return new UpdateValueStrategy();
	}
	
	/**
	 * Creates a {@link Binding} to synchronize the values of two
	 * {@link IObservableList observable lists}. During synchronization
	 * validation and conversion can be employed to customize the process. For
	 * specifics on the customization of the process see
	 * {@link UpdateListStrategy}.
	 * 
	 * @param targetObservableList
	 *            target list, commonly a list representing a list in the UI
	 * @param modelObservableList
	 *            model list
	 * @param targetToModel
	 *            strategy to employ when the target is the source of the change
	 *            and the model is the destination
	 * @param modelToTarget
	 *            strategy to employ when the model is the source of the change
	 *            and the target is the destination
	 * @return created binding
	 * 
	 * @see UpdateListStrategy
	 */
	public final Binding bindList(IObservableList targetObservableList,
			IObservableList modelObservableList,
			UpdateListStrategy targetToModel, UpdateListStrategy modelToTarget) {
		UpdateListStrategy targetToModelStrategy = targetToModel != null ? targetToModel
				: createTargetToModelUpdateListStrategy(targetObservableList,
						modelObservableList);
		UpdateListStrategy modelToTargetStrategy = modelToTarget != null ? modelToTarget
				: createModelToTargetUpdateListStrategy(modelObservableList,
						targetObservableList);
		targetToModelStrategy.fillDefaults(targetObservableList,
				modelObservableList);
		modelToTargetStrategy.fillDefaults(modelObservableList,
				targetObservableList);
		ListBinding result = new ListBinding(targetObservableList,
				modelObservableList, targetToModelStrategy,
				modelToTargetStrategy);
		result.init(this);
		return result;
	}

	/**
	 * @param modelObservableList
	 * @param targetObservableList
	 * @return an update list strategy
	 */
	protected UpdateListStrategy createModelToTargetUpdateListStrategy(
			IObservableList modelObservableList,
			IObservableList targetObservableList) {
		return new UpdateListStrategy();
	}

	/**
	 * @param targetObservableList
	 * @param modelObservableList
	 * @return an update list strategy 
	 */
	protected UpdateListStrategy createTargetToModelUpdateListStrategy(
			IObservableList targetObservableList,
			IObservableList modelObservableList) {
		return new UpdateListStrategy();
	}

	/**
	 * Disposes of this data binding context and all bindings that were added to
	 * this context.
	 */
	public final void dispose() {
		Binding[] bindingArray = (Binding[]) bindings.toArray(new Binding[bindings.size()]);
		for (int i = 0; i < bindingArray.length; i++) {
			bindingArray[i].dispose();
		}
	}

	/**
	 * Returns an unmodifiable observable list with elements of type
	 * {@link Binding}, ordered by time of addition.
	 * 
	 * @return the observable list containing all bindings
	 */
	public final IObservableList getBindings() {
		return unmodifiableBindings;
	}

	/**
	 * Returns an observable map from bindings (type: {@link Binding}) to
	 * statuses (type: {@link IStatus}). The keys of the map are the bindings
	 * returned by {@link #getBindings()}, and the values are the current
	 * validaion status objects for each binding.
	 * 
	 * @return the observable map from bindings to status objects.
	 */
	public final IObservableMap getValidationStatusMap() {
		return validationStatusMap;
	}

	/**
	 * Adds the given binding to this data binding context.
	 * 
	 * @param binding
	 *            The binding to add.
	 */
	public void addBinding(Binding binding) {
		bindings.add(binding);
	}

	/**
	 * Updates all model observable objects to reflect the current state of the
	 * target observable objects.
	 * 
	 */
	public final void updateModels() {
		for (Iterator it = bindings.iterator(); it.hasNext();) {
			Binding binding = (Binding) it.next();
			binding.updateTargetToModel();
		}
	}

	/**
	 * Updates all target observable objects to reflect the current state of the
	 * model observable objects.
	 * 
	 */
	public final void updateTargets() {
		for (Iterator it = bindings.iterator(); it.hasNext();) {
			Binding binding = (Binding) it.next();
			binding.updateModelToTarget();
		}
	}

	/**
	 * Removes the given binding.
	 * 
	 * @param binding
	 * @return <code>true</code> if was associated with the context,
	 *         <code>false</code> if not
	 */
	public boolean removeBinding(Binding binding) {
		return bindings.remove(binding);
	}

	/**
	 * Returns the validation realm.
	 * 
	 * @return the realm for the validation observables
	 * @see Realm
	 */
	public final Realm getValidationRealm() {
		return validationRealm;
	}
}
