/*******************************************************************************
 * Copyright (c) 2000, 2004 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.team.examples.filesystem;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFileModificationValidator;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.team.ResourceRuleFactory;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.history.IFileHistoryProvider;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.examples.filesystem.history.FileSystemHistoryProvider;
import org.eclipse.team.examples.filesystem.subscriber.FileSystemResourceVariant;
import org.eclipse.team.examples.filesystem.subscriber.FileSystemSubscriber;

import it.mice.voila2.sync.plugin.Activator;

/**
 * This example illustrates how to create a concrete implementation of a <code>RepositoryProvider</code>
 * that uses the file system to act as the repository. See the plugin.xml file for the xml required
 * to register this provider with the Team extension point <code>org.eclipse.team.core.repository</code>.
 * The plugin.xml file also contains examples of how to filter menu items using a repository provider's
 * ID.
 *
 * <p>
 * This example provider illustrates the following:
 * <ol>
 * <li>simple working implementation of <code>RepositoyProvider</code>
 * <li>storage of a persistent property with the project (which provides the target location for the provider)
 * <li>access to an instance of <code>SimpleAccessOperations</code> for performing simple file operations
 * </ol>
 *
 * <p>
 * Additional functionality that will be illustrated in the future include:
 * <ol>
 * <li>Validate Save/Validate Edit
 * <li>Move/Delete Hook
 * <li>Project Sets
 * <li>Use of the workspace synchronizer (ISynchronizer)
 * <li>Use of decorators
 * <li>combining streams and progress monitors to get responsive UI
 * </ol>
 *
 */
public class FileSystemProvider extends RepositoryProvider {

	/*
	 * Create a custom rule factory to allow more optimistic concurrency
	 */
	private static final ResourceRuleFactory RESOURCE_RULE_FACTORY = new ResourceRuleFactory() {
		// Just need a subclass to instantiate
	};

	protected static final Log log = LogFactory.getLog(FileSystemProvider.class);

	// The location of the folder on file system where the repository is stored.
	private IPath root;
	private IPath baseRoot;

	// The QualifiedName that is used to persist the location across workspace as a persistent property on a resource
	private static QualifiedName FILESYSTEM_REPO_LOC = new QualifiedName(Activator.PLUGIN_ID, "disk_location"); //$NON-NLS-1$
	private static QualifiedName FILESYSTEM_BASE_REPO_LOC = new QualifiedName(Activator.PLUGIN_ID, "disk_base_location"); //$NON-NLS-1$

	private static FileSystemHistoryProvider fileHistoryProvider;

	/**
	 * Create a new FileSystemProvider.
	 */
	public FileSystemProvider() {
		super();
	}

	/**
	 * This method is invoked when the provider is mapped to a project.
	 * Although we have access to the project at this point (using
	 * <code>getProject()</code>, we don't know the root location so
	 * there is nothing we can do yet.
	 *
	 * @see org.eclipse.team.core.RepositoryProvider#configureProject()
	 */
	public void configureProject() throws CoreException {
		FileSystemSubscriber.getInstance().handleRootChanged(getProject(), true /* added */);
	}

	/**
	 * This method is invoked when the provider is unmapped from its
	 * project.
	 *
	 * @see org.eclipse.core.resources.IProjectNature#deconfigure()
	 */
	public void deconfigure() throws CoreException {
		// Clear the persistant property containing the location
		getProject().setPersistentProperty(FILESYSTEM_REPO_LOC, null);
		getProject().setPersistentProperty(FILESYSTEM_BASE_REPO_LOC, null);
		FileSystemSubscriber.getInstance().handleRootChanged(getProject(), false /* removed */);
	}

	/**
	 * Return the provider ID as specified in the plugin.xml
	 *
	 * @see RepositoryProvider#getID()
	 */
	public String getID() {
		return Activator.PROVIDER_ID;
	}

	/**
	 * Set the file system location for the provider. This mist be invoked after
	 * the provider is mapped and configured but before the provider is used to
	 * perform any operations.
	 *
	 * @param location the path representing the location where the project contents will be stored.
	 * @throws TeamException
	 */
	public void setTargetLocation(String location, String baseLocation) throws TeamException {

		// set the instance variable to the provided path
		root = new Path(location);
		baseRoot = new Path(baseLocation);

		// ensure that the location is a folder (if it exists)
		File file = new File(location);
		if (file.exists() && !file.isDirectory()) {
			throw new TeamException(Policy.bind("FileSystemProvider.mustBeFolder", location)); //$NON-NLS-1$
		}
		// ensure that the location is a folder (if it exists)
		file = new File(baseLocation);
		if (file.exists() && !file.isDirectory()) {
			throw new TeamException(Policy.bind("FileSystemProvider.mustBeFolder", baseLocation)); //$NON-NLS-1$
		}

		// record the location as a persistant property so it will be remembered across platform invokations
		try {
			getProject().setPersistentProperty(FILESYSTEM_REPO_LOC, location);
			getProject().setPersistentProperty(FILESYSTEM_BASE_REPO_LOC, baseLocation);
		} catch (CoreException e) {
			throw Activator.wrapException(e);
		}
	}

	/**
	 * Returns the folder in the file system to which the provider is connected.
	 * Return <code>null</code> if there is no location or there was a problem
	 * determining it.
	 *
	 * @return IPath The path to the root of the repository.
	 */
	public IPath getRoot() {
		if (root == null) {
			try {
				String location = getProject().getPersistentProperty(FILESYSTEM_REPO_LOC);
				if (location == null) {
					return null;
				}
				root = new Path(location);
			} catch (CoreException e) {
				// log the problem and carry on
				log.warn(e.getStatus());
				//FileSystemPlugin.log(e.getStatus());
				return null;
			}
		}
		return root;
	}

