// Copyright 2014 Google Inc. 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.packages;

import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
import com.google.devtools.build.lib.packages.RuleFactory.InvalidRuleException;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Label.SyntaxException;
import com.google.devtools.build.lib.vfs.Path;

import java.io.Serializable;
import java.util.Map;

/**
 * This creates the //external package, where targets not homed in this repository can be bound.
 */
public class ExternalPackage extends Package {
  public static final String NAME = "external";
  public static final PackageIdentifier PACKAGE_IDENTIFIER =
      PackageIdentifier.createInDefaultRepo(NAME);

  private Map<RepositoryName, Rule> repositoryMap;

  ExternalPackage() {
    super(PACKAGE_IDENTIFIER);
  }

  /**
   * Returns a description of the repository with the given name, or null if there's no such
   * repository.
   */
  public Rule getRepositoryInfo(RepositoryName repositoryName) {
    return repositoryMap.get(repositoryName);
  }

  /**
   * Checks if the given package is //external.
   */
  public static boolean isExternal(Package pkg) {
    return pkg != null && pkg.getName().equals(NAME);
  }

  /**
   * Holder for a binding's actual label and location.
   */
  public static class Binding implements Serializable {
    private final Label actual;
    private final Location location;

    public Binding(Label actual, Location location) {
      this.actual = actual;
      this.location = location;
    }

    public Label getActual() {
      return actual;
    }

    public Location getLocation() {
      return location;
    }

    /**
     * Checks if the label is bound, i.e., starts with {@code //external:}.
     */
    public static boolean isBoundLabel(Label label) {
      return label.getPackageName().equals(NAME);
    }
  }

  /**
   * Given a workspace file path, creates an ExternalPackage.
   */
  public static class Builder extends Package.Builder {
    private Map<RepositoryName, Rule> repositoryMap = Maps.newLinkedHashMap();

    public Builder(Path workspacePath) {
      super(new ExternalPackage());
      setFilename(workspacePath);
      setMakeEnv(new MakeEnvironment.Builder());
    }

    protected ExternalPackage externalPackage() {
      return (ExternalPackage) pkg;
    }

    @Override
    public ExternalPackage build() {
      for (Rule rule : repositoryMap.values()) {
        try {
          addRule(rule);
        } catch (NameConflictException e) {
          throw new IllegalStateException("Got a name conflict for " + rule
              + ", which can't happen: " + e.getMessage());
        }
      }
      externalPackage().repositoryMap = ImmutableMap.copyOf(repositoryMap);

      Package base = super.build();
      return (ExternalPackage) base;
    }

    /**
     * Sets the name for this repository.
     */
    @Override
    public Builder setWorkspaceName(String workspaceName) {
      pkg.workspaceName = workspaceName;
      return this;
    }

    private void overwriteRule(Rule rule) throws NameConflictException {
      Preconditions.checkArgument(rule.getOutputFiles().isEmpty());
      Target old = targets.get(rule.getName());
      if (old != null) {
        if (old instanceof Rule) {
          Verify.verify(((Rule) old).getOutputFiles().isEmpty());
        }

        targets.remove(rule.getName());
      }

      addRule(rule);
    }

    public void addBindRule(
        RuleClass bindRuleClass, Label virtual, Label actual, Location location)
        throws InvalidRuleException, NameConflictException {

      Map<String, Object> attributes = Maps.newHashMap();
      // Bound rules don't have a name field, but this works because we don't want more than one
      // with the same virtual name.
      attributes.put("name", virtual.getName());
      if (actual != null) {
        attributes.put("actual", actual);
      }
      StoredEventHandler handler = new StoredEventHandler();
      Rule rule = RuleFactory.createRule(this, bindRuleClass, attributes, handler, null, location);
      overwriteRule(rule);
      rule.setVisibility(ConstantRuleVisibility.PUBLIC);
    }

    /**
     * Adds the rule to the map of rules. Overwrites rules that are already there, to allow "later"
     * WORKSPACE files to overwrite "earlier" ones.
     */
    public Builder createAndAddRepositoryRule(RuleClass ruleClass, RuleClass bindRuleClass,
        Map<String, Object> kwargs, FuncallExpression ast)
        throws InvalidRuleException, NameConflictException, SyntaxException {
      StoredEventHandler eventHandler = new StoredEventHandler();
      Rule tempRule = RuleFactory.createRule(this, ruleClass, kwargs, eventHandler, ast,
          ast.getLocation());
      addEvents(eventHandler.getEvents());
      repositoryMap.put(RepositoryName.create("@" + tempRule.getName()), tempRule);
      for (Map.Entry<String, Label> entry :
        ruleClass.getExternalBindingsFunction().apply(tempRule).entrySet()) {
        Label nameLabel = Label.parseAbsolute("//external:" + entry.getKey());
        addBindRule(bindRuleClass, nameLabel, entry.getValue(), tempRule.getLocation());
      }
      return this;
    }
  }
}

@@ -1331,37 +1331,8 @@
 h;%0A%0A
-import java.io.Serializable;%0A
 impo
@@ -2015,823 +2015,8 @@
  %7D%0A%0A
-  /**%0A   * Checks if the given package is //external.%0A   */%0A  public static boolean isExternal(Package pkg) %7B%0A    return pkg != null && pkg.getName().equals(NAME);%0A  %7D%0A%0A  /**%0A   * Holder for a binding's actual label and location.%0A   */%0A  public static class Binding implements Serializable %7B%0A    private final Label actual;%0A    private final Location location;%0A%0A    public Binding(Label actual, Location location) %7B%0A      this.actual = actual;%0A      this.location = location;%0A    %7D%0A%0A    public Label getActual() %7B%0A      return actual;%0A    %7D%0A%0A    public Location getLocation() %7B%0A      return location;%0A    %7D%0A%0A    /**%0A     * Checks if the label is bound, i.e., starts with %7B@code //external:%7D.%0A     */%0A    public static boolean isBoundLabel(Label label) %7B%0A      return label.getPackageName().equals(NAME);%0A    %7D%0A  %7D%0A%0A
   /*

Remove dead code