/*
 * Copyright (C) 2014 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.squareup.javapoet;

import com.google.common.base.Ascii;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static javax.lang.model.element.NestingKind.MEMBER;
import static javax.lang.model.element.NestingKind.TOP_LEVEL;

/** A fully-qualified class name for top-level and member classes. */
public final class ClassName implements Type, Comparable<ClassName> {
  public static final ClassName OBJECT = ClassName.get(Object.class);

  /** From top to bottom. This will be ["java.util", "Map", "Entry"] for {@link Map.Entry}. */
  final ImmutableList<String> names;
  final String canonicalName;

  private ClassName(List<String> names) {
    for (int i = 1; i < names.size(); i++) {
      checkArgument(SourceVersion.isName(names.get(i)), "part '%s' is keyword", names.get(i));
    }
    this.names = ImmutableList.copyOf(names);
    this.canonicalName = Joiner.on(".").join(names.get(0).isEmpty()
        ? names.subList(1, names.size())
        : names);
  }

  /** Returns the package name, like {@code "java.util"} for {@code Map.Entry}. */
  public String packageName() {
    return names.get(0);
  }

  /**
   * Returns the enclosing class, like {@link Map} for {@code Map.Entry}. Returns null if this class
   * is not nested in another class.
   */
  public ClassName enclosingClassName() {
    if (names.size() == 2) return null;
    return new ClassName(names.subList(0, names.size() - 1));
  }

  ImmutableList<String> simpleNames() {
    return names.subList(1, names.size());
  }

  /** Returns the simple name of this class, like {@code "Entry"} for {@link Map.Entry}. */
  public String simpleName() {
    return Iterables.getLast(names);
  }

  public static ClassName get(Class<?> clazz) {
    checkNotNull(clazz, "clazz == null");
    checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
    checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
    checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName");
    List<String> names = new ArrayList<>();
    for (Class<?> c = clazz; c != null; c = c.getEnclosingClass()) {
      names.add(c.getSimpleName());
    }
    names.add(clazz.getPackage().getName());
    Collections.reverse(names);
    return new ClassName(names);
  }

  /**
   * Returns a new {@link ClassName} instance for the given fully-qualified class name string. This
   * method assumes that the input is ASCII and follows typical Java style (lowercase package
   * names, UpperCamelCase class names) and may produce incorrect results or throw
   * {@link IllegalArgumentException} otherwise. For that reason, {@link #get(Class)} and
   * {@link #get(Class)} should be preferred as they can correctly create {@link ClassName}
   * instances without such restrictions.
   */
  public static ClassName bestGuess(String classNameString) {
    List<String> names = new ArrayList<>();

    // Add the package name, like "java.util.concurrent", or "" for no package.
    int p = 0;
    while (p < classNameString.length() && Ascii.isLowerCase(classNameString.charAt(p))) {
      p = classNameString.indexOf('.', p) + 1;
      checkArgument(p != 0, "couldn't make a guess for %s", classNameString);
    }
    names.add(p != 0 ? classNameString.substring(0, p - 1) : "");

    // Add the class names, like "Map" and "Entry".
    for (String part : Splitter.on('.').split(classNameString.substring(p))) {
      checkArgument(!part.isEmpty() && Ascii.isUpperCase(part.charAt(0)),
          "couldn't make a guess for %s", classNameString);
      names.add(part);
    }

    checkArgument(names.size() >= 2, "couldn't make a guess for %s", classNameString);
    return new ClassName(names);
  }

  /**
   * Returns a class name created from the given parts. For example, calling this with package name
   * {@code "java.util"} and simple names {@code "Map"}, {@code "Entry"} yields {@link Map.Entry}.
   */
  public static ClassName get(String packageName, String simpleName, String... simpleNames) {
    return new ClassName(new ImmutableList.Builder<String>()
        .add(packageName)
        .add(simpleName)
        .add(simpleNames)
        .build());
  }

  private static final ImmutableSet<NestingKind> ACCEPTABLE_NESTING_KINDS =
      Sets.immutableEnumSet(TOP_LEVEL, MEMBER);

  /** Returns the class name for {@code element}. */
  public static ClassName get(TypeElement element) {
    checkNotNull(element, "element == null");
    List<String> names = new ArrayList<>();
    for (Element e = element; isClassOrInterface(e); e = e.getEnclosingElement()) {
      checkArgument(ACCEPTABLE_NESTING_KINDS.contains(element.getNestingKind()));
      names.add(e.getSimpleName().toString());
    }
    names.add(getPackage(element).getQualifiedName().toString());
    Collections.reverse(names);
    return new ClassName(names);
  }

  private static boolean isClassOrInterface(Element e) {
    return e.getKind().isClass() || e.getKind().isInterface();
  }

  private static PackageElement getPackage(Element type) {
    while (type.getKind() != ElementKind.PACKAGE) {
      type = type.getEnclosingElement();
    }
    return (PackageElement) type;
  }

  @Override public boolean equals(Object o) {
    return o instanceof ClassName
        && canonicalName.equals(((ClassName) o).canonicalName);
  }

  @Override public int hashCode() {
    return canonicalName.hashCode();
  }

  @Override public int compareTo(ClassName o) {
    return canonicalName.compareTo(o.canonicalName);
  }

  @Override public String toString() {
    return canonicalName;
  }
}

@@ -2751,16 +2751,23 @@
 %0A  %7D%0A%0A  
+public 
 Immutabl

Make ClassName#simpleNames public.