/*
 * Copyright 2015 Google Inc. All rights reserved.
 *
 * 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 org.inferred.freebuilder.processor.util;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.getLast;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.TypeMirror;

/**
 * The qualified name of a type. Lets us pass a type to a {@link TypeShortener} without a Class or
 * javax.lang.model reference.
 */
public class QualifiedName extends ValueType {

  /**
   * Returns a {@link QualifiedName} for a type in {@code packageName}. If {@code nestedTypes} is
   * empty, it is a top level type called {@code topLevelType}; otherwise, it is nested in that
   * type.
   */
  public static QualifiedName of(String packageName, String topLevelType, String... nestedTypes) {
    Preconditions.checkNotNull(!packageName.isEmpty());
    Preconditions.checkArgument(!topLevelType.isEmpty());
    return new QualifiedName(
        packageName, ImmutableList.<String>builder().add(topLevelType).add(nestedTypes).build());
  }

  /**
   * Returns a {@link QualifiedName} for {@code cls}.
   */
  public static QualifiedName of(Class<?> cls) {
    if (cls.getEnclosingClass() != null) {
      return QualifiedName.of(cls.getEnclosingClass()).nestedType(cls.getSimpleName());
    } else if (cls.getPackage() != null) {
      return QualifiedName.of(cls.getPackage().getName(), cls.getSimpleName());
    } else {
      return QualifiedName.of("", cls.getSimpleName());
    }
  }

  /**
   * Returns a {@link QualifiedName} for {@code type}.
   */
  public static QualifiedName of(TypeElement type) {
    if (type.getNestingKind().isNested()) {
      QualifiedName enclosingElement = QualifiedName.of((TypeElement) type.getEnclosingElement());
      return enclosingElement.nestedType(type.getSimpleName().toString());
    } else {
      PackageElement pkg = (PackageElement) type.getEnclosingElement();
      return QualifiedName.of(pkg.getQualifiedName().toString(), type.getSimpleName().toString());
    }
  }

  private final String packageName;
  private final ImmutableList<String> simpleNames;

  private QualifiedName(String packageName, Iterable<String> simpleNames) {
    this.packageName = packageName;
    this.simpleNames = ImmutableList.copyOf(simpleNames);
  }

  /**
   * Returns this qualified name as a string.
   *
   * <p>Returns the same as {@link Class#getName()} and {@link TypeElement#getQualifiedName()}
   * would for the same type, e.g. "java.lang.Integer" or "com.example.OuterType.InnerType".
   */
  @Override
  public String toString() {
    return packageName + "." + Joiner.on('.').join(simpleNames);
  }

  public String getPackage() {
    return packageName;
  }

  public ImmutableList<String> getSimpleNames() {
    return simpleNames;
  }

  public String getSimpleName() {
    return getLast(simpleNames);
  }

  public boolean isTopLevel() {
    return simpleNames.size() == 1;
  }

  /**
   * Returns the {@link QualifiedName} of a type called {@code simpleName} nested in this one.
   */
  public QualifiedName nestedType(String simpleName) {
    return new QualifiedName(packageName, concat(simpleNames, ImmutableList.of(simpleName)));
  }

  public ParameterizedType withParameters(String... typeParameters) {
    return new ParameterizedType(this, ImmutableList.copyOf(typeParameters));
  }

  public ParameterizedType withParameters(TypeMirror first, TypeMirror... rest) {
    return new ParameterizedType(this, ImmutableList.builder().add(first).add(rest).build());
  }

  public ParameterizedType withParameters(Iterable<? extends TypeParameterElement> typeParameters) {
    return new ParameterizedType(this, ImmutableList.copyOf(typeParameters));
  }

  public ParameterizedType withParameters(
      TypeParameterElement typeParameter, TypeParameterElement... typeParameters) {
    return withParameters(ImmutableList.<TypeParameterElement>builder()
        .add(typeParameter)
        .add(typeParameters)
        .build());
  }

  /**
   * Returns the {@link QualifiedName} of the type enclosing this one.
   *
   * @throws IllegalStateException if {@link #isTopLevel()} returns true
   */
  public QualifiedName getEnclosingType() {
    checkState(!isTopLevel(), "%s has no enclosing type", this);
    return new QualifiedName(packageName, simpleNames.subList(0, simpleNames.size() - 1));
  }

  @Override
  protected void addFields(FieldReceiver fields) {
    fields.add("packageName", packageName);
    fields.add("simpleNames", simpleNames);
  }
}


@@ -4104,32 +4104,8 @@
 ers(
-String... typeParameters
 ) %7B%0A
@@ -4157,37 +4157,19 @@
 bleList.
-copyOf(typeParameters
+of(
 ));%0A  %7D%0A
@@ -4215,36 +4215,28 @@
 ers(
-TypeMirror first, TypeMirror
+String first, String
 ... 
@@ -4268,32 +4268,41 @@
 rameterizedType(
+%0A        
 this, ImmutableL
@@ -4305,16 +4305,24 @@
 bleList.
+%3CString%3E
 builder(
@@ -4405,63 +4405,44 @@
 ers(
-Iterable%3C? extends TypeParameterElement%3E typeParameters
+TypeMirror first, TypeMirror... rest
 ) %7B%0A
@@ -4466,32 +4466,41 @@
 rameterizedType(
+%0A        
 this, ImmutableL
@@ -4507,29 +4507,57 @@
 ist.
-copyOf(typeParameters
+%3CTypeMirror%3Ebuilder().add(first).add(rest).build(
 ));%0A
@@ -4635,22 +4635,20 @@
 ent 
-typeParameter,
+first,%0A     
  Typ
@@ -4668,30 +4668,20 @@
 ment... 
-typeParameters
+rest
 ) %7B%0A    
@@ -4687,20 +4687,20 @@
  return 
-with
+new 
 Paramete
@@ -4700,18 +4700,40 @@
 arameter
-s(
+izedType(%0A        this, 
 Immutabl
@@ -4773,50 +4773,128 @@
 er()
-%0A        .add(typeParameter)%0A        .add(
+.add(first).add(rest).build());%0A  %7D%0A%0A  public ParameterizedType withParameters(Iterable%3C? extends TypeParameterElement%3E 
 type
@@ -4908,24 +4908,85 @@
 ers)
+ %7B
 %0A    
-    .build(
+return new ParameterizedType(this, ImmutableList.copyOf(typeParameters
 ));%0A

Make withParameters methods consistent