/*******************************************************************************
 * Copyright (c) 2007, 2011 Intel 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:
 * Intel Corporation - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.settings.model.util;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CProjectNature;
import org.eclipse.core.resources.IProject;
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.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

public abstract class ResourceChangeHandlerBase implements IResourceChangeListener {
	public interface IResourceMoveHandler {
		/**
		 * Resource moved fromRc to toRc
		 * @param fromRc
		 * @param toRc
		 * @return boolean indicating if children should be visited
		 */
		boolean handleResourceMove(IResource fromRc, IResource toRc);

		/**
		 * Handle a resource remove
		 * @param rc Removed IResource
		 * @return boolean indicating if children should be visited
		 */
		boolean handleResourceRemove(IResource rc);

		/**
		 * Handle a project close
		 */
		void handleProjectClose(IProject project);

		/**
		 * Call-back ticked at end of a resource change event
		 */
		void done();
	}

	private IWorkspaceRoot fRoot = ResourcesPlugin.getWorkspace().getRoot();

	private class DeltaVisitor implements IResourceDeltaVisitor {
		//		private IResourceDelta fRootDelta;
		private Map<IResource, IResource> fMoveMap = new HashMap<>();
		private IResourceMoveHandler fHandler;

		public DeltaVisitor(IResourceMoveHandler handler, IResourceDelta rootDelta) {
			fHandler = handler;
			//			fRootDelta = rootDelta;
		}

		private IResource getResource(IPath path, IResource baseResource) {
			switch (baseResource.getType()) {
			case IResource.FILE:
				return fRoot.getFile(path);
			case IResource.FOLDER:
				return fRoot.getFolder(path);
			case IResource.PROJECT:
				return fRoot.getProject(path.segment(0));
			case IResource.ROOT:
			default:
				throw new IllegalArgumentException();
			}
		}

		private boolean checkInitHandleResourceMove(IResource fromRc, IResource toRc) {
			if (fMoveMap.put(fromRc, toRc) == null) {
				return fHandler.handleResourceMove(fromRc, toRc);
			}

			return true;
		}

		@Override
		public boolean visit(IResourceDelta delta) throws CoreException {
			IResource dResource = delta.getResource();

			if (dResource.getType() == IResource.PROJECT && !shouldVisit((IProject) dResource))
				return false;

			boolean resume = true;
			boolean removed = false;

			switch (delta.getKind()) {
			case IResourceDelta.REMOVED:
				removed = true;
				//$FALL-THROUGH$
			case IResourceDelta.CHANGED:
				int flags = delta.getFlags();
				if ((flags & IResourceDelta.MOVED_TO) != 0) {
					IPath path = delta.getMovedToPath();
					if (path != null) {
						IResource toRc = getResource(path, dResource);
						resume = checkInitHandleResourceMove(dResource, toRc);
					}
					break;
				} else if ((flags & IResourceDelta.MOVED_FROM) != 0) {
					IPath path = delta.getMovedFromPath();
					if (path != null) {
						IResource fromRc = getResource(path, dResource);
						resume = checkInitHandleResourceMove(fromRc, dResource);
					}
					break;
				} else if (removed) {
					resume = fHandler.handleResourceRemove(dResource);
				}
				break;
			default:
				break;
			}

			return resume; //  visit the children
		}
	}

	/*
	 *  I R e s o u r c e C h a n g e L i s t e n e r
	 */

	/* (non-Javadoc)
	 *
	 *  Handle the renaming and deletion of project resources
	 *  This is necessary in order to update ResourceConfigurations and AdditionalInputs
	 *
	 * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
	 */
	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		if (event.getSource() instanceof IWorkspace) {
			IResourceMoveHandler handler = createResourceMoveHandler(event);
			doHandleResourceMove(event, handler);
		}
	}

	protected boolean shouldVisit(IProject project) {
		try {
			return project.isOpen() ? project.hasNature(CProjectNature.C_NATURE_ID) : true;
		} catch (CoreException e) {
			CCorePlugin.log(e);
			return false;
		}
	}

	/**
	 * @nooverride This method is not intended to be re-implemented or extended by clients.
	 * @noreference This method is not intended to be referenced by clients.
	 */
	protected void doHahdleResourceMove(IResourceChangeEvent event, IResourceMoveHandler handler) {
		doHandleResourceMove(event, handler);
	}

	/**
	 * @since 5.1
	 */
	protected void doHandleResourceMove(IResourceChangeEvent event, IResourceMoveHandler handler) {
		switch (event.getType()) {
		case IResourceChangeEvent.PRE_CLOSE:
			IProject project = (IProject) event.getResource();
			if (shouldVisit(project))
				handler.handleProjectClose(project);
			break;
		//				case IResourceChangeEvent.PRE_DELETE :
		//					handler.handleResourceRemove(event.getResource());
		//					break;
		case IResourceChangeEvent.POST_CHANGE:
			IResourceDelta resDelta = event.getDelta();
			if (resDelta == null) {
				break;
			}
			try {
				DeltaVisitor rcChecker = new DeltaVisitor(handler, resDelta);
				resDelta.accept(rcChecker);
			} catch (CoreException e) {
				CCorePlugin.log(e);
			}
			break;
		default:
			break;
		}

		handler.done();
	}

	protected abstract IResourceMoveHandler createResourceMoveHandler(IResourceChangeEvent event);
}
