/*
 * Copyright 2014-present Facebook, 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.facebook.buck.java;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclarationStatement;

import java.util.LinkedList;
import java.util.Map;

/**
 * Extracts the set of exported symbols (class and enum names) from a Java code file, using the
 * ASTParser from Eclipse.
 */
public class JavaFileParser {

  private final int jlsLevel;
  private final String javaVersion;

  private static final ImmutableMap<String, String> javaVersionMap =
      ImmutableMap.<String, String>builder()
      .put("1", JavaCore.VERSION_1_1)
      .put("2", JavaCore.VERSION_1_2)
      .put("3", JavaCore.VERSION_1_3)
      .put("4", JavaCore.VERSION_1_4)
      .put("5", JavaCore.VERSION_1_5)
      .put("6", JavaCore.VERSION_1_6)
      .put("7", JavaCore.VERSION_1_7)
      .build();

  private JavaFileParser(int jlsLevel, String javaVersion) {
    this.jlsLevel = jlsLevel;
    this.javaVersion = javaVersion;
  }

  public static JavaFileParser createJavaFileParser(JavacOptions options) {
    String javaVersion = Preconditions.checkNotNull(javaVersionMap.get(options.getSourceLevel()));
    return new JavaFileParser(AST.JLS8, javaVersion);
  }

  public ImmutableSortedSet<String> getExportedSymbolsFromString(String code) {
    final CompilationUnit compilationUnit = makeCompilationUnitFromSource(code);

    final ImmutableSortedSet.Builder<String> symbolsBuilder = ImmutableSortedSet.naturalOrder();

    compilationUnit.accept(new ASTVisitor() {
      @Override
      public boolean visit(TypeDeclaration node) {
        // Local classes can be declared inside of methods. Skip over these.
        if (node.getParent() instanceof TypeDeclarationStatement) {
          return true;
        }

        symbolsBuilder.add(getFullyQualifiedTypeName(node));
        return true;
      }

      @Override
      public boolean visit(EnumDeclaration node) {
        symbolsBuilder.add(getFullyQualifiedTypeName(node));
        return true;
      }

      @Override
      public boolean visit(AnnotationTypeDeclaration node) {
        symbolsBuilder.add(getFullyQualifiedTypeName(node));
        return true;
      }
    });

    return symbolsBuilder.build();
  }

  private CompilationUnit makeCompilationUnitFromSource(String code) {
    ASTParser parser = ASTParser.newParser(jlsLevel);
    parser.setSource(code.toCharArray());
    parser.setKind(ASTParser.K_COMPILATION_UNIT);

    @SuppressWarnings("unchecked")
    Map<String, String> options = JavaCore.getOptions();
    JavaCore.setComplianceOptions(javaVersion, options);
    parser.setCompilerOptions(options);

    return (CompilationUnit) parser.createAST(/* monitor */ null);
  }

  public Optional<String> getPackageNameFromSource(String code) {
    final CompilationUnit compilationUnit = makeCompilationUnitFromSource(code);

    // A Java file might not have a package. Hopefully all of ours do though...
    PackageDeclaration packageDecl = compilationUnit.getPackage();
    if (packageDecl != null) {
      return Optional.of(packageDecl.getName().toString());
    }
    return Optional.absent();
  }

  private String getFullyQualifiedTypeName(AbstractTypeDeclaration node) {
    LinkedList<String> nameParts = Lists.newLinkedList();
    nameParts.add(node.getName().toString());
    ASTNode parent = node.getParent();
    while (!(parent instanceof CompilationUnit)) {
      nameParts.addFirst(((AbstractTypeDeclaration) parent).getName().toString());
      parent = parent.getParent();
    }

    // A Java file might not have a package. Hopefully all of ours do though...
    PackageDeclaration packageDecl = ((CompilationUnit) parent).getPackage();
    if (packageDecl != null) {
      nameParts.addFirst(packageDecl.getName().toString());
    }

    return Joiner.on(".").join(nameParts);
  }
}

@@ -2135,16 +2135,54 @@
 ON_1_7)%0A
+      .put(%228%22, JavaCore.VERSION_1_8)%0A
       .b

Fix the experimental IntelliJ generator for Java 8 sources.