/*******************************************************************************
 * 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 - copied to jdt.core.manipulation
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.dom.fragments;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.InfixExpression;

import org.eclipse.jdt.internal.corext.SourceRangeFactory;
import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor;
import org.eclipse.jdt.internal.corext.dom.Selection;
import org.eclipse.jdt.internal.corext.dom.SelectionAnalyzer;

/**
 * Creates various differing kinds of IASTFragments, all through
 * a very narrow interface.  The kind of IASTFragment produced will depend
 * on properties of the parameters supplied to the factory methods, such
 * as the types and characteristics of AST nodes, or the location of
 * source ranges.
 *
 * In general, the client will not be aware of exactly what kind of
 * fragment is obtained from these methods.  Beyond the functionality
 * provided by the IASTFragment interface, the client can know, however,
 * based on the parameters passed, some things about the created fragment.
 * See the documentation of the factory methods.
 *
 * @see IASTFragment
 *
 */
public class ASTFragmentFactory {

	// Factory Methods: /////////////////////////////////////////////////////////////////////////

	/**
	 * Creates and returns a fragment representing the entire subtree
	 * rooted at <code>node</code>.  It is not true in general that
	 * the node to which the produced IASTFragment maps (see {@link  org.eclipse.jdt.internal.corext.dom.fragments.IASTFragment  IASTFragment})
	 * will be <code>node</code>.
	 *
	 * XXX: more doc (current assertions about input vs. output)
	 */
	public static IASTFragment createFragmentForFullSubtree(ASTNode node) {
		IASTFragment result= FragmentForFullSubtreeFactory.createFragmentFor(node);
		Assert.isNotNull(result);
		return result;
	}

	/**
	 * If possible, this method creates a fragment whose source code
	 * range is <code>range</code> within compilation unit <code>cu</code>,
	 * and which resides somewhere within the subtree identified by
	 * <code>scope</code>.
	 *
	 * XXX: more doc (current assertions about input vs. output)
	 *
	 * @param range	The source range which the create fragment must have.
	 * @param scope	A node identifying the AST subtree in which the fragment must lie.
	 * @param cu		The compilation unit to which the source range applies, and to which the AST corresponds.
	 * @return IASTFragment	A fragment whose source range is <code>range</code> within
	 * 							compilation unit <code>cu</code>, residing somewhere within the
	 * 							AST subtree identified by <code>scope</code>.
	 * @throws JavaModelException
	 */
	public static IASTFragment createFragmentForSourceRange(ISourceRange range, ASTNode scope, ICompilationUnit cu) throws JavaModelException {
		SelectionAnalyzer sa= new SelectionAnalyzer(Selection.createFromStartLength(range.getOffset(), range.getLength()), false);
		scope.accept(sa);

		if (isSingleNodeSelected(sa, range, cu))
			return ASTFragmentFactory.createFragmentForFullSubtree(sa.getFirstSelectedNode());
		if (isEmptySelectionCoveredByANode(range, sa))
			return ASTFragmentFactory.createFragmentForFullSubtree(sa.getLastCoveringNode());
		return ASTFragmentFactory.createFragmentForSubPartBySourceRange(sa.getLastCoveringNode(), range, cu);
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////


	private static boolean isEmptySelectionCoveredByANode(ISourceRange range, SelectionAnalyzer sa) {
		return range.getLength() == 0 && sa.getFirstSelectedNode() == null && sa.getLastCoveringNode() != null;
	}

	private static boolean isSingleNodeSelected(SelectionAnalyzer sa, ISourceRange range, ICompilationUnit cu) throws JavaModelException {
		return sa.getSelectedNodes().length == 1 && !rangeIncludesNonWhitespaceOutsideNode(range, sa.getFirstSelectedNode(), cu);
	}

	private static boolean rangeIncludesNonWhitespaceOutsideNode(ISourceRange range, ASTNode node, ICompilationUnit cu) throws JavaModelException {
		return Util.rangeIncludesNonWhitespaceOutsideRange(range, SourceRangeFactory.create(node), cu.getBuffer());
	}


	/**
	 * Returns <code>null</code> if the indices, taken with respect to
	 * the node, do not correspond to a valid node-sub-part
	 * fragment.
	 */
	private static IASTFragment createFragmentForSubPartBySourceRange(ASTNode node, ISourceRange range, ICompilationUnit cu) throws JavaModelException {
		return FragmentForSubPartBySourceRangeFactory.createFragmentFor(node, range, cu);
	}

	private static class FragmentForFullSubtreeFactory extends FragmentFactory {
		public static IASTFragment createFragmentFor(ASTNode node) {
			return new FragmentForFullSubtreeFactory().createFragment(node);
		}

		@Override
		public boolean visit(InfixExpression node) {
			/* Try creating an associative infix expression fragment
			/* for the full subtree.  If this is not applicable,
			 * try something more generic.
			 */
			IASTFragment fragment= AssociativeInfixExpressionFragment.createFragmentForFullSubtree(node);
			if(fragment == null)
				return visit((Expression) node);

			setFragment(fragment);
			return false;
		}
		@Override
		public boolean visit(Expression node) {
			setFragment(new SimpleExpressionFragment(node));
			return false;
		}
		@Override
		public boolean visit(ASTNode node) {
			setFragment(new SimpleFragment(node));
			return false;
		}
	}
	private static class FragmentForSubPartBySourceRangeFactory extends FragmentFactory {
		private ISourceRange fRange;
		private ICompilationUnit fCu;

		private JavaModelException javaModelException= null;

		public static IASTFragment createFragmentFor(ASTNode node, ISourceRange range, ICompilationUnit cu) throws JavaModelException {
			return new FragmentForSubPartBySourceRangeFactory().createFragment(node, range, cu);
		}

		@Override
		public boolean visit(InfixExpression node) {
			try {
				setFragment(createInfixExpressionSubPartFragmentBySourceRange(node, fRange, fCu));
			} catch(JavaModelException e) {
				javaModelException= e;
			}
			return false;
		}

		@Override
		public boolean visit(ASTNode node) {
			//let fragment be null
			return false;
		}

		protected IASTFragment createFragment(ASTNode node, ISourceRange range, ICompilationUnit cu) throws JavaModelException {
			fRange= range;
			fCu= cu;
			IASTFragment result= createFragment(node);
			if(javaModelException != null)
				throw javaModelException;
			return result;
		}

		private static IExpressionFragment createInfixExpressionSubPartFragmentBySourceRange(InfixExpression node, ISourceRange range, ICompilationUnit cu) throws JavaModelException {
			return AssociativeInfixExpressionFragment.createSubPartFragmentBySourceRange(node, range, cu);
		}
	}
	private static abstract class FragmentFactory extends HierarchicalASTVisitor {
		private IASTFragment fFragment;

		protected IASTFragment createFragment(ASTNode node) {
			fFragment= null;
			node.accept(this);
			return fFragment;
		}

		protected final IASTFragment getFragment() {
			return fFragment;
		}
		protected final void setFragment(IASTFragment fragment) {
			Assert.isTrue(!isFragmentSet());
			fFragment= fragment;
		}
		protected final boolean isFragmentSet() {
			return getFragment() != null;
		}
	}

	private ASTFragmentFactory() {
	}
}
