//------------------------------------------------------------------------------
// 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;

import java.io.File;
import java.security.AccessController;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;

import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.internal.resources.ResourceStatus;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
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;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.epf.persistence.util.PersistenceResources;
import org.eclipse.epf.uma.util.IFileManager;
import org.eclipse.osgi.util.NLS;

import sun.security.action.GetPropertyAction;

/**
 * @author Phong Nguyen Le
 * @since 1.0
 */
public class FileManager implements IFileManager {

	public static final String PLUGIN_ID = FileManager.class.getPackage()
			.getName();

	private static FileManager instance = null;

	private static String tmpdir;

	public static String getTempDir() {
		if (tmpdir == null) {
			GetPropertyAction a = new GetPropertyAction("java.io.tmpdir"); //$NON-NLS-1$
			tmpdir = ((String) AccessController.doPrivileged(a));
		}
		return tmpdir;
	}

	private boolean validateEditInitialized = false;

	public static final FileManager getInstance() {
		if (instance == null) {
			synchronized (FileManager.class) {
				if (instance == null) {
					instance = new FileManager();
				}
			}
		}

		return instance;
	}

	private FileManager() {
	}

	public static IResource getResourceForLocation(String location) {
		File file = new File(location);
		if (!file.exists()) {
			return null;
		}
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		IPath path = new Path(location);
		IResource resource;
		if (file.isFile()) {
			resource = workspaceRoot.getFileForLocation(path);
			if (resource == null) {
				IResource parentResource = getResourceForLocation(file
						.getParent());
				if (parentResource != null) {
					try {
						parentResource.refreshLocal(IResource.DEPTH_ONE, null);
					} catch (CoreException e) {
						CommonPlugin.INSTANCE.log(e);
					}
					resource = workspaceRoot.getFileForLocation(path);
				}
			}
		} else {
			resource = workspaceRoot.getContainerForLocation(path);
		}
		return resource;
	}

	public static boolean refresh(IResource resource) throws CoreException {
		if (!resource.exists()) {
			ArrayList foldersToRefresh = new ArrayList();
			IContainer container;
			for (container = resource.getParent(); !container.exists(); container = container
					.getParent()) {
				foldersToRefresh.add(0, container);
			}
			if (container.exists()) {
				container.refreshLocal(IResource.DEPTH_ONE, null);
			}
			if (!foldersToRefresh.isEmpty()) {
				for (Iterator iter = foldersToRefresh.iterator(); iter
						.hasNext();) {
					IFolder folder = (IFolder) iter.next();
					if (folder.exists()) {
						folder.refreshLocal(IResource.DEPTH_ONE, null);
					} else {
						return false;
					}
				}
			}
		}
		resource.refreshLocal(IResource.DEPTH_ONE, null);
		return true;
	}

	/**
	 * Refreshes file or directory with given local file system
	 * <code>path</code>
	 * 
	 * @param path
	 *            local file system path
	 * @return
	 * @throws CoreException
	 */
	private static boolean refresh(String path) throws CoreException {
		IResource resource = getResourceForLocation(path);
		if (resource != null) {
			return refresh(resource);
		}
		return false;
	}

	public boolean refresh(Resource resource) {
		try {
			return refresh(resource.getURI().toFileString());
		} catch (CoreException e) {
			CommonPlugin.INSTANCE.log(e);
			return false;
		}
	}

	public boolean move(String oldPath, String newPath) {
		return move(oldPath, newPath, false);
	}

