/*******************************************************************************
 * Copyright (c) 2008, 2017 Marko Topolnik 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:
 *     Marko Topolnik - initial API and implementation (bug 184830)
 *     Matthew Hall - bug 184830
 *     Stefan Xenos <sxenos@gmail.com> - Bug 335792
 ******************************************************************************/

package org.eclipse.core.internal.databinding.observable;

import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.IStaleListener;
import org.eclipse.core.databinding.observable.ObservableTracker;
import org.eclipse.core.databinding.observable.map.IMapChangeListener;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;

/**
 * An {@link IObservableValue} that tracks the value of an entry in an
 * {@link IObservableMap}, identified by the entry's key.
 *
 * @param <K>
 *            the type of the keys in this map
 * @param <V>
 *            the type of the values in this map
 *
 * @since 1.1
 */
public class MapEntryObservableValue<K, V> extends AbstractObservableValue<V> {
	private IObservableMap<K, V> map;
	private K key;
	private Object valueType;

	private IMapChangeListener<K, V> changeListener = event -> {
		if (event.diff.getAddedKeys().contains(key)) {
			final V newValue = event.diff.getNewValue(key);
			if (newValue != null) {
				fireValueChange(Diffs.createValueDiff(null, newValue));
			}
		} else if (event.diff.getChangedKeys().contains(key)) {
			fireValueChange(Diffs.createValueDiff(event.diff.getOldValue(key), event.diff.getNewValue(key)));
		} else if (event.diff.getRemovedKeys().contains(key)) {
			final V oldValue = event.diff.getOldValue(key);
			if (oldValue != null) {
				fireValueChange(Diffs.createValueDiff(oldValue, null));
			}
		}
	};

	private IStaleListener staleListener = staleEvent -> fireStale();

	/**
	 * Creates a map entry observable.
	 *
	 * @param map
	 *            the observable map whose entry will be tracked
	 * @param key
	 *            the key identifying the entry whose value will be tracked
	 * @param valueType
	 *            the type of the value
	 */
	public MapEntryObservableValue(IObservableMap<K, V> map, K key,
			Object valueType) {
		super(map.getRealm());
		this.map = map;
		this.key = key;
		this.valueType = valueType;

		map.addMapChangeListener(changeListener);
		map.addStaleListener(staleListener);
	}

	@Override
	public Object getValueType() {
		return this.valueType;
	}

	@Override
	public boolean isStale() {
		ObservableTracker.getterCalled(this);
		return map.isStale();
	}

	@Override
	public synchronized void dispose() {
		if (map != null) {
			map.removeMapChangeListener(changeListener);
			map.removeStaleListener(staleListener);
			map = null;
			changeListener = null;
			staleListener = null;
		}
		super.dispose();
	}

	@Override
	protected V doGetValue() {
		return this.map.get(this.key);
	}

	@Override
	protected void doSetValue(V value) {
		this.map.put(this.key, value);
	}
}