/*
 * Copyright 2015-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.jvm.java;

import com.facebook.buck.model.BuildTarget;
import java.util.Set;
import javax.annotation.processing.Completion;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;

/** Wraps an annotation processor, tracing all method calls to BuckEventBus. */
class TracingProcessorWrapper implements Processor {
  private final JavacEventSink eventSink;
  private final Processor innerProcessor;
  private final BuildTarget buildTarget;
  private final String annotationProcessorName;

  private int roundNumber = 0;
  private boolean isLastRound = false;

  public TracingProcessorWrapper(
      JavacEventSink eventSink, BuildTarget buildTarget, Processor processor) {
    this.eventSink = eventSink;
    this.buildTarget = buildTarget;
    innerProcessor = processor;
    annotationProcessorName = innerProcessor.getClass().getName();
  }

  @Override
  public Set<String> getSupportedOptions() {
    AnnotationProcessingEvent.Started started =
        begin(AnnotationProcessingEvent.Operation.GET_SUPPORTED_OPTIONS);
    try {
      return innerProcessor.getSupportedOptions();
    } finally {
      end(started);
    }
  }

  @Override
  public Set<String> getSupportedAnnotationTypes() {
    AnnotationProcessingEvent.Started started =
        begin(AnnotationProcessingEvent.Operation.GET_SUPPORTED_ANNOTATION_TYPES);
    try {
      return innerProcessor.getSupportedAnnotationTypes();
    } finally {
      end(started);
    }
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    AnnotationProcessingEvent.Started started =
        begin(AnnotationProcessingEvent.Operation.GET_SUPPORTED_SOURCE_VERSION);
    try {
      return innerProcessor.getSupportedSourceVersion();
    } finally {
      end(started);
    }
  }

  @Override
  public void init(ProcessingEnvironment processingEnv) {
    AnnotationProcessingEvent.Started started = begin(AnnotationProcessingEvent.Operation.INIT);
    try {
      innerProcessor.init(processingEnv);
    } finally {
      end(started);
    }
  }

  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    roundNumber += 1;
    isLastRound = roundEnv.processingOver();
    AnnotationProcessingEvent.Started started = begin(AnnotationProcessingEvent.Operation.PROCESS);
    try {
      return innerProcessor.process(annotations, roundEnv);
    } finally {
      end(started);
    }
  }

  @Override
  public Iterable<? extends Completion> getCompletions(
      Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
    AnnotationProcessingEvent.Started started =
        begin(AnnotationProcessingEvent.Operation.GET_COMPLETIONS);
    try {
      return innerProcessor.getCompletions(element, annotation, member, userText);
    } finally {
      end(started);
    }
  }

  private AnnotationProcessingEvent.Started begin(AnnotationProcessingEvent.Operation operation) {
    AnnotationProcessingEvent.Started started =
        AnnotationProcessingEvent.started(
            buildTarget, annotationProcessorName, operation, roundNumber, isLastRound);
    eventSink.reportAnnotationProcessingEventStarted(
        buildTarget, annotationProcessorName, operation.toString(), roundNumber, isLastRound);
    return started;
  }

  private void end(AnnotationProcessingEvent.Started started) {
    eventSink.reportAnnotationProcessingEventFinished(
        started.getBuildTarget(),
        started.getAnnotationProcessorName(),
        started.getOperation().toString(),
        started.getRound(),
        started.isLastRound());
  }
}