	public boolean move(String oldPath, String newPath,
			boolean forceRemoveSource) {
		try {
			refresh(oldPath);

			IResource resource = null;
			IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace()
					.getRoot();

			// create the folders of the destination if they did not exist
			IPath destPath = new Path(newPath);
			if (new File(oldPath).isFile()) {
				resource = workspaceRoot.getFileForLocation(destPath);
			} else {
				resource = workspaceRoot.getContainerForLocation(destPath);
			}
			if (resource.exists()) {
				throw new MultiFileIOException(NLS.bind(
						PersistenceResources.moveError_msg, oldPath, newPath));
			}
			ArrayList foldersToCreate = new ArrayList();
			IContainer container;
			for (container = resource.getParent(); !container.exists(); container = container
					.getParent()) {
				foldersToCreate.add(0, container);
			}
			if (!foldersToCreate.isEmpty()) {
				container.refreshLocal(IResource.DEPTH_ONE, null);
				for (Iterator iter = foldersToCreate.iterator(); iter.hasNext();) {
					IFolder folder = (IFolder) iter.next();
					if (!folder.exists()) {
						folder.create(true, true, null);
					} else {
						folder.refreshLocal(IResource.DEPTH_ONE, null);
					}
				}
			}
			destPath = resource.getFullPath();

			IPath path = new Path(oldPath);
			IFile file = workspaceRoot.getFileForLocation(path);
			if (file != null && file.exists()) {
				resource = file;
			} else {
				resource = workspaceRoot.getContainerForLocation(path);
			}
			if (resource != null) {
				try {
					resource.move(destPath, true, null);
				} catch (ResourceException e) {
					if (forceRemoveSource) {
						throw e;
					}

					boolean failed = false;

					// handle situation when Eclipse moves file/directory by
					// copying it to destination then deleting the source
					// but deletion failed
					IStatus[] statuses = e.getStatus().getChildren();
					for (int i = 0; i < statuses.length; i++) {
						IStatus status = statuses[i];
						if (status.getCode() == IResourceStatus.FAILED_DELETE_LOCAL
								&& status.getMessage() == Messages.localstore_deleteProblem) {
							String msg = MessageFormat
									.format(
											"Warning while moving ''{0}'' to ''{1}'': {2}", new Object[] { oldPath, newPath, status.getMessage() }); //$NON-NLS-1$
							CommonPlugin.INSTANCE.log(msg);
						} else {
							failed = true;
						}
					}
					if (failed || !new File(newPath).exists()) {
						return false;
					}
				}
				return true;
			}
		} catch (CoreException e) {
			CommonPlugin.INSTANCE.log(e);
			if (MultiFileSaveUtil.DEBUG) {
				e.printStackTrace();
			}
		}
		return false;
	}

	public boolean rename(File oldFile, File newFile) {
		return move(oldFile.getAbsolutePath(), newFile.getAbsolutePath());
	}

	public void deleteResource(String path, IProgressMonitor monitor)
			throws CoreException {
		// no need to refresh the whole tree from specified path
		// getResourceForLocation() refreshes resource in a more efficient way
		//
		// IWorkspaceRoot workspaceRoot =
		// ResourcesPlugin.getWorkspace().getRoot();
		// try {
		// workspaceRoot.refreshLocal(IResource.DEPTH_INFINITE, monitor);
		// } catch (CoreException e1) {
		// e1.printStackTrace();
		// }

		IResource resource = getResourceForLocation(path);
		if (resource != null) {
			resource.delete(true, monitor);
		}
	}

	public boolean delete(String path) {
		try {
			deleteResource(path, null);
			return true;
		} catch (CoreException e) {
			CommonPlugin.INSTANCE.log(e);
			if (MultiFileSaveUtil.DEBUG) {
				e.printStackTrace();
			}
		}
		return false;
	}

	private static boolean fromCC(IStatus status) {
		String pluginId = status.getPlugin();
		return pluginId != null
				&& pluginId.toLowerCase().indexOf("clearcase") != -1; //$NON-NLS-1$
	}

