/*******************************************************************************
 * Copyright (c) 2006, 2015 IBM 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ltk.internal.core.refactoring.resource.undostates;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFileState;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspaceRoot;

import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;

/**
 * {@link FileUndoState} is a lightweight description that describes a file to be
 * created.
 *
 * This class is not intended to be instantiated or used by clients.
 *
 * @since 3.4
 *
 */
public class FileUndoState extends AbstractResourceUndoState {

	protected String name;

	private URI location;
	private String charset;
	private IFileContentDescription fileContentDescription;

	/**
	 * Create a {@link FileUndoState} that can be used to later restore the given
	 * file. The file typically already exists, but this constructor will not
	 * fail if the file does not exist.
	 *
	 * @param file
	 *            the file to be restored.
	 */
	public FileUndoState(IFile file) {
		super(file);
		this.name= file.getName();
		try {
			this.charset= file.getCharset(false);
		} catch (CoreException e) {
			// we don't care, a null charset is fine.
		}
		if (file.isLinked()) {
			location= file.getLocationURI();
		}

	}

	/**
	 * Create a {@link FileUndoState} from the specified file handle. The handle does
	 * not exist, so no information should be derived from it. If a location
	 * path is specified, this file should represent a link to another location.
	 * The content description describes any state that should be used when the
	 * file resource is created.
	 *
	 * @param file
	 *            the file to be described
	 * @param linkLocation
	 *            the location of the file's link, or <code>null</code> if the
	 *            file is not linked
	 * @param fileContentDescription
	 *            the file content description that can be used to get
	 *            information about the file, such as its initial content
	 */
	public FileUndoState(IFile file, URI linkLocation, IFileContentDescription fileContentDescription) {
		super(file);
		this.name= file.getName();
		this.location= linkLocation;
		this.charset= null;
		this.fileContentDescription= fileContentDescription;
	}

	@Override
	public void recordStateFromHistory(IResource resource, IProgressMonitor monitor) throws CoreException {
		Assert.isLegal(resource.getType() == IResource.FILE);

		if (location != null) {
			// file is linked, no need to record any history
			return;
		}
		IFileState[] states= ((IFile) resource).getHistory(monitor);
		if (states.length > 0) {
			final IFileState state= getMatchingFileState(states);
			this.fileContentDescription= new IFileContentDescription() {
				@Override
				public boolean exists() {
					return state.exists();
				}

				@Override
				public InputStream getContents() throws CoreException {
					return state.getContents();
				}

				@Override
				public String getCharset() throws CoreException {
					return state.getCharset();
				}
			};
		}
	}

	@Override
	public IResource createResourceHandle() {
		IWorkspaceRoot workspaceRoot= parent.getWorkspace().getRoot();
		IPath fullPath= parent.getFullPath().append(name);
		return workspaceRoot.getFile(fullPath);
	}

	@Override
	public void createExistentResourceFromHandle(IResource resource, IProgressMonitor monitor) throws CoreException {

		Assert.isLegal(resource instanceof IFile);
		if (resource.exists()) {
			return;
		}
		IFile fileHandle= (IFile) resource;
		monitor.beginTask("", 200); //$NON-NLS-1$
		monitor.setTaskName(RefactoringCoreMessages.FileDescription_NewFileProgress);
		try {
			if (monitor.isCanceled()) {
				throw new OperationCanceledException();
			}
			if (location != null) {
				fileHandle.createLink(location, IResource.ALLOW_MISSING_LOCAL, new SubProgressMonitor(monitor, 200));
			} else {
				InputStream contents;
				// Retrieve the contents from the file content
				// description. Other file state attributes, such as timestamps,
				// have already been retrieved from the original IResource
				// object and are restored in #restoreResourceAttributes
				if (fileContentDescription != null && fileContentDescription.exists()) {
					contents= fileContentDescription.getContents();
				} else {
					contents= new ByteArrayInputStream(RefactoringCoreMessages.FileDescription_ContentsCouldNotBeRestored.getBytes());
				}
				fileHandle.create(contents, false, new SubProgressMonitor(monitor, 100));
				fileHandle.setCharset(charset, new SubProgressMonitor(monitor, 100));
			}
			if (monitor.isCanceled()) {
				throw new OperationCanceledException();
			}
		} catch (CoreException e) {
			if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) {
				fileHandle.refreshLocal(IResource.DEPTH_ZERO, null);
			} else {
				throw e;
			}
		} finally {
			monitor.done();
		}
	}

	@Override
	public boolean isValid() {
		if (location != null) {
			return super.isValid();
		}
		return super.isValid() && fileContentDescription != null && fileContentDescription.exists();
	}

	@Override
	public String getName() {
		return name;
	}

	/**
	 * Get the file state that matches this file description. The local time
	 * stamp is used to try to find a matching file state. If none can be found,
	 * the most recent copy of the file state is used.
	 * @param states file states
	 * @return  best guess state
	 */
	private IFileState getMatchingFileState(IFileState[] states) {
		for (IFileState state : states) {
			if (localTimeStamp == state.getModificationTime()) {
				return state;
			}
		}
		return states[0];

	}

	@Override
	protected void restoreResourceAttributes(IResource resource) throws CoreException {
		super.restoreResourceAttributes(resource);
		Assert.isLegal(resource instanceof IFile);
		IFile file= (IFile) resource;
		if (charset != null) {
			file.setCharset(charset, null);
		}
	}
}
