/*
 * Copyright (C) 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 dagger.internal.codegen;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import dagger.internal.codegen.writer.ClassName;
import dagger.internal.codegen.writer.ClassWriter;
import dagger.internal.codegen.writer.JavaWriter;
import dagger.internal.codegen.writer.MethodWriter;
import javax.annotation.Generated;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;

import static dagger.internal.codegen.Util.componentCanMakeNewInstances;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;

/**
 * Creates the implementation class for a component.
 */
class ComponentWriter extends AbstractComponentWriter {

  ComponentWriter(
      Types types,
      Elements elements,
      Key.Factory keyFactory,
      Kind nullableValidationType,
      ClassName name,
      BindingGraph graph) {
    super(types, elements, keyFactory, nullableValidationType, name, graph);
  }

  @Override
  protected ClassWriter createComponentClass() {
    JavaWriter javaWriter = JavaWriter.inPackage(name.packageName());
    javaWriters.add(javaWriter);

    ClassWriter componentWriter = javaWriter.addClass(name.simpleName());
    componentWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getCanonicalName());
    componentWriter.addModifiers(PUBLIC, FINAL);
    componentWriter.setSupertype(componentDefinitionType());
    return componentWriter;
  }

  @Override
  protected ClassWriter createBuilder() {
    ClassWriter builderWriter = componentWriter.addNestedClass("Builder");
    builderWriter.addModifiers(STATIC);

    // Only top-level components have the factory builder() method.
    // Mirror the user's builder API type if they had one.
    MethodWriter builderFactoryMethod =
        graph.componentDescriptor().builderSpec().isPresent()
            ? componentWriter.addMethod(
                graph
                    .componentDescriptor()
                    .builderSpec()
                    .get()
                    .builderDefinitionType()
                    .asType(),
                "builder")
            : componentWriter.addMethod(builderWriter, "builder");
    builderFactoryMethod.addModifiers(PUBLIC, STATIC);
    builderFactoryMethod.body().addSnippet("return new %s();", builderWriter.name());
    return builderWriter;
  }

  @Override
  protected void addFactoryMethods() {
    if (canInstantiateAllRequirements()) {
      MethodWriter factoryMethod =
          componentWriter.addMethod(componentDefinitionTypeName(), "create");
      factoryMethod.addModifiers(PUBLIC, STATIC);
      // TODO(gak): replace this with something that doesn't allocate a builder
      factoryMethod
          .body()
          .addSnippet(
              "return builder().%s();",
              graph.componentDescriptor().builderSpec().isPresent()
                  ? graph
                      .componentDescriptor()
                      .builderSpec()
                      .get()
                      .buildMethod()
                      .getSimpleName()
                  : "build");
    }
  }

  /** {@code true} if all of the graph's required dependencies can be automatically constructed. */
  private boolean canInstantiateAllRequirements() {
    return Iterables.all(
        graph.componentRequirements(),
        new Predicate<TypeElement>() {
          @Override
          public boolean apply(TypeElement dependency) {
            return componentCanMakeNewInstances(dependency);
          }
        });
  }
}