/*******************************************************************************
 * Copyright (c) 2000, 2021 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
 *     Microsoft Corporation - read formatting options from the compilation unit
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.codemanipulation;

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.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;

import org.eclipse.core.resources.IWorkspaceRunnable;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;

import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.TextUtilities;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.manipulation.CodeGeneration;

import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.core.manipulation.util.Strings;
import org.eclipse.jdt.internal.corext.dom.TokenScanner;
import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
import org.eclipse.jdt.internal.corext.util.SuperTypeHierarchyCache;

import org.eclipse.jdt.internal.ui.JavaUIStatus;

/**
 * Add javadoc stubs to members. All members must belong to the same compilation unit.
 * If the parent type is open in an editor, be sure to pass over its working copy.
 */
public class AddJavaDocStubOperation implements IWorkspaceRunnable {

	private IMember[] fMembers;

	public AddJavaDocStubOperation(IMember[] members) {
		Assert.isLegal(members.length > 0);
		fMembers= members;
	}

	private String createTypeComment(IType type, String lineDelimiter) throws CoreException {
		String[] typeParameterNames= StubUtility.getTypeParameterNames(type.getTypeParameters());
		return CodeGeneration.getTypeComment(type.getCompilationUnit(), type.getTypeQualifiedName('.'), typeParameterNames, lineDelimiter);
	}

	private String createMethodComment(IMethod meth, String lineDelimiter) throws CoreException {
		IType declaringType= meth.getDeclaringType();

		IMethod overridden= null;
		if (!meth.isConstructor()) {
			ITypeHierarchy hierarchy= SuperTypeHierarchyCache.getTypeHierarchy(declaringType);
			MethodOverrideTester tester= new MethodOverrideTester(declaringType, hierarchy);
			overridden= tester.findOverriddenMethod(meth, true);
		}
		return CodeGeneration.getMethodComment(meth, overridden, lineDelimiter);
	}

	private String createFieldComment(IField field, String lineDelimiter) throws JavaModelException, CoreException {
		String typeName= Signature.toString(field.getTypeSignature());
		String fieldName= field.getElementName();
		return CodeGeneration.getFieldComment(field.getCompilationUnit(), typeName, fieldName, lineDelimiter);
	}

	/**
	 * @return Returns the scheduling rule for this operation
	 */
	public ISchedulingRule getScheduleRule() {
		return fMembers[0].getResource();
	}

	/*
	 * @see org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public void run(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
		if (monitor == null)
			monitor= new NullProgressMonitor();

		try {
			monitor.beginTask(CodeGenerationMessages.AddJavaDocStubOperation_description, fMembers.length + 2);

			addJavadocComments(monitor);
		} finally {
			monitor.done();
		}
	}

	private void addJavadocComments(IProgressMonitor monitor) throws CoreException {
		ICompilationUnit cu= fMembers[0].getCompilationUnit();

		ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
		IPath path= cu.getPath();

		manager.connect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
		try {
			IDocument document= manager.getTextFileBuffer(path, LocationKind.IFILE).getDocument();

			String lineDelim= TextUtilities.getDefaultLineDelimiter(document);
			MultiTextEdit edit= new MultiTextEdit();

			for (IMember curr : fMembers) {
				int memberStartOffset= getMemberStartOffset(curr, document);

				String comment= null;
				switch (curr.getElementType()) {
					case IJavaElement.TYPE:
						comment= createTypeComment((IType) curr, lineDelim);
						break;
					case IJavaElement.FIELD:
						comment= createFieldComment((IField) curr, lineDelim);
						break;
					case IJavaElement.METHOD:
						comment= createMethodComment((IMethod) curr, lineDelim);
						break;
				}
				if (comment == null) {
					StringBuilder buf= new StringBuilder();
					buf.append("/**").append(lineDelim); //$NON-NLS-1$
					buf.append(" *").append(lineDelim); //$NON-NLS-1$
					buf.append(" */").append(lineDelim); //$NON-NLS-1$
					comment= buf.toString();
				} else {
					if (!comment.endsWith(lineDelim)) {
						comment= comment + lineDelim;
					}
				}

				IRegion region= document.getLineInformationOfOffset(memberStartOffset);

				String line= document.get(region.getOffset(), region.getLength());
				String indentString= Strings.getIndentString(line, cu);

				String indentedComment= Strings.changeIndent(comment, 0, cu, indentString, lineDelim);

				edit.addChild(new InsertEdit(memberStartOffset, indentedComment));

				monitor.worked(1);
			}
			edit.apply(document); // apply all edits
		} catch (BadLocationException e) {
			throw new CoreException(JavaUIStatus.createError(IStatus.ERROR, e));
		} finally {
			manager.disconnect(path, LocationKind.IFILE,new SubProgressMonitor(monitor, 1));
		}
	}

	private int getMemberStartOffset(IMember curr, IDocument document) throws JavaModelException {
		int offset= curr.getSourceRange().getOffset();
		TokenScanner scanner= new TokenScanner(document, curr.getJavaProject());
		try {
			return scanner.getNextStartOffset(offset, true); // read to the first real non comment token
		} catch (CoreException e) {
			// ignore
		}
		return offset;
	}

}
