/*******************************************************************************
 * Copyright (c) 2017 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.core.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.core.BasicCompilationUnit;
import org.eclipse.jdt.internal.core.ClasspathEntry;
import org.eclipse.jdt.internal.core.builder.NameEnvironment;
import org.eclipse.jdt.internal.core.builder.ProblemFactory;

public class ModuleUtil {

	static class ModuleAccumulatorEnvironment extends NameEnvironment {
		public ModuleAccumulatorEnvironment(IJavaProject javaProject) {
			super(javaProject);
		}

		Set<String> modules = new HashSet<>();
		public String[] getModules() {
			this.modules.remove(String.valueOf(TypeConstants.JAVA_BASE));
			String[] mods = new String[this.modules.size()];
			return this.modules.toArray(mods);
		}

		@Override
		protected boolean isOnModulePath(ClasspathEntry entry) {
			return true; // try to interpret all dependencies as modules from now on
		}

		@Override
		public void cleanup() {
			this.modules.clear();
		}

		@Override
		public NameEnvironmentAnswer findType(char[][] compoundTypeName, char[] moduleName) {
			NameEnvironmentAnswer answer = super.findType(compoundTypeName, moduleName);
			if (answer != null && answer.moduleName() != null) {
				this.modules.add(String.valueOf(answer.moduleName()));
			}
			return answer;
		}

		@Override
		public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName, char[] moduleName) {
			NameEnvironmentAnswer answer = super.findType(typeName, packageName, moduleName);
			if (answer != null && answer.moduleName() != null) {
				this.modules.add(String.valueOf(answer.moduleName()));
			}
			return answer;
		}
	}
	private static Compiler newCompiler(ModuleAccumulatorEnvironment environment, IJavaProject javaProject) {
		Map<String, String> projectOptions = javaProject.getOptions(true);
		CompilerOptions compilerOptions = new CompilerOptions(projectOptions);
		compilerOptions.performMethodsFullRecovery = true;
		compilerOptions.performStatementsRecovery = true;
		ICompilerRequestor requestor = new ICompilerRequestor() {
			@Override
			public void acceptResult(CompilationResult result) {
				// Nothing to do here
			}
		};
		Compiler newCompiler = new Compiler(
			environment,
			DefaultErrorHandlingPolicies.proceedWithAllProblems(),
			compilerOptions,
			requestor,
			ProblemFactory.getProblemFactory(Locale.getDefault()));

		return newCompiler;
	}
	public static String[] getReferencedModules(IJavaProject project) throws CoreException {

		ModuleAccumulatorEnvironment environment = new ModuleAccumulatorEnvironment(project);
		Compiler compiler = newCompiler(environment, project);
		// First go over the binary roots and see if any of them are modules
		List<String> required = new ArrayList<>();
		Set<org.eclipse.jdt.internal.compiler.env.ICompilationUnit> toCompile = new HashSet<>();
		IPackageFragmentRoot[] roots = project.getPackageFragmentRoots();
		for (IPackageFragmentRoot root : roots) {
			if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
				IJavaElement[] children = root.getChildren();
				for (IJavaElement child : children) {
					if (child instanceof IPackageFragment) {
						IPackageFragment fragment = (IPackageFragment) child;
						if (fragment.isDefaultPackage()) continue;
						ICompilationUnit[] units = fragment.getCompilationUnits();
						if (units.length != 0) {
							String pack = fragment.getElementName();
							for (ICompilationUnit iUnit : units) {
								org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceFile = 
										new BasicCompilationUnit(iUnit.getSource().toCharArray(),
												CharOperation.splitOn('.', pack.toCharArray()),
												iUnit.getPath().toOSString(),
												iUnit);
								toCompile.add(sourceFile);
							}
						}
					}
				}
			}
		}

		org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] sources = new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[toCompile.size()];
		toCompile.toArray(sources);
		compiler.compile(sources);
		String[] mods = environment.getModules();
		for (String string : mods) {
			required.add(string);
		}
		Collections.sort(required, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.compareTo(o2);
			}
		});
		return required.toArray(new String[required.size()]);
	}
}