	public IStatus checkModify(String[] paths, Object context) {
		IStatus status = null;
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		IFile[] files = new IFile[paths.length];
		ArrayList notFoundFiles = new ArrayList();
		for (int i = 0; i < paths.length; i++) {
			String path = paths[i];
			try {
				refresh(path);
			} catch (CoreException e) {
				CommonPlugin.INSTANCE.log(e);
			}
			IFile file = workspace.getRoot().getFileForLocation(new Path(path));
			if (file == null) {
				notFoundFiles.add(path);
			} else {
				files[i] = file;
			}
		}
		if (!notFoundFiles.isEmpty()) {
			return new Status(IStatus.WARNING, PLUGIN_ID, IStatus.WARNING, NLS
					.bind(PersistenceResources.fileNotFoundError_msg,
							notFoundFiles), null);
		}

		if (!validateEditInitialized) {
			status = workspace.validateEdit(files, context);
			validateEditInitialized = true;
			if (status.isOK()) {
				// double-check after initialization
				//
				status = workspace.validateEdit(files, context);
			}
		} else {
			status = workspace.validateEdit(files, context);
		}

		if (status.isOK()) {
			// some version control provider still returns OK status even though
			// user cancelled the check out
			// double-check here again to make sure the file is not read-only
			//			
			ArrayList readOnlyFiles = new ArrayList();
			for (int i = 0; i < files.length; i++) {
				IFile file = files[i];
				try {
					file.refreshLocal(IResource.DEPTH_ZERO, null);
				} catch (CoreException e) {
					CommonPlugin.INSTANCE.log(e);
				}
				if (file.isReadOnly()) {
					readOnlyFiles.add(file);
				}
			}
			if (!readOnlyFiles.isEmpty()) {
				MultiStatus multiStatus = new MultiStatus(PLUGIN_ID,
						IStatus.OK, PersistenceResources.modifyFilesError_msg,
						null);
				for (Iterator iter = readOnlyFiles.iterator(); iter.hasNext();) {
					IFile file = (IFile) iter.next();
					String localPath = file.getLocation().toOSString();
					String msg = MessageFormat.format(
							PersistenceResources.FileManager_fileReadOnly,
							new Object[] { localPath });
					multiStatus.add(new ResourceStatus(IStatus.ERROR, 0, file
							.getFullPath(), msg, null));
				}
				status = multiStatus;
				return status;
			}
		} else {
			// hack for clearcase
			if (fromCC(status)) {
				String msg = PersistenceResources.modifyFilesError_msg;
				MultiStatus multiStatus = new MultiStatus(PLUGIN_ID, status
						.getCode(), msg, null);
				multiStatus.add(status);
				return multiStatus;
			}
		}

		// convert workspace path to local file system path
		if (status instanceof MultiStatus) {
			MultiStatus ms = (MultiStatus) status;
			for (int i = 0; i < ms.getChildren().length; i++) {
				IStatus childStatus = ms.getChildren()[i];
				if (childStatus instanceof IResourceStatus
						&& childStatus.getCode() == IResourceStatus.READ_ONLY_LOCAL) {
					IResourceStatus resourceStatus = ((IResourceStatus) childStatus);
					IPath path = resourceStatus.getPath();
					IFile file = ResourcesPlugin.getWorkspace().getRoot()
							.getFile(path);
					String localPath = file.getLocation().toOSString();
					String msg = MessageFormat.format(
							PersistenceResources.FileManager_fileReadOnly,
							new Object[] { localPath }); //$NON-NLS-1$
					ms.getChildren()[i] = new ResourceStatus(childStatus
							.getSeverity(), childStatus.getCode(),
							resourceStatus.getPath(), msg, childStatus
									.getException());
				}
			}
		}

		return status;
	}

	/**
	 * @see org.eclipse.epf.uma.util.IFileManager#checkModify(java.lang.String,
	 *      java.lang.Object)
	 */
	public IStatus checkModify(String path, Object context) {
		return checkModify(new String[] { path }, context);
	}

	/**
	 * Checks if the given path is team-private file or folder
	 * 
	 * @param path
	 * @return
	 */
	public boolean isTeamPrivate(String path) {
		IResource resource = getResourceForLocation(path);
		return resource != null && resource.isTeamPrivateMember();
	}

}