/*******************************************************************************
 * Copyright (c) 2006 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.html.core.internal.contentproperties;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.html.core.internal.HTMLCoreMessages;
import org.eclipse.wst.html.core.internal.Logger;
import org.osgi.framework.Bundle;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

public class HTMLContentPropertiesManager {
	public HTMLContentPropertiesManager() {
		super();
		fResourceChangeListener = new ResourceChangeListener();
		fJob = new ContentPropertiesManagerJob();
	}

	private static HTMLContentPropertiesManager _instance = null;
	private IResourceChangeListener fResourceChangeListener;
	ContentPropertiesManagerJob fJob;

	/**
	 * This job implementation is used to allow the resource change listener
	 * to schedule operations that need to modify the workspace.
	 */
	private class ContentPropertiesManagerJob extends Job {
		private static final int PROPERTIES_UPDATE_DELAY = 500;
		private List asyncChanges = new ArrayList();

		public ContentPropertiesManagerJob() {
			super(HTMLCoreMessages.HTMLContentPropertiesManager_Updating);
			setSystem(true);
			setPriority(Job.INTERACTIVE);
		}

		public void addChanges(Set newChanges) {
			if (newChanges.isEmpty())
				return;
			synchronized (asyncChanges) {
				asyncChanges.addAll(newChanges);
				asyncChanges.notify();
			}
			schedule(PROPERTIES_UPDATE_DELAY);
		}

		public IProject getNextChange() {
			synchronized (asyncChanges) {
				return asyncChanges.isEmpty() ? null : (IProject) asyncChanges.remove(asyncChanges.size() - 1);
			}
		}

		protected IStatus run(IProgressMonitor monitor) {
			MultiStatus result = new MultiStatus(HTMLContentProperties.HTMLCORE_ID, IResourceStatus.FAILED_SETTING_CHARSET, HTMLCoreMessages.HTMLContentPropertiesManager_Updating, null);
			monitor = monitor == null ? new NullProgressMonitor() : monitor;
			try {
				monitor.beginTask(HTMLCoreMessages.HTMLContentPropertiesManager_Updating, asyncChanges.size());
				try {
					IProject next;
					while ((next = getNextChange()) != null) {
						// just exit if the system is shutting down or has
						// been shut down
						// it is too late to change the workspace at this
						// point anyway
						if (Platform.getBundle("org.eclipse.osgi").getState() != Bundle.ACTIVE) //$NON-NLS-1$
							return Status.OK_STATUS;
						try {
							// save the preferences nodes
							if (next.isAccessible()) {
								// save document type preferences
								Preferences projectPrefs = HTMLContentProperties.getPreferences(next, HTMLContentProperties.DOCUMENT_TYPE, false);
								if (projectPrefs != null)
									projectPrefs.flush();
							}
						}
						catch (BackingStoreException e) {
							// we got an error saving
							String detailMessage = NLS.bind(HTMLCoreMessages.HTMLContentPropertiesManager_Problems_Updating, next.getFullPath());
							result.add(new Status(1 << (IResourceStatus.FAILED_SETTING_CHARSET % 100 / 33), ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_SETTING_CHARSET, detailMessage, e));
						}
					}
					monitor.worked(1);
				}
				catch (OperationCanceledException e) {
					throw e;
				}
			}
			finally {
				monitor.done();
			}
			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
		 */
		public boolean shouldRun() {
			synchronized (asyncChanges) {
				return !asyncChanges.isEmpty();
			}
		}
	}

	class ResourceChangeListener implements IResourceChangeListener {
		private void processEntryChanges(IResourceDelta projectDelta, Set projectsToSave) {
			// check each resource with web content setting to see if it has
			// been moved/deleted
			boolean resourceChanges = false;

			// project affected
			IProject currentProject = (IProject) projectDelta.getResource();

			resourceChanges = processPreferences(currentProject, HTMLContentProperties.DOCUMENT_TYPE, projectDelta, projectsToSave);

			// if there was a preference key change, need to save preferences
			if (resourceChanges)
				projectsToSave.add(currentProject);
		}

		/**
		 * Goes through all the resource-dependent preferences associated with
		 * currentProject & key and updates the preference keys if needed
		 * based on projectDelta
		 * 
		 * @param currentProject
		 *            current project of the preferences to be looked at
		 * @param key
		 *            current key/subcategory of the preferences to be looked
		 *            at
		 * @param projectDelta
		 *            the changes to process the preference keys against
		 * @param projectsToSave
		 *            the projects that need to be updated/saved
		 * @return true if currentProject's preferences were modified
		 */
		private boolean processPreferences(IProject currentProject, String key, IResourceDelta projectDelta, Set projectsToSave) {
			boolean resourceChanges = false;

			// get the project-key preference node
			Preferences projectPrefs = HTMLContentProperties.getPreferences(currentProject, key, false);
			if (projectPrefs == null)
				// no preferences for this project-key, just bail
				return false;
			String[] affectedResources;
			try {
				affectedResources = projectPrefs.keys();
			}
			catch (BackingStoreException e) {
				// problems with the project scope... we gonna miss the
				// changes (but will log)
				Logger.log(Logger.WARNING_DEBUG, "Problem retreiving JSP Fragment preferences", e); //$NON-NLS-1$
				return false;
			}

			// go through each preference key (which is really a file name)
			for (int i = 0; i < affectedResources.length; i++) {
				// see if preference key/file name was file that was changed
				IResourceDelta memberDelta = projectDelta.findMember(new Path(affectedResources[i]));
				// no changes for the given resource
				if (memberDelta == null)
					continue;
				if (memberDelta.getKind() == IResourceDelta.REMOVED) {
					resourceChanges = true;
					// remove the setting for the original location
					String currentValue = projectPrefs.get(affectedResources[i], null);
					projectPrefs.remove(affectedResources[i]);
					if ((memberDelta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
						// if moving, copy the setting for the new location
						IProject targetProject = ResourcesPlugin.getWorkspace().getRoot().getProject(memberDelta.getMovedToPath().segment(0));
						Preferences targetPrefs = HTMLContentProperties.getPreferences(targetProject, key, true);
						targetPrefs.put(HTMLContentProperties.getKeyFor(memberDelta.getMovedToPath()), currentValue);
						if (targetProject != currentProject)
							projectsToSave.add(targetProject);
					}
				}
			}
			return resourceChanges;
		}

		/**
		 * For any change to the encoding file or any resource with encoding
		 * set, just discard the cache for the corresponding project.
		 */
		public void resourceChanged(IResourceChangeEvent event) {
			IResourceDelta delta = event.getDelta();
			if (delta == null)
				return;
			IResourceDelta[] projectDeltas = delta.getAffectedChildren();
			// process each project in the delta
			Set projectsToSave = new HashSet();
			for (int i = 0; i < projectDeltas.length; i++)
				// nothing to do if a project has been added/removed/moved
				if (projectDeltas[i].getKind() == IResourceDelta.CHANGED && (projectDeltas[i].getFlags() & IResourceDelta.OPEN) == 0)
					processEntryChanges(projectDeltas[i], projectsToSave);
			fJob.addChanges(projectsToSave);
		}
	}

	public synchronized static void startup() {
		_instance = new HTMLContentPropertiesManager();
		ResourcesPlugin.getWorkspace().addResourceChangeListener(_instance.fResourceChangeListener, IResourceChangeEvent.POST_CHANGE);
	}

	public synchronized static void shutdown() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(_instance.fResourceChangeListener);
		_instance = null;
	}
}
