/*
 * Copyright 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.j2cl.frontend;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.j2cl.common.Problems;
import com.google.j2cl.common.Problems.Message;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.BindingKey;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;

/**
 * A delegator of JDT's ASTParser that provides a more convenient interface for
 * parsing source files into compilation unit.
 */
public class JdtParser {
  private final Problems problems;
  private final Map<String, String> compilerOptions = new HashMap<>();
  private final List<String> classpathEntries;
  private boolean includeRunningVMBootclasspath;

  /** Create and initialize a JdtParser based on an options object. */
  public JdtParser(FrontendOptions options, Problems problems) {
    this(options.getClasspathEntries(), problems);
  }

  /** Create and initialize a JdtParser based on passed parameters. */
  public JdtParser(List<String> classpathEntries, Problems problems) {
    compilerOptions.put(JavaCore.COMPILER_SOURCE, "1.8");
    compilerOptions.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, "1.8");
    compilerOptions.put(JavaCore.COMPILER_COMPLIANCE, "1.8");

    this.classpathEntries = ImmutableList.copyOf(classpathEntries);
    this.problems = problems;
  }

  private static final List<String> wellKnownClassNames =
      ImmutableList.of(
          "java.lang.Class",
          "java.lang.CharSequence",
          "java.lang.Comparable",
          "java.lang.Number",
          "java.lang.Object",
          "java.lang.String",
          "java.lang.Throwable");

  /** Returns a map from file paths to compilation units after JDT parsing. */
  public CompilationUnitsAndTypeBindings parseFiles(List<String> filePaths) {

    // Parse and create a compilation unit for every file.
    ASTParser parser = newASTParser(true);

    // The map must be ordered because it will be iterated over later and if it was not ordered then
    // our output would be unstable
    final Map<String, CompilationUnit> compilationUnitsByFilePath = new LinkedHashMap<>();
    final List<ITypeBinding> wellKnownTypeBindings = new ArrayList<>();

    FileASTRequestor astRequestor =
        new FileASTRequestor() {
          @Override
          public void acceptAST(String filePath, CompilationUnit compilationUnit) {
            if (compilationHasErrors(filePath, compilationUnit)) {
              return;
            }
            compilationUnitsByFilePath.put(filePath, compilationUnit);
          }

          @Override
          public void acceptBinding(String bindingKey, IBinding binding) {
            wellKnownTypeBindings.add((ITypeBinding) binding);
          }
        };
    parser.createASTs(
        filePaths.stream().toArray(String[]::new),
        getEncodings(filePaths.size()),
        wellKnownClassNames.stream().map(BindingKey::createTypeBindingKey).toArray(String[]::new),
        astRequestor,
        null);
    return new CompilationUnitsAndTypeBindings(compilationUnitsByFilePath, wellKnownTypeBindings);
  }

  public void setIncludeRunningVMBootclasspath(boolean includeRunningVMBootclasspath) {
    this.includeRunningVMBootclasspath = includeRunningVMBootclasspath;
  }

  private ASTParser newASTParser(boolean resolveBinding) {
    ASTParser parser = ASTParser.newParser(AST.JLS8);

    parser.setCompilerOptions(compilerOptions);
    parser.setResolveBindings(resolveBinding);
    parser.setEnvironment(
        Iterables.toArray(classpathEntries, String.class),
        new String[0],
        new String[0],
        includeRunningVMBootclasspath);
    return parser;
  }

  private String[] getEncodings(int length) {
    String[] encodings = new String[length];
    Arrays.fill(encodings, "UTF_8");
    return encodings;
  }

  private boolean compilationHasErrors(String filename, CompilationUnit unit) {
    boolean hasErrors = false;
    // Here we check for instances of @GwtIncompatible in the ast. If that is the case, we throw an
    // error since these should have been stripped by the build system already.
    GwtIncompatibleNodeCollector collector = new GwtIncompatibleNodeCollector();
    unit.accept(collector);
    if (!collector.getNodes().isEmpty()) {
      problems.error(Message.ERR_GWT_INCOMPATIBLE_FOUND_IN_COMPILE, filename);
      return true; // Ignore possible type errors discovered below.
    }
    for (IProblem problem : unit.getProblems()) {
      if (problem.isError()) {
        problems.error(problem.getSourceLineNumber(), filename, problem.getMessage());
        hasErrors = true;
      }
    }
    return hasErrors;
  }

  private ITypeBinding findTypeBindingByName(ITypeBinding typeBinding, String soughtTypeName) {
    if (typeBinding == null) {
      return null;
    }
    if (soughtTypeName.equals(typeBinding.getQualifiedName())) {
      return typeBinding;
    }
    ITypeBinding superTypeBinding =
        findTypeBindingByName(typeBinding.getSuperclass(), soughtTypeName);

    if (superTypeBinding != null) {
      return superTypeBinding;
    }

    for (ITypeBinding superInterfaceBinding : typeBinding.getInterfaces()) {
      superTypeBinding = findTypeBindingByName(superInterfaceBinding, soughtTypeName);
      if (superTypeBinding != null) {
        return superTypeBinding;
      }
    }

    return null;
  }
}

@@ -1664,57 +1664,8 @@
 ies;
-%0A  private boolean includeRunningVMBootclasspath;
 %0A%0A  
@@ -4062,173 +4062,8 @@
  %7D%0A%0A
-  public void setIncludeRunningVMBootclasspath(boolean includeRunningVMBootclasspath) %7B%0A    this.includeRunningVMBootclasspath = includeRunningVMBootclasspath;%0A  %7D%0A%0A
   pr
@@ -4352,24 +4352,16 @@
 .class),
-%0A       
  new Str
@@ -4367,24 +4367,16 @@
 ring%5B0%5D,
-%0A       
  new Str
@@ -4386,46 +4386,14 @@
 %5B0%5D,
-%0A        includeRunningVMBootclasspath
+ false
 );%0A 

Remove VmBootClassPath from JdtParser.