/*******************************************************************************
 * Copyright (c) 2000, 2015 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
 *     James Blackburn (Broadcom Corp.) - ongoing development
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
 *******************************************************************************/
package org.eclipse.core.internal.resources;

import java.util.*;
import org.eclipse.core.internal.events.LifecycleEvent;
import org.eclipse.core.internal.localstore.IHistoryStore;
import org.eclipse.core.internal.utils.*;
import org.eclipse.core.internal.watson.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.osgi.util.NLS;

public abstract class Container extends Resource implements IContainer {
	protected Container(IPath path, Workspace container) {
		super(path, container);
	}

	/**
	 * Converts this resource and all its children into phantoms by modifying
	 * their resource infos in-place.
	 */
	@Override
	public void convertToPhantom() throws CoreException {
		if (isPhantom())
			return;
		super.convertToPhantom();
		IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
		for (IResource member : members)
			((Resource) member).convertToPhantom();
	}

	@Override
	public IResourceFilterDescription createFilter(int type, FileInfoMatcherDescription matcherDescription, int updateFlags, IProgressMonitor monitor) throws CoreException {
		Assert.isNotNull(getProject());
		monitor = Policy.monitorFor(monitor);
		FilterDescription filter = null;
		try {
			String message = NLS.bind(Messages.links_creating, getFullPath());
			monitor.beginTask(message, Policy.totalWork);
			Policy.checkCanceled(monitor);
			checkValidPath(path, FOLDER | PROJECT, true);
			final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
			try {
				workspace.prepareOperation(rule, monitor);
				workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_FILTER_ADD, this));
				workspace.beginOperation(true);
				monitor.worked(Policy.opWork * 5 / 100);
				//save the filter in the project description
				filter = new FilterDescription(this, type, matcherDescription);
				filter.setId(System.currentTimeMillis());

				Project project = (Project) getProject();
				project.internalGetDescription().addFilter(getProjectRelativePath(), filter);
				project.writeDescription(IResource.NONE);
				monitor.worked(Policy.opWork * 5 / 100);

				//refresh to discover any new resources below this folder
				if (getType() != IResource.FILE) {
					//refresh either in background or foreground
					if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) {
						workspace.refreshManager.refresh(this);
						monitor.worked(Policy.opWork * 90 / 100);
					} else {
						refreshLocal(DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 90 / 100));
					}
				} else
					monitor.worked(Policy.opWork * 90 / 100);
			} catch (OperationCanceledException e) {
				workspace.getWorkManager().operationCanceled();
				throw e;
			} finally {
				workspace.endOperation(rule, true);
			}
		} finally {
			monitor.done();
		}
		return filter;
	}

	@Override
	public boolean exists(IPath childPath) {
		return workspace.getResourceInfo(getFullPath().append(childPath), false, false) != null;
	}

	@Override
	public IResource findMember(String memberPath) {
		return findMember(memberPath, false);
	}

	@Override
	public IResource findMember(String memberPath, boolean phantom) {
		IPath childPath = getFullPath().append(memberPath);
		ResourceInfo info = workspace.getResourceInfo(childPath, phantom, false);
		return info == null ? null : workspace.newResource(childPath, info.getType());
	}

	@Override
	public IResource findMember(IPath childPath) {
		return findMember(childPath, false);
	}

	@Override
	public IResource findMember(IPath childPath, boolean phantom) {
		childPath = getFullPath().append(childPath);
		ResourceInfo info = workspace.getResourceInfo(childPath, phantom, false);
		return (info == null) ? null : workspace.newResource(childPath, info.getType());
	}

	@Override
	protected void fixupAfterMoveSource() throws CoreException {
		super.fixupAfterMoveSource();
		if (!synchronizing(getResourceInfo(true, false)))
			return;
		IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
		for (IResource member : members)
			((Resource) member).fixupAfterMoveSource();
	}

	protected IResource[] getChildren(int memberFlags) {
		IPath[] children = null;
		try {
			children = workspace.tree.getChildren(path);
		} catch (IllegalArgumentException e) {
			//concurrency problem: the container has been deleted by another
			//thread during this call.  Just return empty children set
		}
		if (children == null || children.length == 0)
			return ICoreConstants.EMPTY_RESOURCE_ARRAY;
		Resource[] result = new Resource[children.length];
		int found = 0;
		for (IPath child : children) {
			ResourceInfo info = workspace.getResourceInfo(child, true, false);
			if (info != null && isMember(info.getFlags(), memberFlags))
				result[found++] = workspace.newResource(child, info.getType());
		}
		if (found == result.length)
			return result;
		Resource[] trimmedResult = new Resource[found];
		System.arraycopy(result, 0, trimmedResult, 0, found);
		return trimmedResult;
	}

	public IFile getFile(String name) {
		return (IFile) workspace.newResource(getFullPath().append(name), FILE);
	}

	@Override
	public IResourceFilterDescription[] getFilters() throws CoreException {
		IResourceFilterDescription[] results = null;
		checkValidPath(path, FOLDER | PROJECT, true);
		Project project = (Project) getProject();
		ProjectDescription desc = project.internalGetDescription();
		if (desc != null) {
			LinkedList<FilterDescription> list = desc.getFilter(getProjectRelativePath());
			if (list != null) {
				results = new IResourceFilterDescription[list.size()];
				for (int i = 0; i < list.size(); i++) {
					results[i] = list.get(i);
				}
				return results;
			}
		}
		return new IResourceFilterDescription[0];
	}

	public boolean hasFilters() {
		IProject project = getProject();
		if (project == null)
			return false;
		ProjectDescription desc = ((Project) project).internalGetDescription();
		if (desc == null)
			return false;
		LinkedList<FilterDescription> filters = desc.getFilter(getProjectRelativePath());
		if ((filters != null) && (filters.size() > 0))
			return true;
		return false;
	}

	@Override
	public IFile getFile(IPath childPath) {
		return (IFile) workspace.newResource(getFullPath().append(childPath), FILE);
	}

	public IFolder getFolder(String name) {
		return (IFolder) workspace.newResource(getFullPath().append(name), FOLDER);
	}

	@Override
	public IFolder getFolder(IPath childPath) {
		return (IFolder) workspace.newResource(getFullPath().append(childPath), FOLDER);
	}

	@Deprecated
	@Override
	public boolean isLocal(int flags, int depth) {
		if (!super.isLocal(flags, depth))
			return false;
		if (depth == DEPTH_ZERO)
			return true;
		if (depth == DEPTH_ONE)
			depth = DEPTH_ZERO;
		// get the children via the workspace since we know that this
		// resource exists (it is local).
		IResource[] children = getChildren(IResource.NONE);
		for (int i = 0; i < children.length; i++)
			if (!children[i].isLocal(depth))
				return false;
		return true;
	}

	@Override
	public IResource[] members() throws CoreException {
		// forward to central method
		return members(IResource.NONE);
	}

	@Override
	public IResource[] members(boolean phantom) throws CoreException {
		// forward to central method
		return members(phantom ? INCLUDE_PHANTOMS : IResource.NONE);
	}

	@Override
	public IResource[] members(int memberFlags) throws CoreException {
		final boolean phantom = (memberFlags & INCLUDE_PHANTOMS) != 0;
		ResourceInfo info = getResourceInfo(phantom, false);
		checkAccessible(getFlags(info));
		//if children are currently unknown, ask for immediate refresh
		if (info.isSet(ICoreConstants.M_CHILDREN_UNKNOWN))
			workspace.refreshManager.refresh(this);
		return getChildren(memberFlags);
	}

	public void removeFilter(IResourceFilterDescription filterDescription, int updateFlags, IProgressMonitor monitor) throws CoreException {
		monitor = Policy.monitorFor(monitor);
		try {
			String message = NLS.bind(Messages.links_creating, getFullPath());
			monitor.beginTask(message, Policy.totalWork);
			Policy.checkCanceled(monitor);
			checkValidPath(path, FOLDER | PROJECT, true);
			final ISchedulingRule rule = workspace.getRuleFactory().createRule(this);
			try {
				workspace.prepareOperation(rule, monitor);
				workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_FILTER_REMOVE, this));
				workspace.beginOperation(true);
				monitor.worked(Policy.opWork * 5 / 100);
				//save the filter in the project description
				Project project = (Project) getProject();
				project.internalGetDescription().removeFilter(getProjectRelativePath(), (FilterDescription) filterDescription);
				project.writeDescription(IResource.NONE);
				monitor.worked(Policy.opWork * 5 / 100);

				//refresh to discover any new resources below this linked location
				if (getType() != IResource.FILE) {
					//refresh either in background or foreground
					if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) {
						workspace.refreshManager.refresh(this);
						monitor.worked(Policy.opWork * 90 / 100);
					} else {
						refreshLocal(DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 90 / 100));
					}
				} else
					monitor.worked(Policy.opWork * 90 / 100);
			} catch (OperationCanceledException e) {
				workspace.getWorkManager().operationCanceled();
				throw e;
			} finally {
				workspace.endOperation(rule, true);
			}
		} finally {
			monitor.done();
		}
	}

	@Override
	public String getDefaultCharset() throws CoreException {
		return getDefaultCharset(true);
	}

	@Override
	public IFile[] findDeletedMembersWithHistory(int depth, IProgressMonitor monitor) {
		IHistoryStore historyStore = getLocalManager().getHistoryStore();
		IPath basePath = getFullPath();
		IWorkspaceRoot root = getWorkspace().getRoot();
		Set<IFile> deletedFiles = new HashSet<>();

		if (depth == IResource.DEPTH_ZERO) {
			// this folder might have been a file in a past life
			if (historyStore.getStates(basePath, monitor).length > 0) {
				IFile file = root.getFile(basePath);
				if (!file.exists()) {
					deletedFiles.add(file);
				}
			}
		} else {
			// convert IPaths to IFiles keeping only files that no longer exist
			for (IPath filePath : historyStore.allFiles(basePath, depth, monitor)) {
				IFile file = root.getFile(filePath);
				if (!file.exists()) {
					deletedFiles.add(file);
				}
			}
		}
		return deletedFiles.toArray(new IFile[deletedFiles.size()]);
	}

	@Deprecated
	@Override
	public void setDefaultCharset(String charset) throws CoreException {
		ResourceInfo info = getResourceInfo(false, false);
		checkAccessible(getFlags(info));
		workspace.getCharsetManager().setCharsetFor(getFullPath(), charset);
	}

	@Override
	public void setDefaultCharset(String newCharset, IProgressMonitor monitor) throws CoreException {
		monitor = Policy.monitorFor(monitor);
		try {
			String message = NLS.bind(Messages.resources_settingDefaultCharsetContainer, getFullPath());
			monitor.beginTask(message, Policy.totalWork);
			// need to get the project as a scheduling rule because we might be
			// creating a new folder/file to hold the project settings
			final ISchedulingRule rule = workspace.getRuleFactory().charsetRule(this);
			try {
				workspace.prepareOperation(rule, monitor);
				checkAccessible(getFlags(getResourceInfo(false, false)));
				workspace.beginOperation(true);
				workspace.getCharsetManager().setCharsetFor(getFullPath(), newCharset);
				// now propagate the changes to all children inheriting their setting from this container
				IElementContentVisitor visitor = new IElementContentVisitor() {
					boolean visitedRoot = false;

					@Override
					public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) {
						if (elementContents == null)
							return false;
						IPath nodePath = requestor.requestPath();
						// we will always generate an event at least for the root of the sub tree
						// (skip visiting the root because we already have set the charset above and
						// that is the condition we are checking later)
						if (!visitedRoot) {
							visitedRoot = true;
							ResourceInfo info = workspace.getResourceInfo(nodePath, false, true);
							if (info == null)
								return false;
							info.incrementCharsetGenerationCount();
							return true;
						}
						// does it already have an encoding explicitly set?
						if (workspace.getCharsetManager().getCharsetFor(nodePath, false) != null)
							return false;
						ResourceInfo info = workspace.getResourceInfo(nodePath, false, true);
						if (info == null)
							return false;
						info.incrementCharsetGenerationCount();
						return true;
					}
				};
				try {
					new ElementTreeIterator(workspace.getElementTree(), getFullPath()).iterate(visitor);
				} catch (WrappedRuntimeException e) {
					throw (CoreException) e.getTargetException();
				}
				monitor.worked(Policy.opWork);
			} catch (OperationCanceledException e) {
				workspace.getWorkManager().operationCanceled();
				throw e;
			} finally {
				workspace.endOperation(rule, true);
			}
		} finally {
			monitor.done();
		}
	}
}
