// Copyright 2015 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.engine;

import com.google.devtools.build.lib.query2.engine.QueryEnvironment.TargetAccessor;

/**
 * A predicate for targets included in the visibility list of a rule.
 *
 * <p>A rule's visibility is described by a set of {@link QueryVisibility}. Each element
 * in the set corresponds to an entry in the rule's visibility attribute, or an entry in the
 * packages attribute of an included package_group.
 */
public abstract class QueryVisibility<T> {

  /** Returns true if the visibility specification includes the given target's package. */
  public abstract boolean contains(T target);

  /** Global visibility. */
  @SuppressWarnings("unchecked")  // Safe covariant cast.
  public static <T> QueryVisibility<T> everything() {
    return (QueryVisibility<T>) (Object) EVERYTHING;
  }

  private static final QueryVisibility<?> EVERYTHING = new QueryVisibility<Object>() {
    @Override
    public boolean contains(Object target) {
      return true;
    }

    @Override
    public String toString() {
      return "QueryVisibility(//visibility:public)";
    }
  };

  /**
   * Empty visibility.
   *
   * <p>This is useful as a sentinel to indicate that a package specification could be parsed into
   * a {@link QueryVisibility}. Same-package visibility is handled separately by
   * {@link VisibleFunction}.
   */
  @SuppressWarnings("unchecked")  // Safe covariant cast.
  public static <T> QueryVisibility<T> nothing() {
    return (QueryVisibility<T>) (Object) NOTHING;
  }

  private static final QueryVisibility<?> NOTHING = new QueryVisibility<Object>() {
    @Override
    public boolean contains(Object target) {
      return false;
    }

    @Override
    public String toString() {
      return "QueryVisibility(//visibility:private)";
    }
  };

  /**
   * Same-package visibility.
   *
   * <p>Targets are always visible to other targets in the same package. Additionally, targets
   * under java/ are always visible to the corresponding package in javatests/. 
   */
  public static <T> QueryVisibility<T> samePackage(T from, TargetAccessor<T> accessor) {
    return new SamePackageVisibility<>(from, accessor);
  }

  private static final class SamePackageVisibility<T> extends QueryVisibility<T> {

    private static final String JAVA_PREFIX = "java/";
    private static final String JAVATESTS_PREFIX = "javatests/";

    private final String packageName;
    private final TargetAccessor<T> accessor;

    public SamePackageVisibility(T target, TargetAccessor<T> accessor) {
      this.packageName = accessor.getPackage(target);
      this.accessor = accessor;
    }

    @Override
    public boolean contains(T target) {
      String other = accessor.getPackage(target);
      if (packageName.equals(other)) {
        return true;
      }

      // packages in java/ are always visible from the corresponding package in javatests/
      if (other.startsWith(JAVATESTS_PREFIX)
          && packageName.equals(JAVA_PREFIX + other.substring(JAVATESTS_PREFIX.length()))) {
        return true;
      }

      return false;
    }

    @Override
    public String toString() {
      return String.format("QueryVisibility(samePackage=%s)", "<PACKAGE>");
    }
  }
}
