/*******************************************************************************
 * Copyright (c) 2002, 2013 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.aspectj.org.eclipse.jdt.internal.core.util;

import java.util.Locale;
import java.util.Map;

import org.aspectj.org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.aspectj.org.eclipse.jdt.internal.compiler.CompilationResult;
import org.aspectj.org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.Expression;
import org.aspectj.org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.aspectj.org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.aspectj.org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.aspectj.org.eclipse.jdt.internal.compiler.problem.ProblemReporter;

/**
 * Utility class to parse different code snippets
 */
public class CodeSnippetParsingUtil {

	public RecordedParsingInformation recordedParsingInformation;
	public boolean ignoreMethodBodies;

	public CodeSnippetParsingUtil(boolean ignoreMethodBodies) {
		this.ignoreMethodBodies = ignoreMethodBodies;
	}

	public CodeSnippetParsingUtil() {
		this(false);
	}

	private RecordedParsingInformation getRecordedParsingInformation(CompilationResult compilationResult, int[][] commentPositions) {
		int problemsCount = compilationResult.problemCount;
		CategorizedProblem[] problems = null;
		if (problemsCount != 0) {
			final CategorizedProblem[] compilationResultProblems = compilationResult.problems;
			if (compilationResultProblems.length == problemsCount) {
				problems = compilationResultProblems;
			} else {
				System.arraycopy(compilationResultProblems, 0, (problems = new CategorizedProblem[problemsCount]), 0, problemsCount);
			}
		}
		return new RecordedParsingInformation(problems, compilationResult.getLineSeparatorPositions(), commentPositions);
	}

	public ASTNode[] parseClassBodyDeclarations(char[] source, Map<String, String> settings, boolean recordParsingInformation) {
		return parseClassBodyDeclarations(source, 0, source.length, settings, recordParsingInformation, false);
	}

	public ASTNode[] parseClassBodyDeclarations(
			char[] source,
			int offset,
			int length,
			Map<String, String> settings,
			boolean recordParsingInformation,
			boolean enabledStatementRecovery) {
		if (source == null) {
			throw new IllegalArgumentException();
		}
		CompilerOptions compilerOptions = new CompilerOptions(settings);
		compilerOptions.ignoreMethodBodies = this.ignoreMethodBodies;
		final ProblemReporter problemReporter = new ProblemReporter(
					DefaultErrorHandlingPolicies.proceedWithAllProblems(),
					compilerOptions,
					new DefaultProblemFactory(Locale.getDefault()));

		CommentRecorderParser parser = new CommentRecorderParser(problemReporter, false);
		parser.setMethodsFullRecovery(false);
		parser.setStatementsRecovery(enabledStatementRecovery);

		ICompilationUnit sourceUnit =
			new CompilationUnit(
				source,
				"", //$NON-NLS-1$
				compilerOptions.defaultEncoding);

		CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, compilerOptions.maxProblemsPerUnit);
		final CompilationUnitDeclaration compilationUnitDeclaration = new CompilationUnitDeclaration(problemReporter, compilationResult, source.length);
		ASTNode[] result = parser.parseClassBodyDeclarations(source, offset, length, compilationUnitDeclaration);

