package com.rs2.event;

import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.rs2.game.players.Player;
import com.rs2.util.ClassUtils;

import java.util.Collection;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * A universal event provider which posts, provides and deprives subscribers.
 *
 * @author Ryley Kimmel <ryley.kimmel@live.com>
 */
public final class UniversalEventProvider implements EventProvider {

	/**
	 * A {@link Multimap} of {@link Event} classes to subscribers.
	 */
	private final Multimap<Class<? extends Event>, EventSubscriber<? super Event>> events = ArrayListMultimap.create();

	/**
	 * The universal context of this event provider.
	 */
	private final EventContext context = new UniversalEventContext();

	@SuppressWarnings("unchecked")
	@Override
	public void provideSubscriber(EventSubscriber<?> subscriber) {
		checkSubscriber(subscriber, annotation -> events.put(annotation.value(), (EventSubscriber<? super Event>) subscriber));
	}

	@SuppressWarnings("unchecked")
	@Override
	public void depriveSubscriber(EventSubscriber<?> subscriber) {
		checkSubscriber(subscriber, annotation -> events.remove(annotation.value(), (EventSubscriber<? super Event>) subscriber));
	}

	private void checkSubscriber(EventSubscriber<?> subscriber, Consumer<SubscribesTo> consumer) {
		Optional<SubscribesTo> optional = ClassUtils.getAnnotation(subscriber.getClass(), SubscribesTo.class);
		Preconditions.checkArgument(optional.isPresent(), String.format("%s is not annotated with @SubscribesTo", subscriber.getClass()));
		consumer.accept(optional.get());
	}

	@Override
	public <E extends Event> void post(Player player, E event) {
		Collection<EventSubscriber<? super Event>> subscribers = events.get(event.getClass());

		for (EventSubscriber<? super Event> subscriber : subscribers) {
			/* Check to be sure we can subscribe to the event. */
			if (subscriber.test(event)) {
				subscriber.subscribe(context, player, event);

				/* If the chain is broken, don't continue parsing subscribers. */
				if (context.isChainBroken()) {
					break;
				}
			}
		}

		context.repairSubscriberChain();
	}
	
	public Multimap<Class<? extends Event>, EventSubscriber<? super Event>> getEvents() {
		return events;
	}

}
