// 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.workspace;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.bazel.BazelMain;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.WorkspaceFactory;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.BlazeRuntime;
import com.google.devtools.build.lib.runtime.ServerBuilder;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.ParserInputSource;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.workspace.maven.DefaultModelResolver;
import com.google.devtools.build.workspace.maven.Resolver;
import com.google.devtools.build.workspace.maven.Resolver.InvalidArtifactCoordinateException;
import com.google.devtools.build.workspace.maven.Rule;
import java.io.IOException;
import java.util.List;
import org.apache.maven.model.building.ModelSource;
import org.apache.maven.model.resolution.UnresolvableModelException;

/**
 * Finds the transitive dependencies of a WORKSPACE file.
 */
public class WorkspaceResolver {

  private final RuleClassProvider ruleClassProvider;
  private final ImmutableList<EnvironmentExtension> environmentExtensions;
  private final EventHandler handler;
  private final com.google.devtools.build.workspace.maven.Resolver resolver;

  WorkspaceResolver(Resolver resolver, EventHandler handler) {
    this.resolver = resolver;
    this.handler = handler;
    ServerBuilder serverBuilder = new ServerBuilder();
    ConfiguredRuleClassProvider.Builder ruleClassBuilder =
        new ConfiguredRuleClassProvider.Builder();
    List<BlazeModule> blazeModules = BlazeRuntime.createModules(BazelMain.BAZEL_MODULES);
    for (BlazeModule blazeModule : blazeModules) {
      try {
        blazeModule.serverInit(null, serverBuilder);
      } catch (AbruptExitException e) {
        throw new RuntimeException(e);
      }
      blazeModule.initializeRuleClasses(ruleClassBuilder);
    }
    this.ruleClassProvider = ruleClassBuilder.build();
    this.environmentExtensions = serverBuilder.getEnvironmentExtensions();
  }

  /** Converts the WORKSPACE file content into an ExternalPackage. */
  public Package parse(Path workspacePath) throws InterruptedException {
    Package.Builder builder = Package.newExternalPackageBuilder(
        Package.Builder.DefaultHelper.INSTANCE,
        workspacePath,
        ruleClassProvider.getRunfilesPrefix());
    try (Mutability mutability = Mutability.create("External Package %s", workspacePath)) {
      new WorkspaceFactory(builder, ruleClassProvider, environmentExtensions, mutability)
          .parse(ParserInputSource.create(workspacePath));
    } catch (IOException | BuildFileContainsErrorsException | InterruptedException e) {
      handler.handle(Event.error(Location.fromFile(workspacePath), e.getMessage()));
    }

    return builder.build();
  }

  /**
   * Calculates transitive dependencies of the given //external package.
   */
  public void resolveTransitiveDependencies(Package externalPackage) {
    Location location = Location.fromFile(externalPackage.getFilename());
    for (Target target : externalPackage.getTargets()) {
      // Targets are //external:foo.
      if (target.getTargetKind().startsWith("maven_jar ")) {
        RepositoryName repositoryName;
        try {
          repositoryName = RepositoryName.create("@" + target.getName());
        } catch (LabelSyntaxException e) {
          handler.handle(Event.error(location, "Invalid repository name for " + target + ": "
              + e.getMessage()));
          return;
        }
        com.google.devtools.build.lib.packages.Rule workspaceRule =
            externalPackage.getRule(repositoryName.strippedName());

        DefaultModelResolver modelResolver = resolver.getModelResolver();
        AttributeMap attributeMap = AggregatingAttributeMapper.of(workspaceRule);
        Rule rule;
        try {
          rule = new Rule(Resolver.getArtifact(attributeMap.get("artifact", Type.STRING)));
        } catch (InvalidArtifactCoordinateException e) {
          handler.handle(Event.error(location, "Couldn't get attribute: " + e.getMessage()));
          return;
        }
        if (attributeMap.isAttributeValueExplicitlySpecified("repository")) {
          modelResolver.addUserRepository(attributeMap.get("repository", Type.STRING));
          rule.setRepository(attributeMap.get("repository", Type.STRING), handler);
        }
        if (attributeMap.isAttributeValueExplicitlySpecified("sha1")) {
          rule.setSha1(attributeMap.get("sha1", Type.STRING));
        } else {
          rule.setSha1(resolver.downloadSha1(rule));
        }

        ModelSource modelSource;
        try {
          modelSource = modelResolver.resolveModel(
              rule.groupId(), rule.artifactId(), rule.version());
        } catch (UnresolvableModelException e) {
          handler.handle(Event.error(
              "Could not resolve model for " + target + ": " + e.getMessage()));
          continue;
        }
        resolver.addArtifact(rule, modelSource.getLocation());
        resolver.resolveEffectiveModel(modelSource, Sets.<String>newHashSet(), rule);
      } else if (!target.getTargetKind().startsWith("bind")
          && !target.getTargetKind().startsWith("source ")) {
        handler.handle(Event.warn(location, "Cannot fetch transitive dependencies for " + target
            + " yet, skipping"));
      }
    }
  }
}
