/*
 * Copyright 2016-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.apple;

import com.facebook.buck.cxx.CxxCompilationDatabase;
import com.facebook.buck.cxx.CxxInferEnhancer;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.FlavorDomain;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePaths;
import com.facebook.buck.util.HumanReadableException;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;

import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;

public class MultiarchFileInfos {

  // Utility class, do not instantiate.
  private MultiarchFileInfos() { }

  /**
   * Inspect the given build target and return information about it if its a fat binary.
   *
   * @return non-empty when the target represents a fat binary.
   * @throws com.facebook.buck.util.HumanReadableException
   *    when the target is a fat binary but has incompatible flavors.
   */
  public static Optional<MultiarchFileInfo> create(
      final FlavorDomain<AppleCxxPlatform> appleCxxPlatforms,
      BuildTarget target) {
    ImmutableList<ImmutableSortedSet<Flavor>> thinFlavorSets =
        generateThinFlavors(appleCxxPlatforms.getFlavors(), target.getFlavors());
    if (thinFlavorSets.size() <= 1) {  // Actually a thin binary
      return Optional.empty();
    }

    if (!Sets.intersection(target.getFlavors(), FORBIDDEN_BUILD_ACTIONS).isEmpty()) {
      throw new HumanReadableException(
          "%s: Fat binaries is only supported when building an actual binary.",
          target);
    }

    AppleCxxPlatform representativePlatform = null;
    AppleSdk sdk = null;
    for (SortedSet<Flavor> flavorSet : thinFlavorSets) {
      AppleCxxPlatform platform = Preconditions.checkNotNull(
          appleCxxPlatforms.getValue(flavorSet).orElse(null));
      if (sdk == null) {
        sdk = platform.getAppleSdk();
        representativePlatform = platform;
      } else if (sdk != platform.getAppleSdk()) {
        throw new HumanReadableException(
            "%s: Fat binaries can only be generated from binaries compiled for the same SDK.",
            target);
      }
    }

    MultiarchFileInfo.Builder builder =
        MultiarchFileInfo.builder()
            .setFatTarget(target)
            .setRepresentativePlatform(Preconditions.checkNotNull(representativePlatform));

    BuildTarget platformFreeTarget = target.withoutFlavors(appleCxxPlatforms.getFlavors());
    for (SortedSet<Flavor> flavorSet : thinFlavorSets) {
      builder.addThinTargets(platformFreeTarget.withFlavors(flavorSet));
    }

    return Optional.of(builder.build());
  }

  /**
   * Expand flavors representing a fat binary into its thin binary equivalents.
   *
   * Useful when dealing with functions unaware of fat binaries.
   *
   * This does not actually check that the particular flavor set is valid.
   */
  public static ImmutableList<ImmutableSortedSet<Flavor>> generateThinFlavors(
      Set<Flavor> platformFlavors,
      SortedSet<Flavor> flavors) {
    Set<Flavor> platformFreeFlavors = Sets.difference(flavors, platformFlavors);
    ImmutableList.Builder<ImmutableSortedSet<Flavor>> thinTargetsBuilder = ImmutableList.builder();
    for (Flavor flavor : flavors) {
      if (platformFlavors.contains(flavor)) {
        thinTargetsBuilder.add(
            ImmutableSortedSet.<Flavor>naturalOrder()
                .addAll(platformFreeFlavors)
                .add(flavor)
                .build());
      }
    }
    return thinTargetsBuilder.build();
  }

  /**
   * Generate a fat rule from thin rules.
   *
   * Invariant: thinRules contain all the thin rules listed in info.getThinTargets().
   */
  public static BuildRule requireMultiarchRule(
      BuildRuleParams params,
      BuildRuleResolver resolver,
      MultiarchFileInfo info,
      ImmutableSortedSet<BuildRule> thinRules) {
    Optional<BuildRule> existingRule = resolver.getRuleOptional(info.getFatTarget());
    if (existingRule.isPresent()) {
      return existingRule.get();
    }

    ImmutableSortedSet<SourcePath> inputs = FluentIterable
        .from(thinRules)
        .transform(SourcePaths.getToBuildTargetSourcePath())
        .toSortedSet(Ordering.natural());
    SourcePathResolver pathResolver = new SourcePathResolver(resolver);
    MultiarchFile multiarchFile = new MultiarchFile(
        params.copyWithDeps(
            Suppliers.ofInstance(ImmutableSortedSet.of()),
            Suppliers.ofInstance(thinRules)),
        pathResolver,
        info.getRepresentativePlatform().getLipo(),
        inputs,
        BuildTargets.getGenPath(params.getProjectFilesystem(), params.getBuildTarget(), "%s"));
    resolver.addToIndex(multiarchFile);
    return multiarchFile;
  }

  private static final ImmutableSet<Flavor> FORBIDDEN_BUILD_ACTIONS =
      ImmutableSet.<Flavor>builder()
          .addAll(CxxInferEnhancer.InferFlavors.getAll())
          .add(CxxCompilationDatabase.COMPILATION_DATABASE).build();

}
