/*
 * 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.auto.common.BasicAnnotationProcessor.ProcessingStep;
import com.google.auto.common.MoreElements;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import java.lang.annotation.Annotation;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

/**
 * A {@link ProcessingStep} that is responsible for dealing with a component or production component
 * as part of the {@link ComponentProcessor}.
 */
abstract class AbstractComponentProcessingStep implements ProcessingStep {

  private final Class<? extends Annotation> componentAnnotation;
  private final Messager messager;
  private final ComponentHierarchyValidator componentHierarchyValidator;
  private final BindingGraphValidator bindingGraphValidator;
  private final ComponentDescriptor.Factory componentDescriptorFactory;
  private final BindingGraph.Factory bindingGraphFactory;
  private final ComponentGenerator componentGenerator;

  AbstractComponentProcessingStep(
      Class<? extends Annotation> componentAnnotation,
      Messager messager,
      ComponentHierarchyValidator componentHierarchyValidator,
      BindingGraphValidator bindingGraphValidator,
      ComponentDescriptor.Factory componentDescriptorFactory,
      BindingGraph.Factory bindingGraphFactory,
      ComponentGenerator componentGenerator) {
    this.componentAnnotation = componentAnnotation;
    this.messager = messager;
    this.componentHierarchyValidator = componentHierarchyValidator;
    this.bindingGraphValidator = bindingGraphValidator;
    this.componentDescriptorFactory = componentDescriptorFactory;
    this.bindingGraphFactory = bindingGraphFactory;
    this.componentGenerator = componentGenerator;
  }

  @Override
  public final ImmutableSet<Element> process(
      SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
    ImmutableSet.Builder<Element> rejectedElements = ImmutableSet.builder();
    ComponentElementValidator componentElementValidator =
        componentElementValidator(elementsByAnnotation);
    for (Element element : elementsByAnnotation.get(componentAnnotation)) {
      TypeElement componentTypeElement = MoreElements.asType(element);
      try {
        if (componentElementValidator.validateComponent(componentTypeElement, messager)) {
          ComponentDescriptor componentDescriptor =
              componentDescriptorFactory.forComponent(componentTypeElement);
          ValidationReport<TypeElement> hierarchyReport =
              componentHierarchyValidator.validate(componentDescriptor);
          hierarchyReport.printMessagesTo(messager);
          if (hierarchyReport.isClean()) {
            BindingGraph bindingGraph = bindingGraphFactory.create(componentDescriptor);
            ValidationReport<TypeElement> graphReport =
                bindingGraphValidator.validate(bindingGraph);
            graphReport.printMessagesTo(messager);
            if (graphReport.isClean()) {
              generateComponent(bindingGraph);
            }
          }
        }
      } catch (TypeNotPresentException e) {
        rejectedElements.add(componentTypeElement);
      }
    }
    return rejectedElements.build();
  }

  private void generateComponent(BindingGraph bindingGraph) {
    try {
      componentGenerator.generate(bindingGraph);
    } catch (SourceFileGenerationException e) {
      e.printMessageTo(messager);
    }
  }

  /**
   * Returns an object that can validate a type element annotated with the component type.
   */
  protected abstract ComponentElementValidator componentElementValidator(
      SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation);

  /**
   * Validates a component type element.
   */
  protected static abstract class ComponentElementValidator {
    /**
     * Validates a component type element. Prints any messages about the element to
     * {@code messager}.
     *
     * @throws TypeNotPresentException if any type required to validate the component cannot be
     *     found
     */
    abstract boolean validateComponent(TypeElement componentTypeElement, Messager messager);
  }
}