//------------------------------------------------------------------------------
// Copyright (c) 2005, 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 implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.persistence.refresh;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.epf.persistence.FileManager;
import org.eclipse.epf.persistence.MultiFileResourceSetImpl;

/**
 * @author Phong Nguyen Le
 * @since 1.0
 */
public class RefreshJob extends WorkspaceJob implements IResourceChangeListener {

	private static final long UPDATE_DELAY = 200;

	private static final boolean DEBUG = org.eclipse.epf.common.CommonPlugin
			.getDefault().isDebugging();

	private static final String DEBUG_PREFIX = "EPF Auto-refresh:"; //$NON-NLS-1$

	private ResourceSet resourceSet;

	private Collection addedResources = new UniqueEList();

	private Collection changedResources = new UniqueEList();

	private Collection removedResources = new UniqueEList();

	private UniqueEList savedResources = new UniqueEList();

	private Collection loadedBeforeRefreshResources = new ArrayList();

	private IRefreshHandler refreshHandler;

	private boolean enabled = true;

	private Collection addedWorkspaceResources = new UniqueEList();

	private RefreshJob() {
		super("EPF Auto-Refresh"); //$NON-NLS-1$
	}

	public void setEnabled(boolean b) {
		enabled = b;
	}

	public boolean isEnabled() {
		return enabled;
	}

	/**
	 * @param resourceSet
	 *            The resourceSet to set.
	 */
	public void setResourceSet(ResourceSet resourceSet) {
		this.resourceSet = resourceSet;
	}

	public void setRefreshHandler(IRefreshHandler handler) {
		refreshHandler = handler;
	}

	/**
	 * Gets existing resources that reappear in workspaces
	 * 
	 * @return the addedResources
	 */
	public Collection getAddedResources() {
		return addedResources;
	}

	
	public Collection getAddedWorkspaceResources() {
		return addedWorkspaceResources;
	}

	/**
	 * @return Returns the changedResources.
	 */
	public Collection getChangedResources() {
		removeFromChangedResources(savedResources);
		removeFromChangedResources(loadedBeforeRefreshResources);
		return changedResources;
	}

	private void removeFromChangedResources(Collection resources) {
		synchronized (resources) {
			if (!resources.isEmpty()) {
				for (Iterator iter = resources.iterator(); iter.hasNext();) {
					Object resource = iter.next();
					if (changedResources.remove(resource)) {
						iter.remove();
					}
				}
			}
		}
	}

	/**
	 * @return Returns the removedResources.
	 */
	public Collection getRemovedResources() {
		return removedResources;
	}

	public void resourceSaved(Resource resource) {
		synchronized (savedResources) {
			savedResources.add(resource);
		}
	}

	/**
	 * @return the loadedBeforeRefreshResources
	 */
	public Collection getReloadedBeforeRefreshResources() {
		return loadedBeforeRefreshResources;
	}

	public void reset() {
		changedResources.clear();
		removedResources.clear();
		savedResources.clear();
		loadedBeforeRefreshResources.clear();
		addedResources.clear();
	}

