/*******************************************************************************
 * Copyright (c) 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 API and implementation
 *******************************************************************************/
package org.eclipse.team.examples.filesystem.subscriber;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

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.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.diff.IThreeWayDiff;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.mapping.IResourceDiff;
import org.eclipse.team.core.mapping.ISynchronizationScopeManager;
import org.eclipse.team.core.mapping.provider.ResourceDiffTree;
import org.eclipse.team.core.subscribers.SubscriberMergeContext;
import org.eclipse.team.internal.core.Messages;
import org.eclipse.team.internal.core.Policy;

import it.micegroup.voila2.cartridge.library.javaservice.FileCopyUtils;

/**
 * A merge context for merging file system changes.
 */
public class FileSystemMergeContext extends SubscriberMergeContext {

	/**
	 * Create the file system merge context for the given scope manager.
	 * 
	 * @param manager the scope manager
	 */
	public FileSystemMergeContext(ISynchronizationScopeManager manager) {
		super(FileSystemSubscriber.getInstance(), manager);
		initialize();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.team.core.mapping.provider.MergeContext#makeInSync(org.eclipse.
	 * team.core.diff.IDiff, org.eclipse.core.runtime.IProgressMonitor)
	 */
	protected void makeInSync(IDiff diff, IProgressMonitor monitor) throws CoreException {
		IResource resource = ResourceDiffTree.getResourceFor(diff);
		FileSystemSubscriber.getInstance().makeInSync(resource);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.team.core.mapping.IMergeContext#markAsMerged(org.eclipse.team.
	 * core.diff.IDiff, boolean, org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void markAsMerged(IDiff diff, boolean inSyncHint, IProgressMonitor monitor) throws CoreException {
		// TODO if inSyncHint is true, we should test to see if the contents match
		IResource resource = ResourceDiffTree.getResourceFor(diff);
		FileSystemSubscriber.getInstance().markAsMerged(resource, monitor);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.team.core.mapping.IMergeContext#reject(org.eclipse.team.core.diff
	 * .IDiff, org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void reject(IDiff diff, IProgressMonitor monitor) throws CoreException {
		markAsMerged(diff, false, monitor);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.team.core.mapping.provider.MergeContext#getMergeRule(org.eclipse.
	 * team.core.diff.IDiff)
	 */
	public ISchedulingRule getMergeRule(IDiff node) {
		return ResourceDiffTree.getResourceFor(node).getProject();
	}

	/**
	 * Ensure that the parent folders of the given resource exist. This method is
	 * invoked from {@link #performReplace(IDiff, IProgressMonitor)} for files that
	 * are being merged that do not exist locally. By default, this method creates
	 * the parents using {@link IFolder#create(boolean, boolean, IProgressMonitor)}.
	 * Subclasses may override.
	 * 
	 * @param resource a resource
	 * @param monitor  a progress monitor
	 * @throws CoreException if an error occurs
	 */
	@Override
	protected void ensureParentsExist(IResource resource, IProgressMonitor monitor) throws CoreException {
		IContainer parent = resource.getParent();
		if (parent.getType() != IResource.FOLDER) {
			// this method will only create folders
			return;
		}
		if (!FileCopyUtils.exists(parent)) {
			ensureParentsExist(parent, monitor);
			File parentFile = new File(parent.getLocation().toOSString());
			parentFile.mkdir();
//			((IFolder) parent).create(false, true, monitor);
		}
	}

	@Override
	protected void performReplace(IDiff diff, IProgressMonitor monitor) throws CoreException {
		IResourceDiff d;
		IFile file = getLocalFile(diff);
		IFileRevision remote = null;
		if (diff instanceof IResourceDiff) {
			d = (IResourceDiff) diff;
			if (d != null)
				remote = d.getAfterState();
		} else {
			d = (IResourceDiff) ((IThreeWayDiff) diff).getRemoteChange();
			if (d != null)
				remote = d.getAfterState();
		}
		if (d == null) {
			d = (IResourceDiff) ((IThreeWayDiff) diff).getLocalChange();
			if (d != null)
				remote = d.getBeforeState();
		}

		// Only perform the replace if a local or remote change was found
		if (d != null) {
			performReplace(diff, file, remote, monitor);
		}
	}

	private void performReplace(final IDiff diff, final IFile file, final IFileRevision remote,
			IProgressMonitor monitor) throws CoreException {
		run(new IWorkspaceRunnable() {
			public void run(IProgressMonitor monitor) throws CoreException {
				try {
					monitor.beginTask(null, 100);
					monitor.subTask(NLS.bind(Messages.MergeContext_6, file.getFullPath().toString()));
					if ((remote == null || !remote.exists()) && file.exists()) {
						file.delete(false, true, Policy.subMonitorFor(monitor, 95));
					} else if (remote != null) {
						ensureParentsExist(file, monitor);
						InputStream stream = remote.getStorage(monitor).getContents();
						stream = new BufferedInputStream(stream);

						// Il primo tentativo di creazione dei file viene fatto mediante IFile
						// (org.eclipse.IFile)
						try {
							if (FileCopyUtils.exists(file)) {
								file.setContents(stream, true, true, Policy.subMonitorFor(monitor, 95));
							} else {
								file.create(stream, true, Policy.subMonitorFor(monitor, 95));
							}
						} catch (Exception e) {
							// In caso di fallimento viene ritestata la scrittura mediante File
							// (java.io.File)
							try {
								stream = remote.getStorage(monitor).getContents();
								stream = new BufferedInputStream(stream);

								File nativeFile = new File(file.getLocation().toOSString());
								FileOutputStream fos = new FileOutputStream(nativeFile);
								fos.write(stream.readAllBytes());
								fos.close();

							} catch (Exception ex) {
								// FIXME Gestire questa eccezione?
								ex.printStackTrace();
//								throw new RuntimeException(ex);
							} finally {
								try {
									stream.close();
								} catch (IOException ex) {
									// Ignore
								}
							}
						}
					}
					// Performing a replace should leave the file in-sync
					//file.getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor);
					makeInSync(diff, Policy.subMonitorFor(monitor, 5));
				} finally {
					monitor.done();
				}
			}
		}, getMergeRule(diff), IWorkspace.AVOID_UPDATE, monitor);
	}

	private IFile getLocalFile(IDiff delta) {
		return ResourcesPlugin.getWorkspace().getRoot().getFile(delta.getPath());
	}

}
