package ru.rsdn.patterns.behavioral.observer;

import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Множественный издатель
 */
public abstract class MultiPublisher {
	@SuppressWarnings("unchecked")
	private final Map<Class, Collection<WeakReference<Object>>> subscribers = new ConcurrentHashMap<Class, Collection<WeakReference<Object>>>();

	/**
	 * Подписаться
	 * @param <S> тип подписчика
	 * @param _class интерфейс подписки
	 * @param subscriber подписчик
	 */
	protected <S> void subscribe(Class<? super S> _class, S subscriber) {
		if (_class == null)
			throw new NullPointerException("Пустой параметр _class");
		if (subscriber == null)
			throw new NullPointerException("Пустой параметр subscriber");
		if (!subscribers.containsKey(_class))
			subscribers.put(_class,
					new CopyOnWriteArrayList<WeakReference<Object>>());
		subscribers.get(_class).add(new WeakReference<Object>(subscriber));
	}

	/**
	 * Отписаться
	 * @param <S> тип подписчика
	 * @param _class интерфейс подписки
	 * @param subscriber подписчик
	 */
	protected <S> void unsubscribe(Class<? super S> _class, S subscriber) {
		if (_class == null)
			throw new NullPointerException("Пустой параметр _class");
		if (subscriber == null)
			throw new NullPointerException("Пустой параметр subscriber");
		if (!subscribers.containsKey(_class))
			throw new IllegalArgumentException("Неизвестный тип подписки: " +
					_class);
		final Collection<WeakReference<Object>> _subscribers = subscribers
				.get(_class);
		for (final WeakReference<Object> ref : _subscribers) {
			final Object _subscriber = ref.get();
			if (_subscriber == null || _subscriber.equals(subscriber))
				_subscribers.remove(ref);
		}
	}

	/**
	 * @param <S> тип подписчиков
	 * @param _class интерфейс подписки
	 * @return подписчики
	 */
	@SuppressWarnings("unchecked")
	protected <S> Collection<S> getSubscribers(Class<? super S> _class) {
		final Collection<S> result = new HashSet<S>();
		if (subscribers.containsKey(_class)) {
			final Collection<WeakReference<Object>> _subscribers = subscribers
					.get(_class);
			for (final WeakReference<Object> ref : _subscribers) {
				final S subscriber = (S) ref.get();
				if (subscriber == null)
					_subscribers.remove(ref);
				else
					result.add(subscriber);
			}
		}
		return result;
	}
}