	private void scheduleRefresh() {
		if (getState() == Job.NONE) {
			schedule(UPDATE_DELAY);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see WorkspaceJob#runInWorkspace
	 */
	public IStatus runInWorkspace(IProgressMonitor monitor) {
		if (refreshHandler == null)
			return Status.OK_STATUS;

		long start = System.currentTimeMillis();
		Throwable error = null;
		try {
			if (DEBUG)
				System.out.println(DEBUG_PREFIX + " starting refresh job"); //$NON-NLS-1$
			monitor.beginTask("", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
			if (monitor.isCanceled())
				throw new OperationCanceledException();
			try {
				refreshHandler.refresh(monitor);
			} catch (Throwable e) {
				error = e;
			}
		} finally {
			monitor.done();
			if (DEBUG)
				System.out
						.println(DEBUG_PREFIX
								+ " finished refresh job in: " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
		}
		if (error != null)
			return new Status(IStatus.ERROR, FileManager.PLUGIN_ID, 0,
					"Refresh error", error); //$NON-NLS-1$
		return Status.OK_STATUS;
	}

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

	private boolean shouldRefresh() {
		return !removedResources.isEmpty() || !changedResources.isEmpty()
				|| !addedResources.isEmpty()
				|| !loadedBeforeRefreshResources.isEmpty()
				|| !addedWorkspaceResources.isEmpty();
	}

	/**
	 * Starts the refresh job
	 */
	public void start() {
		if (DEBUG) {
			System.out.println("RefreshJob.start()"); //$NON-NLS-1$
		}
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
	}

	/**
	 * Stops the refresh job
	 */
	public void stop() {
		if (DEBUG) {
			System.out.println("RefreshJob.stop()"); //$NON-NLS-1$
		}
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		cancel();
	}

	public Resource getResource(String path) {
		URI uri = URI.createFileURI(path);
		for (Iterator iter = new ArrayList(resourceSet.getResources())
				.iterator(); iter.hasNext();) {
			Resource resource = (Resource) iter.next();
			if (uri.equals(resource.getURI())) {
				return resource;
			}
		}
		return null;
	}

	public Resource getResource(IResource wsRes) {
		return getResource(wsRes.getLocation().toString());
	}
	
	/**
	 * Checks if the given resource can be accepted as a resource of resource set of this refresh job
	 * 
	 * @param resource
	 * @return
	 */
	private boolean accept(IResource resource) {
		if(resourceSet instanceof MultiFileResourceSetImpl) {
			return ((MultiFileResourceSetImpl)resourceSet).isLibraryResource(resource);
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
	 */
	public void resourceChanged(IResourceChangeEvent event) {
		if (!isEnabled() || resourceSet == null)
			return;
		IResourceDelta delta = event.getDelta();
		if (delta == null)
			return;
		try {
			class ResourceDeltaVisitor implements IResourceDeltaVisitor {
				private Collection changedResources = new ArrayList();

				private Collection removedResources = new ArrayList();

				private Collection addedResources = new ArrayList();

				private ArrayList addedWorkspaceResources = new ArrayList();

				public boolean visit(IResourceDelta delta) throws CoreException {
					Resource resource;
					if (delta.getFlags() != IResourceDelta.MARKERS
							&& delta.getResource().getType() == IResource.FILE) {
						switch (delta.getKind()) {
						case IResourceDelta.ADDED:
							// handle added resource
							//
							String loc = delta.getResource().getLocation().toString();
							resource = getResource(loc);
							if (resource != null) {
								addedResources.add(resource);
							}
							else if(accept(delta.getResource())){
								addedWorkspaceResources .add(delta.getResource());
							}
							break;
						case IResourceDelta.REMOVED:
							if ((IResourceDelta.MOVED_TO & delta.getFlags()) != 0) {
								// handle file move
								//
								if (DEBUG) {
									final IPath movedFromPath = delta
											.getResource().getLocation();
									final IPath movedToPath = delta
											.getMovedToPath();
									System.out
											.println("Resource moved from '" + movedFromPath + "' to '" + movedToPath + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
								}

							} else {
								// resource =
								// resourceSet.getResource(URI.createFileURI(delta.getResource().getLocation().toString()),
								// false);
								resource = getResource(delta.getResource()
										.getLocation().toString());
								if (resource != null) {
									removedResources.add(resource);
								}
							}
							break;
						case IResourceDelta.CHANGED:
							boolean encodingChanged = ((IResourceDelta.ENCODING & delta
									.getFlags()) != 0);
							boolean contentChanged = ((IResourceDelta.CONTENT & delta
									.getFlags()) != 0);
							if (encodingChanged || contentChanged) {
								// resource =
								// resourceSet.getResource(URI.createFileURI(delta.getResource().getLocation().toString()),
								// false);
								resource = getResource(delta.getResource()
										.getLocation().toString());
								if (resource != null) {
									changedResources.add(resource);
								}
							}
							break;
						}
					}
					return true;
				}

				public Collection getChangedResources() {
					return changedResources;
				}

				public Collection getRemovedResources() {
					return removedResources;
				}

			}
			;

			ResourceDeltaVisitor visitor = new ResourceDeltaVisitor();
			delta.accept(visitor);

			removedResources.addAll(visitor.getRemovedResources());
			changedResources.addAll(visitor.getChangedResources());
			addedResources.addAll(visitor.addedResources);
			addedWorkspaceResources .addAll(visitor.addedWorkspaceResources);

			if (shouldRefresh()) {
				scheduleRefresh();
			}
		} catch (CoreException e) {
			CommonPlugin.INSTANCE.log(e);
		}
	}

	/**
	 * Resolves the proxy and
	 * 
	 * @param proxy
	 * @return
	 */
	public EObject resolve(EObject proxy) {
		EObject resolved = EcoreUtil.resolve(proxy, resourceSet);
		EObject container = proxy.eContainer();
		if (resolved.eContainer() == null && container != null) {
			if (container.eIsProxy()) {
				container = resolve(container);
			}
			EReference ref = proxy.eContainmentFeature();
			if (ref.isMany()) {
				List values = (List) container.eGet(ref);
				for (Iterator iter = values.iterator(); iter.hasNext(); iter
						.next())
					;
			} else {
				container.eGet(ref);
			}
		}
		return resolved;
	}

	public static RefreshJob getInstance() {
		return instance;
	}

	private static RefreshJob instance = new RefreshJob();

}
