/*
 * SPDX-License-Identifier: LGPL-2.1-only
 *
 * Copyright (C) 2015 EfficiOS Inc.
 * Copyright (C) 2015 Alexandre Montplaisir <alexmonthy@efficios.com>
 */

package org.lttng.ust.agent.filter;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.lttng.ust.agent.session.EventRule;

/**
 * Singleton class managing the filter notifications.
 *
 * Applications can register a {@link IFilterChangeListener} to be notified when
 * event filtering rules change in the tracing sessions.
 *
 * @author Alexandre Montplaisir
 */
public final class FilterChangeNotifier {

	/** Lazy-loaded singleton instance object */
	private static FilterChangeNotifier instance = null;

	private final Map<EventRule, Integer> enabledEventRules = new HashMap<EventRule, Integer>();
	private final Collection<IFilterChangeListener> registeredListeners = new LinkedList<IFilterChangeListener>();


	/**
	 * Private constructor, singleton class should not be instantiated directly.
	 */
	private FilterChangeNotifier() {
	}

	/**
	 * Get the singleton instance, initializing it if needed.
	 *
	 * @return The singleton instance
	 */
	public static synchronized FilterChangeNotifier getInstance() {
		if (instance == null) {
			instance = new FilterChangeNotifier();
		}
		return instance;
	}

	/**
	 * Notify the filter manager that a new rule was enabled in a tracing
	 * session ("lttng enable-event ...")
	 *
	 * This is meant to be called by the LTTng Agent only. External Java
	 * applications should not call this.
	 *
	 * @param rule
	 *            The rule that was added
	 */
	public synchronized void addEventRule(EventRule rule) {
		Integer count = enabledEventRules.get(rule);
		if (count == null) {
			/*
			 * This is the first instance of this rule being enabled. Add it to
			 * the map and send notifications to the registered notifiers.
			 */
			enabledEventRules.put(rule, Integer.valueOf(1));
			notifyForAddedRule(rule);
			return;
		}
		if (count.intValue() <= 0) {
			/* It should not have been in the map! */
			throw new IllegalStateException();
		}
		/*
		 * This exact event rule was already enabled, just increment its
		 * refcount without sending notifications
		 */
		enabledEventRules.put(rule, Integer.valueOf(count.intValue() + 1));
	}

	/**
	 * Notify the filter manager that an event name was disabled in the tracing
	 * sessions ("lttng disable-event ...").
	 *
	 * The "disable-event" only specifies an event name. This means all the
	 * rules containing this event name are to be disabled.
	 *
	 * This is meant to be called by the LTTng Agent only. External Java
	 * applications should not call this.
	 *
	 * @param eventName
	 *            The event name to disable
	 */
	public synchronized void removeEventRules(String eventName) {
		List<EventRule> rulesToRemove = new LinkedList<EventRule>();

		for (EventRule eventRule : enabledEventRules.keySet()) {
			if (eventRule.getEventName().equals(eventName)) {
				rulesToRemove.add(eventRule);
			}
		}
		/*
		 * We cannot modify the map while iterating on it. We have to do the
		 * removal separately from the iteration above.
		 */
		for (EventRule rule : rulesToRemove) {
			removeEventRule(rule);
		}
	}

	private synchronized void removeEventRule(EventRule eventRule) {
		Integer count = enabledEventRules.get(eventRule);
		if (count == null || count.intValue() <= 0) {
			/*
			 * We were asked us to disable an event rule that was not enabled
			 * previously. Command error?
			 */
			throw new IllegalStateException();
		}
		if (count.intValue() == 1) {
			/*
			 * This is the last instance of this event rule being disabled,
			 * remove it from the map and send notifications of this rule being
			 * gone.
			 */
			enabledEventRules.remove(eventRule);
			notifyForRemovedRule(eventRule);
			return;
		}
		/*
		 * Other sessions/daemons are still looking for this event rule, simply
		 * decrement its refcount, and do not send notifications.
		 */
		enabledEventRules.put(eventRule, Integer.valueOf(count.intValue() - 1));

	}

	/**
	 * Register a new listener to the manager.
	 *
	 * @param listener
	 *            The listener to add
	 */
	public synchronized void registerListener(IFilterChangeListener listener) {
		registeredListeners.add(listener);

		/* Send the current rules to the new listener ("statedump") */
		for (EventRule rule : enabledEventRules.keySet()) {
			listener.eventRuleAdded(rule);
		}
	}

	/**
	 * Unregister a listener from the manager.
	 *
	 * @param listener
	 *            The listener to remove
	 */
	public synchronized void unregisterListener(IFilterChangeListener listener) {
		registeredListeners.remove(listener);
	}

	private void notifyForAddedRule(final EventRule rule) {
		for (IFilterChangeListener notifier : registeredListeners) {
			notifier.eventRuleAdded(rule);
		}
	}

	private void notifyForRemovedRule(final EventRule rule) {
		for (IFilterChangeListener notifier : registeredListeners) {
			notifier.eventRuleRemoved(rule);
		}
	}
}