	public IPath getBaseRoot() {
		if (baseRoot == null) {
			try {
				String location = getProject().getPersistentProperty(FILESYSTEM_BASE_REPO_LOC);
				if (location == null) {
					return null;
				}
				baseRoot = new Path(location);
			} catch (CoreException e) {
				// log the problem and carry on
				log.warn(e.getStatus());
				//FileSystemPlugin.log(e.getStatus());
				return null;
			}
		}
		return baseRoot;
	}

	/**
	 * Return an object that provides the operations for transfering data
	 * to and from the provider's location.
	 */
	public FileSystemOperations getOperations() {
		return new FileSystemOperations(this);
	}
	/**
	 * @see org.eclipse.team.core.RepositoryProvider#getFileModificationValidator()
	 */
	public IFileModificationValidator getFileModificationValidator() {
		return new FileModificationValidator(this);
	}

	/**
	 * Return the resource variant for the local resource using the bytes to
	 * identify the variant.
	 * @param resource the resource
	 * @param bytes the bytes that identify the resource variant
	 * @return the resource variant handle
	 */
	public IResourceVariant getResourceVariant(IResource resource, byte[] bytes) {
		if (bytes == null) return null;
		File file = getFile(resource);
		if (file == null) return null;
		return new FileSystemResourceVariant(file, bytes);
	}

	public IResourceVariant getBaseResourceVariant(IResource resource, byte[] bytes) {
		if (bytes == null) return null;
		File file = getBaseFile(resource);
		if (file == null) return null;
		return new FileSystemResourceVariant(file, bytes);
	}

	/**
	 * Return the resource variant for the local resource.
	 * @param resource the resource
	 * @return the resource variant
	 */
	public IResourceVariant getResourceVariant(IResource resource) {
		File file = getFile(resource);
		if (file == null || !file.exists()) return null;
		return new FileSystemResourceVariant(file);
	}

	/**
	 * Return the resource variant for the local resource.
	 * @param resource the resource
	 * @return the resource variant
	 */
	public IResourceVariant getBaseResourceVariant(IResource resource) {
		File file = getBaseFile(resource);
		if (file == null || !file.exists()) return null;
		return new FileSystemResourceVariant(file);
	}

	/**
	 * Return the <code>java.io.File</code> that the given resource maps to.
	 * Return <code>null</code> if the resource is not a child of this provider's
	 * project.
	 * @param resource the resource
	 * @return the file that the resource maps to.
	 */
	public java.io.File getFile(IResource resource) {
		if (resource.getProject().equals(getProject())) {
			IPath rootdir = getRoot();
			return new File(rootdir.append(resource.getProjectRelativePath()).toOSString());
		}
		return null;
	}

	/**
	 * Return the <code>java.io.File</code> that the given resource maps to.
	 * Return <code>null</code> if the resource is not a child of this provider's
	 * project.
	 * @param resource the resource
	 * @return the file that the resource maps to.
	 */
	public java.io.File getBaseFile(IResource resource) {
		if (resource.getProject().equals(getProject())) {
			IPath rootdir = getBaseRoot();
			return new File(rootdir.append(resource.getProjectRelativePath()).toOSString());
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.team.core.RepositoryProvider#getRuleFactory()
	 */
	public IResourceRuleFactory getRuleFactory() {
		return RESOURCE_RULE_FACTORY;
	}

	public IFileHistoryProvider getFileHistoryProvider() {
		  if (FileSystemProvider.fileHistoryProvider == null) {
			  FileSystemProvider.fileHistoryProvider = new FileSystemHistoryProvider();
	        }
	        return FileSystemProvider.fileHistoryProvider;
	}


	/**
	 * Returns a provider of type with the given id if associated with the given project
	 * or <code>null</code> if the project is not associated with a provider of that type
	 * or the nature id is that of a non-team repository provider nature.
	 *
	 * @param project the project to query for a provider
	 * @param id the repository provider id
	 * @return the repository provider
	 */
	final public static RepositoryProvider getFakeProvider(IProject project, String id) {
		RepositoryProvider repositoryProvider = (RepositoryProvider)providerMap.get(project);
		if (repositoryProvider != null) {
			return repositoryProvider;
		} else {
			return registerNewProviderFor(project);
		}
	}

	private static RepositoryProvider registerNewProviderFor(IProject project) {
		FileSystemProvider newProvider = new FileSystemProvider();
		newProvider.setProject(project);
		try {
			IProject rootProject = project;
//			if (project.getName().lastIndexOf("-") < 0) {
//				rootProject = project;
//			} else {
//				String projectName = project.getName().substring(0, project.getName().lastIndexOf("-"));
//				rootProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
//			}
			String rootLocation = rootProject.getLocation() + "/.syncFolder/";
			String remoteTarget = rootLocation + "remote/" + project.getName();
			String baseTarget = rootLocation + "base/" + project.getName();
			newProvider.setTargetLocation(remoteTarget, baseTarget);
			providerMap.put(project, newProvider);
			newProvider.configureProject();
		} catch (CoreException e) {
			//Activator.getDefault().getLog().log(e.getStatus());
//			FileSystemPlugin.log(e.getStatus());
			log.warn(e.getStatus());
			return null;
		}
		return newProvider;
	}

	private static Map providerMap = new HashMap();
}
