/*******************************************************************************
 * Copyright (c) 2000, 2019 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 - refactored to jdt.core.manipulation
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.codemanipulation;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;

import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;

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

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;

import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;

/**
 * Workspace runnable to add custom constructors initializing fields.
 *
 * @since 3.1
 */
public final class AddCustomConstructorOperation implements IWorkspaceRunnable {

	/** Should the resulting edit be applied? */
	private boolean fApply= true;

	/** The super constructor method binding */
	private final IMethodBinding fConstructorBinding;

	/** The variable bindings to implement */
	private final IVariableBinding[] fFieldBindings;

	/** The resulting text edit */
	private TextEdit fResultingEdit= null;

	/** The insertion point, or <code>null</code> */
	private final IJavaElement fInsert;

	/** Should the call to the super constructor be omitted? */
	private boolean fOmitSuper= false;

	/** Should the compilation unit content be saved? */
	private final boolean fSave;

	/** The code generation settings to use */
	private final CodeGenerationSettings fSettings;

	/** The type declaration to add the constructors to */
	private final ITypeBinding fParentType;

	/** The compilation unit ast node */
	private final CompilationUnit fASTRoot;

	/** The visibility flags of the new constructor */
	private int fVisibility= 0;

	/**
	 * Creates a new add custom constructor operation.
	 *
	 * @param astRoot the compilation unit ast node
	 * @param parentType the type to add the methods to
	 * 	@param variables the variable bindings to use in the constructor
	 * @param constructor the method binding of the super constructor
	 * @param insert the insertion point, or <code>null</code>


	 * @param settings the code generation settings to use
	 * @param apply <code>true</code> if the resulting edit should be applied, <code>false</code> otherwise
	 * @param save <code>true</code> if the changed compilation unit should be saved, <code>false</code> otherwise
	 */
	public AddCustomConstructorOperation(CompilationUnit astRoot, ITypeBinding parentType, IVariableBinding[] variables, IMethodBinding constructor, IJavaElement insert, CodeGenerationSettings settings, boolean apply, boolean save) {
		Assert.isTrue(astRoot != null && astRoot.getTypeRoot() instanceof ICompilationUnit);
		Assert.isNotNull(parentType);
		Assert.isNotNull(variables);
		Assert.isNotNull(constructor);
		Assert.isNotNull(settings);
		fParentType= parentType;
		fInsert= insert;
		fASTRoot= astRoot;
		fFieldBindings= variables;
		fConstructorBinding= constructor;
		fSettings= settings;
		fSave= save;
		fApply= apply;
	}

	/**
	 * Returns the resulting text edit.
	 *
	 * @return the resulting text edit
	 */
	public final TextEdit getResultingEdit() {
		return fResultingEdit;
	}

	/**
	 * Returns the scheduling rule for this operation.
	 *
	 * @return the scheduling rule
	 */
	public final ISchedulingRule getSchedulingRule() {
		return ResourcesPlugin.getWorkspace().getRoot();
	}

	/**
	 * Returns the visibility modifier of the generated constructors.
	 *
	 * @return the visibility modifier
	 */
	public final int getVisibility() {
		return fVisibility;
	}

	/**
	 * Should the call to the super constructor be omitted?
	 *
	 * @return <code>true</code> to omit the call, <code>false</code> otherwise
	 */
	public final boolean isOmitSuper() {
		return fOmitSuper;
	}

	/*
	 * @see org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	public final void run(IProgressMonitor monitor) throws CoreException {
		if (monitor == null)
			monitor= new NullProgressMonitor();
		try {
			monitor.beginTask("", 2); //$NON-NLS-1$
			monitor.setTaskName(CodeGenerationMessages.AddCustomConstructorOperation_description);

			ICompilationUnit cu= (ICompilationUnit) fASTRoot.getTypeRoot();

			ASTRewrite astRewrite= ASTRewrite.create(fASTRoot.getAST());
			ImportRewrite importRewrite= StubUtility.createImportRewrite(fASTRoot, true);

			ListRewrite listRewriter= null;

			ASTNode typeDecl= fASTRoot.findDeclaringNode(fParentType);
			if (typeDecl instanceof AbstractTypeDeclaration) {
				listRewriter= astRewrite.getListRewrite(typeDecl, ((AbstractTypeDeclaration) typeDecl).getBodyDeclarationsProperty());
			} else if (typeDecl instanceof AnonymousClassDeclaration) {
				listRewriter= astRewrite.getListRewrite(typeDecl, AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY);
			}

			if (listRewriter != null) {
				ImportRewriteContext context= new ContextSensitiveImportRewriteContext(typeDecl, importRewrite);
				MethodDeclaration stub= StubUtility2Core.createConstructorStub(cu, astRewrite, importRewrite, context, fParentType, fOmitSuper ? null : fConstructorBinding, fFieldBindings, fVisibility, fSettings);
				if (stub != null) {
					ASTNode insertion= StubUtility2Core.getNodeToInsertBefore(listRewriter, fInsert);
					if (insertion != null && insertion.getParent() == typeDecl) {
						listRewriter.insertBefore(stub, insertion, null);
					} else {
						listRewriter.insertLast(stub, null);
					}
				}
				fResultingEdit= new MultiTextEdit();
				fResultingEdit.addChild(astRewrite.rewriteAST());
				fResultingEdit.addChild(importRewrite.rewriteImports(new SubProgressMonitor(monitor, 1)));

				if (fApply) {
					JavaModelUtil.applyEdit(cu, fResultingEdit, fSave, new SubProgressMonitor(monitor, 1));
				}
			}
		} finally {
			monitor.done();
		}
	}

	/**
	 * Determines whether the call to the super constructor should be omitted.
	 *
	 * @param omit <code>true</code> to omit the call, <code>false</code> otherwise
	 */
	public final void setOmitSuper(final boolean omit) {
		fOmitSuper= omit;
	}

	/**
	 * Sets the visibility modifier of the generated constructors.
	 *
	 * @param visibility the visibility modifier
	 */
	public final void setVisibility(final int visibility) {
		fVisibility= visibility;
	}
}
