// Copyright 2014 The Bazel Authors. 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 com.google.devtools.build.lib.query2.query;

import com.google.devtools.build.lib.bugreport.BugReport;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.TargetEdgeObserver;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.util.DetailedExitCode;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;

/**
 * Record errors, such as missing package/target or rules containing errors, encountered during
 * visitation. Emit an error message upon encountering missing edges
 *
 * <p>The accessor {@link #hasErrors}) may not be called until the concurrent phase is over, i.e.
 * all external calls to visit() methods have completed.
 *
 * <p>If you need to report errors to the console during visitation, use the subclass {@link
 * ErrorPrintingTargetEdgeErrorObserver}.
 */
class TargetEdgeErrorObserver implements TargetEdgeObserver {

  /**
   * True iff errors were encountered. Note, may be set to "true" during the concurrent phase.
   * Volatile, because it is assigned by worker threads and read by the main thread without monitor
   * synchronization.
   */
  private volatile boolean hasErrors = false;

  private final AtomicReference<DetailedExitCode> errorCode = new AtomicReference<>();

  /**
   * Reports an unresolved label error and records the fact that an error was encountered.
   *
   * @param target the target that referenced the unresolved label
   * @param label the label that could not be resolved
   * @param e the exception that was thrown when the label could not be resolved
   */
  @ThreadSafety.ThreadSafe
  @Override
  public void missingEdge(Target target, Label label, NoSuchThingException e) {
    hasErrors = true;
    errorCode.compareAndSet(/*expectedValue=*/ null, /*newValue=*/ e.getDetailedExitCode());
  }

  /**
   * Returns true iff any errors (such as missing targets or packages, or rules with errors) have
   * been encountered during any work.
   *
   * <p>Not thread-safe; do not call during visitation.
   *
   * @return true iff no errors (such as missing targets or packages, or rules with errors) have
   *     been encountered during any work.
   */
  public boolean hasErrors() {
    return hasErrors;
  }

  /** Returns the first {@link DetailedExitCode} encountered, or {@code null} if there were none. */
  @Nullable
  public DetailedExitCode getDetailedExitCode() {
    return errorCode.get();
  }

  @Override
  public void edge(Target from, Attribute attribute, Target to) {
    // No-op.
  }

  @Override
  public void node(Target node) {
    if (node.getPackage().containsErrors()
        || ((node instanceof Rule) && ((Rule) node).containsErrors())) {
      this.hasErrors = true;
      FailureDetail failureDetail = node.getPackage().getFailureDetail();
      if (failureDetail != null) {
        errorCode.compareAndSet(
            /*expectedValue=*/ null, /*newValue=*/ DetailedExitCode.of(failureDetail));
      } else {
        BugReport.sendNonFatalBugReport(
            new IllegalStateException("Undetailed error from package: " + node));
      }
    }
  }
}
