/*******************************************************************************
 * Copyright (c) 2011 Tasktop Technologies and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.internal.tasks.core.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants;
import org.eclipse.mylyn.internal.tasks.core.RepositoryModel;
import org.eclipse.mylyn.tasks.core.data.ITaskAttributeDiff;
import org.eclipse.mylyn.tasks.core.data.ITaskDataDiff;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.sync.SynchronizationParticipant;
import org.eclipse.osgi.util.NLS;

/**
 * @author Steffen Pingel
 */
public class SynchronizationManger {

	public static class DefaultParticipant extends SynchronizationParticipant {

		private final Set<String> attributeIds;

		public DefaultParticipant(List<String> attributeIds, String connectorKind) {
			Assert.isNotNull(attributeIds);
			this.attributeIds = new HashSet<String>(attributeIds);
			String id = DefaultParticipant.class.getName();
			if (connectorKind != null) {
				id += "." + connectorKind; //$NON-NLS-1$
			}
			setId(id);
		}

		@Override
		public void processUpdate(ITaskDataDiff diff, IProgressMonitor monitor) {
			if (diff.getChangedAttributes().size() > 0) {
				for (Iterator<ITaskAttributeDiff> it = diff.getChangedAttributes().iterator(); it.hasNext();) {
					ITaskAttributeDiff attributeDiff = it.next();
					if (attributeIds.contains(attributeDiff.getAttributeId())) {
						it.remove();
					}
				}
				diff.setHasChanged(diff.getChangedAttributes().size() > 0 || diff.getNewComments().size() > 0);
			}
		}

	}

	private final Map<String, List<SynchronizationParticipant>> participantsByConnectorKind = new HashMap<String, List<SynchronizationParticipant>>();

	private List<SynchronizationParticipant> defaultParticipants;

	private final RepositoryModel model;

	public SynchronizationManger(RepositoryModel model) {
		Assert.isNotNull(model);
		this.model = model;
	}

	private List<SynchronizationParticipant> loadParticipants(String connectorKind) {
		MultiStatus status = new MultiStatus(ITasksCoreConstants.ID_PLUGIN, 0,
				"Synchronization participants failed to load.", null); //$NON-NLS-1$

		List<SynchronizationParticipant> participants = new ArrayList<SynchronizationParticipant>(1);
		List<String> attributeIds = new ArrayList<String>();

		IExtensionRegistry registry = Platform.getExtensionRegistry();
		IExtensionPoint connectorsExtensionPoint = registry.getExtensionPoint(ITasksCoreConstants.ID_PLUGIN
				+ ".synchronizationParticipants"); //$NON-NLS-1$
		IExtension[] extensions = connectorsExtensionPoint.getExtensions();
		for (IExtension extension : extensions) {
			IConfigurationElement[] elements = extension.getConfigurationElements();
			for (IConfigurationElement element : elements) {
				if ("participant".equals(element.getName())) { //$NON-NLS-1$
					String value = element.getAttribute("connectorKind"); //$NON-NLS-1$
					if (value != null && value.equals(connectorKind) || value == connectorKind) {
						try {
							Object object = element.createExecutableExtension("class"); //$NON-NLS-1$
							if (object instanceof SynchronizationParticipant) {
								SynchronizationParticipant participant = (SynchronizationParticipant) object;
								participant.setId(element.getAttribute("id")); //$NON-NLS-1$
								participants.add(participant);
							} else {
								status.add(new Status(
										IStatus.ERROR,
										ITasksCoreConstants.ID_PLUGIN,
										NLS.bind(
												"Connector core ''{0}'' does not extend expected class for extension contributed by {1}", //$NON-NLS-1$
												object.getClass().getCanonicalName(), element.getContributor()
														.getName())));
							}
						} catch (Throwable e) {
							status.add(new Status(
									IStatus.ERROR,
									ITasksCoreConstants.ID_PLUGIN,
									NLS.bind(
											"Connector core failed to load for extension contributed by {0}", element.getContributor().getName()), e)); //$NON-NLS-1$
						}
					}
				} else if ("suppressIncoming".equals(element.getName())) { //$NON-NLS-1$
					String value = element.getAttribute("connectorKind"); //$NON-NLS-1$
					if (value != null && value.equals(connectorKind) || value == connectorKind) {
						String attributeId = element.getAttribute("attributeId"); //$NON-NLS-1$
						if (attributeId != null) {
							attributeIds.add(attributeId);
						}
					}
				}
			}
		}

		if (attributeIds.size() > 0) {
			participants.add(new DefaultParticipant(attributeIds, connectorKind));
		}

		if (!status.isOK()) {
			StatusHandler.log(status);
		}

		return participants;
	}

	public synchronized boolean hasParticipants(String connectorKind) {
		return getDefaultParticipants().size() > 0 || getParticipants(connectorKind).size() > 0;
	}

	public synchronized List<SynchronizationParticipant> getParticipants(String connectorKind) {
		List<SynchronizationParticipant> participants = participantsByConnectorKind.get(connectorKind);
		if (participants == null) {
			participants = loadParticipants(connectorKind);
			participantsByConnectorKind.put(connectorKind, participants);
		}
		return participants;
	}

	public synchronized List<SynchronizationParticipant> getDefaultParticipants() {
		if (defaultParticipants == null) {
			defaultParticipants = loadParticipants(null);
		}
		return defaultParticipants;
	}

	public synchronized List<SynchronizationParticipant> getIgnoredIncomings(String connectorKind) {
		List<SynchronizationParticipant> participants = participantsByConnectorKind.get(connectorKind);
		if (participants == null) {
			participants = loadParticipants(connectorKind);
			participantsByConnectorKind.put(connectorKind, participants);
		}
		return participants;
	}

	public TaskDataDiff createDiff(TaskData newTaskData, TaskData oldTaskData, final IProgressMonitor monitor) {
		final TaskDataDiff diff = new TaskDataDiff(model, newTaskData, oldTaskData);
		for (final SynchronizationParticipant participant : getDefaultParticipants()) {
			SafeRunner.run(new ISafeRunnable() {
				public void handleException(Throwable exception) {
					// handled by framework
				}

				public void run() throws Exception {
					participant.processUpdate(diff, monitor);
				}
			});
		}
		for (final SynchronizationParticipant participant : getParticipants(newTaskData.getConnectorKind())) {
			SafeRunner.run(new ISafeRunnable() {
				public void handleException(Throwable exception) {
					// handled by framework
				}

				public void run() throws Exception {
					participant.processUpdate(diff, monitor);
				}
			});
		}
		return diff;
	}
}
