/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation 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:
 * IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.core.subscribers;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;

/**
 * An abstract class that managers a collection of change sets.
 */
public abstract class ChangeSetManager {

    private ListenerList<IChangeSetChangeListener> listeners = new ListenerList<IChangeSetChangeListener>(ListenerList.IDENTITY);
    private Set<ChangeSet> sets;
	private boolean initializing;

    /**
     * Return the list of listeners registered with this change set manager.
     * @return the list of listeners registered with this change set manager
     */
    protected Object[] getListeners() {
        return listeners.getListeners();
    }

    /**
     * Method that can be invoked by subclasses when the name of
     * a managed change set changes.
     * @param set the set whose title has changed
     */
    protected void fireNameChangedEvent(final ChangeSet set) {
    	if (initializing)
    		return;
        if (contains(set)) {
            Object[] listeners = getListeners();
            for (int i = 0; i < listeners.length; i++) {
                final IChangeSetChangeListener listener = (IChangeSetChangeListener)listeners[i];
                SafeRunner.run(new ISafeRunnable() {
                    @Override
					public void handleException(Throwable exception) {
                        // Exceptions are logged by the platform
                    }
                    @Override
					public void run() throws Exception {
                        listener.nameChanged(set);
                    }
                });
            }
        }
    }

    /**
     * Method which allows subclasses to notify listeners that the default
     * set has changed.
     * @param oldSet the previous default
     * @param defaultSet the new default
     */
    protected void fireDefaultChangedEvent(final ChangeSet oldSet, final ChangeSet defaultSet) {
    	if (initializing)
    		return;
        Object[] listeners = getListeners();
        for (int i = 0; i < listeners.length; i++) {
            final IChangeSetChangeListener listener = (IChangeSetChangeListener)listeners[i];
            SafeRunner.run(new ISafeRunnable() {
                @Override
				public void handleException(Throwable exception) {
                    // Exceptions are logged by the platform
                }
                @Override
				public void run() throws Exception {
                    listener.defaultSetChanged(oldSet, defaultSet);
                }
            });
        }
    }

    /**
     * Add the set to the list of active sets.
     * @param set the set to be added
     */
    public void add(final ChangeSet set) {
        if (!contains(set)) {
        	internalGetSets().add(set);
            handleSetAdded(set);
        }
    }

    /**
     * Handle the set addition by notifying listeners.
     * @param set the added set
     */
	protected void handleSetAdded(final ChangeSet set) {
    	if (initializing)
    		return;
		Object[] listeners = getListeners();
		for (int i = 0; i < listeners.length; i++) {
		    final IChangeSetChangeListener listener = (IChangeSetChangeListener)listeners[i];
		    SafeRunner.run(new ISafeRunnable() {
		        @Override
				public void handleException(Throwable exception) {
		            // Exceptions are logged by the platform
		        }
		        @Override
				public void run() throws Exception {
		            listener.setAdded(set);
		        }
		    });
		}
	}

    /**
     * Remove the set from the list of active sets.
     * @param set the set to be removed
     */
    public void remove(final ChangeSet set) {
        if (contains(set)) {
        	internalGetSets().remove(set);
            handleSetRemoved(set);
        }
    }

    /**
     * Handle the set removal by notifying listeners.
     * @param set the removed set
     */
	protected void handleSetRemoved(final ChangeSet set) {
    	if (initializing)
    		return;
		Object[] listeners = getListeners();
		for (int i = 0; i < listeners.length; i++) {
		    final IChangeSetChangeListener listener = (IChangeSetChangeListener)listeners[i];
		    SafeRunner.run(new ISafeRunnable() {
		        @Override
				public void handleException(Throwable exception) {
		            // Exceptions are logged by the platform
		        }
		        @Override
				public void run() throws Exception {
		            listener.setRemoved(set);
		        }
		    });
		}
	}

    /**
     * Return whether the manager contains the given commit set
     * @param set the commit set being tested
     * @return whether the set is contained in the manager's list of active sets
     */
    public boolean contains(ChangeSet set) {
        return internalGetSets().contains(set);
    }

    /**
     * Add the listener to the set of registered listeners.
     * @param listener the listener to be added
     */
    public void addListener(IChangeSetChangeListener listener) {
        listeners.add(listener);
    }

    /**
     * Remove the listener from the set of registered listeners.
     * @param listener the listener to remove
     */
    public void removeListener(IChangeSetChangeListener listener) {
        listeners.remove(listener);
    }

    /**
     * Return the list of active commit sets.
     * @return the list of active commit sets
     */
    public ChangeSet[] getSets() {
        Set<ChangeSet> sets = internalGetSets();
		return sets.toArray(new ChangeSet[sets.size()]);
    }

    /**
     * Dispose of any resources maintained by the manager
     */
    public void dispose() {
        // Nothing to do
    }

    /**
     * Fire resource change notifications to the listeners.
     * @param changeSet
     * @param allAffectedResources
     */
    protected void fireResourcesChangedEvent(final ChangeSet changeSet, final IPath[] allAffectedResources) {
    	if (initializing)
    		return;
        Object[] listeners = getListeners();
        for (int i = 0; i < listeners.length; i++) {
            final IChangeSetChangeListener listener = (IChangeSetChangeListener)listeners[i];
            SafeRunner.run(new ISafeRunnable() {
                @Override
				public void handleException(Throwable exception) {
                    // Exceptions are logged by the platform
                }
                @Override
				public void run() throws Exception {
                    listener.resourcesChanged(changeSet, allAffectedResources);
                }
            });
        }
    }

    private Set<ChangeSet> internalGetSets() {
    	if (sets == null) {
    		sets = Collections.synchronizedSet(new HashSet<>());
    		try {
    			initializing = true;
    			initializeSets();
    		} finally {
    			initializing = false;
    		}
    	}
    	return sets;
    }

    /**
     * Initialize the sets contained in this manager.
     * This method is called the first time the sets are accessed.
     */
	protected abstract void initializeSets();

	public boolean isInitialized() {
		return sets != null;
	}
}
