/*******************************************************************************
 * Copyright (c) 2000, 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.core.filebuffers.manipulation;

import java.util.Map;

import org.eclipse.core.internal.filebuffers.FileBuffersPlugin;
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.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;

import org.eclipse.core.filebuffers.IFileBuffer;
import org.eclipse.core.filebuffers.IFileBufferStatusCodes;
import org.eclipse.core.filebuffers.ITextFileBuffer;

import org.eclipse.text.edits.TextEdit;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.TextUtilities;

/**
 * Abstraction for a file buffer operation that works on text file buffers.
 * Subclasses have to override the <code>computeTextEdits</code> method in
 * order to provide a sequence of {@link org.eclipse.text.edits.TextEdit}
 * objects.
 *
 * @since 3.1
 */
public abstract class TextFileBufferOperation implements IFileBufferOperation {


	/**
	 * Computes and returns a text edit. Subclasses have to provide that method.
	 *
	 * @param textFileBuffer the text file buffer to manipulate
	 * @param progressMonitor the progress monitor
	 * @return the text edits describing the content manipulation
	 * @throws CoreException in case the computation failed
	 * @throws OperationCanceledException in case the progress monitor has been set to canceled
	 */
	protected abstract MultiTextEditWithProgress computeTextEdit(ITextFileBuffer textFileBuffer, IProgressMonitor progressMonitor) throws CoreException, OperationCanceledException;

	/**
	 * Returns the rewrite session type that corresponds to the text edit sequence.
	 *
	 * @return the rewrite session type
	 */
	protected abstract DocumentRewriteSessionType getDocumentRewriteSessionType();


	private String fOperationName;
	private DocumentRewriteSession fDocumentRewriteSession;

	/**
	 * Creates a new operation with the given name.
	 *
	 * @param operationName the name of the operation
	 */
	protected TextFileBufferOperation(String operationName) {
		fOperationName= operationName;
	}

	@Override
	public String getOperationName() {
		return fOperationName;
	}

	@Override
	public void run(IFileBuffer fileBuffer, IProgressMonitor progressMonitor) throws CoreException, OperationCanceledException {

		if (fileBuffer instanceof ITextFileBuffer) {
			ITextFileBuffer textFileBuffer= (ITextFileBuffer) fileBuffer;
			IPath path= textFileBuffer.getLocation();
			String taskName= path == null ? getOperationName() : path.lastSegment();
			SubMonitor subMonitor= SubMonitor.convert(progressMonitor, taskName, 100);
			MultiTextEditWithProgress edit= computeTextEdit(textFileBuffer, subMonitor.split(10));
			if (edit != null) {
				Map<String, IDocumentPartitioner> stateData= startRewriteSession(textFileBuffer);
				try {
					applyTextEdit(textFileBuffer, edit, subMonitor.split(90));
				} finally {
					stopRewriteSession(textFileBuffer, stateData);
				}
			}
		}
	}

	private Map<String, IDocumentPartitioner> startRewriteSession(ITextFileBuffer fileBuffer) {
		Map<String, IDocumentPartitioner> stateData= null;

		IDocument document= fileBuffer.getDocument();
		if (document instanceof IDocumentExtension4) {
			IDocumentExtension4 extension= (IDocumentExtension4) document;
			fDocumentRewriteSession= extension.startRewriteSession(getDocumentRewriteSessionType());
		} else
			stateData= TextUtilities.removeDocumentPartitioners(document);

		return stateData;
	}

	private void stopRewriteSession(ITextFileBuffer fileBuffer, Map<String, IDocumentPartitioner> stateData) {
		IDocument document= fileBuffer.getDocument();
		if (document instanceof IDocumentExtension4) {
			IDocumentExtension4 extension= (IDocumentExtension4) document;
			extension.stopRewriteSession(fDocumentRewriteSession);
			fDocumentRewriteSession= null;
		} else if (stateData != null)
			TextUtilities.addDocumentPartitioners(document, stateData);
	}

	private void applyTextEdit(ITextFileBuffer fileBuffer, MultiTextEditWithProgress textEdit, IProgressMonitor progressMonitor) throws CoreException, OperationCanceledException {
		try {
			textEdit.apply(fileBuffer.getDocument(), TextEdit.NONE, progressMonitor);
		} catch (BadLocationException x) {
			throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CONTENT_CHANGE_FAILED, "", x)); //$NON-NLS-1$
		}
	}
}
