package eu.dnetlib.miscutils.observer;

import java.util.Map;
import java.util.WeakHashMap;

/**
 * This class mimics the java.util.Observable class but implementing the strongly typed version in miscutils.
 * 
 * This particular implementation does in fact use the java.util.Observable under the hoods, by using an appropriate
 * wrapper.
 * 
 * @author marko
 * @see eu.dnetlib.miscutils.observer.Observable
 * @param <R>
 */
public class AbstractObservable<R extends Observer<? extends Observable<? extends R>>> implements Observable<R> {

	/**
	 * delegate the real job to a java.util.Observable object.
	 */
	private transient final java.util.Observable observable = new DelegationObservable();
	
	/**
	 * weak map for removing single observers later on.
	 */
	private transient Map<R, DelegationObserver<?>> observers = new WeakHashMap<R, DelegationObserver<?>>();

	/**
	 * java.util.Observable requires a "change" in the object to be made for the notifyObservers methods to trigger
	 * actually the notification. We don't think this should be part of the base Observable/Observer pattern and thus
	 * hide this fact. If an user of AbstractObservable wants this behavior it should implement her own change tracking
	 * and avoid calling notifyObservers altogether. The rationale behind this choice is that not all dirty states can
	 * be expressed using a stateful boolean instance variable like that implemented by java.util.Observable; some users
	 * may prefer/need a lazy dirty flag instead.
	 * 
	 * @author marko
	 * 
	 */
	static class DelegationObservable extends java.util.Observable {

		/**
		 * {@inheritDoc}
		 * 
		 * @see java.util.Observable#notifyObservers()
		 */
		@Override
		public void notifyObservers() {
			setChanged();
			super.notifyObservers();
		}

		/**
		 * {@inheritDoc}
		 * 
		 * @see java.util.Observable#notifyObservers(java.lang.Object)
		 */
		@Override
		public void notifyObservers(final Object arg) {
			setChanged();
			super.notifyObservers(arg);
		}

	}

	/**
	 * prevents to be instantiated.
	 */
	protected AbstractObservable() {
		// prevents to be instantiated
	}

	@SuppressWarnings("unchecked")
	public void addObserver(final R observer) {
		final DelegationObserver delegate = new DelegationObserver(this, observer);
		observers.put(observer, delegate);
		observable.addObserver(delegate);
	}

	protected void notifyObservers() {
		observable.notifyObservers();
	}

	protected void notifyObservers(final Object arg) {
		observable.notifyObservers(arg);
	}

	public int countObservers() {
		return observable.countObservers();
	}

	public void deleteObserver(final java.util.Observer observer) {
		observable.deleteObserver(observer);
	}
	
	public void deleteObserver(R observer) {
		DelegationObserver<?> delegate = observers.get(observer);
		if(delegate != null) {
			observable.deleteObserver(delegate);
		}
	}	

	public void deleteObservers() {
		observable.deleteObservers();
	}

}