		if (recordParsingInformation) {
			this.recordedParsingInformation = getRecordedParsingInformation(compilationResult, compilationUnitDeclaration.comments);
		}
		return result;
	}

	public CompilationUnitDeclaration parseCompilationUnit(char[] source, Map<String, String> settings, boolean recordParsingInformation) {
		if (source == null) {
			throw new IllegalArgumentException();
		}
		CompilerOptions compilerOptions = new CompilerOptions(settings);
		compilerOptions.ignoreMethodBodies = this.ignoreMethodBodies;
		CommentRecorderParser parser =
			new CommentRecorderParser(
				new ProblemReporter(
					DefaultErrorHandlingPolicies.proceedWithAllProblems(),
					compilerOptions,
					new DefaultProblemFactory(Locale.getDefault())),
			false);

		ICompilationUnit sourceUnit =
			new CompilationUnit(
				source,
				"", //$NON-NLS-1$
				compilerOptions.defaultEncoding);
		final CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, compilerOptions.maxProblemsPerUnit);
		CompilationUnitDeclaration compilationUnitDeclaration = parser.dietParse(sourceUnit, compilationResult);

		if (recordParsingInformation) {
			this.recordedParsingInformation = getRecordedParsingInformation(compilationResult, compilationUnitDeclaration.comments);
		}

		if (compilationUnitDeclaration.ignoreMethodBodies) {
			compilationUnitDeclaration.ignoreFurtherInvestigation = true;
			// if initial diet parse did not work, no need to dig into method bodies.
			return compilationUnitDeclaration;
		}

		//fill the methods bodies in order for the code to be generated
		//real parse of the method....
		parser.scanner.setSource(compilationResult);
		org.aspectj.org.eclipse.jdt.internal.compiler.ast.TypeDeclaration[] types = compilationUnitDeclaration.types;
		if (types != null) {
			for (int i = 0, length = types.length; i < length; i++) {
				types[i].parseMethods(parser, compilationUnitDeclaration);
			}
		}

		if (recordParsingInformation) {
			this.recordedParsingInformation.updateRecordedParsingInformation(compilationResult);
		}
		return compilationUnitDeclaration;
	}

	public Expression parseExpression(char[] source, Map<String, String> settings, boolean recordParsingInformation) {
		return parseExpression(source, 0, source.length, settings, recordParsingInformation);
	}

	public Expression parseExpression(char[] source, int offset, int length, Map<String, String> settings, boolean recordParsingInformation) {

		if (source == null) {
			throw new IllegalArgumentException();
		}
		CompilerOptions compilerOptions = new CompilerOptions(settings);
		// in this case we don't want to ignore method bodies since we are parsing only an expression
		final ProblemReporter problemReporter = new ProblemReporter(
					DefaultErrorHandlingPolicies.proceedWithAllProblems(),
					compilerOptions,
					new DefaultProblemFactory(Locale.getDefault()));

		CommentRecorderParser parser = new CommentRecorderParser(problemReporter, false);

		ICompilationUnit sourceUnit =
			new CompilationUnit(
				source,
				"", //$NON-NLS-1$
				compilerOptions.defaultEncoding);

		CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, compilerOptions.maxProblemsPerUnit);
		CompilationUnitDeclaration unit = new CompilationUnitDeclaration(problemReporter, compilationResult, source.length);
		Expression result = parser.parseExpression(source, offset, length, unit, true /* record line separators */);

		if (recordParsingInformation) {
			this.recordedParsingInformation = getRecordedParsingInformation(compilationResult, unit.comments);
		}
		return result;
	}

	public ConstructorDeclaration parseStatements(char[] source, Map<String, String> settings, boolean recordParsingInformation, boolean enabledStatementRecovery) {
		return parseStatements(source, 0, source.length, settings, recordParsingInformation, enabledStatementRecovery);
	}

	public ConstructorDeclaration parseStatements(
			char[] source,
			int offset,
			int length,
			Map<String, String> settings,
			boolean recordParsingInformation,
			boolean enabledStatementRecovery) {
		if (source == null) {
			throw new IllegalArgumentException();
		}
		CompilerOptions compilerOptions = new CompilerOptions(settings);
		// in this case we don't want to ignore method bodies since we are parsing only statements
		final ProblemReporter problemReporter = new ProblemReporter(
					DefaultErrorHandlingPolicies.proceedWithAllProblems(),
					compilerOptions,
					new DefaultProblemFactory(Locale.getDefault()));
		CommentRecorderParser parser = new CommentRecorderParser(problemReporter, false);
		parser.setMethodsFullRecovery(false);
		parser.setStatementsRecovery(enabledStatementRecovery);

		ICompilationUnit sourceUnit =
			new CompilationUnit(
				source,
				"", //$NON-NLS-1$
				compilerOptions.defaultEncoding);

		final CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, compilerOptions.maxProblemsPerUnit);
		CompilationUnitDeclaration compilationUnitDeclaration = new CompilationUnitDeclaration(problemReporter, compilationResult, length);

		ConstructorDeclaration constructorDeclaration = new ConstructorDeclaration(compilationResult);
		constructorDeclaration.sourceEnd  = -1;
		constructorDeclaration.declarationSourceEnd = offset + length - 1;
		constructorDeclaration.bodyStart = offset;
		constructorDeclaration.bodyEnd = offset + length - 1;

		parser.scanner.setSource(compilationResult);
		parser.scanner.resetTo(offset, offset + length);
		parser.parse(constructorDeclaration, compilationUnitDeclaration, true);

		if (recordParsingInformation) {
			this.recordedParsingInformation = getRecordedParsingInformation(compilationResult, compilationUnitDeclaration.comments);
		}
		return constructorDeclaration;
	}
}
