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

import com.dd.plist.NSArray;
import com.dd.plist.NSObject;
import com.facebook.buck.model.Pair;
import com.facebook.buck.rules.RuleKeyAppendable;
import com.facebook.buck.rules.RuleKeyObjectSink;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashCode;
import java.nio.file.Path;
import java.util.Date;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.immutables.value.Value;

/** Metadata contained in a provisioning profile (.mobileprovision). */
@Value.Immutable
@BuckStyleImmutable
abstract class AbstractProvisioningProfileMetadata implements RuleKeyAppendable {
  private static final Pattern BUNDLE_ID_PATTERN = Pattern.compile("^([A-Z0-9]{10,10})\\.(.+)$");

  /**
   * Returns a (prefix, identifier) pair for which the profile is valid.
   *
   * <p>e.g. (ABCDE12345, com.example.TestApp) or (ABCDE12345, *)
   */
  public abstract Pair<String, String> getAppID();

  public abstract Date getExpirationDate();

  public abstract String getUUID();

  public abstract Path getProfilePath();

  /** The set of platforms the profile is valid for. */
  @Value.Default
  public ImmutableList<String> getPlatforms() {
    return ImmutableList.of(ApplePlatform.IPHONEOS.getProvisioningProfileName().get());
  }

  /** Key/value pairs of the "Entitlements" dictionary in the embedded plist. */
  public abstract ImmutableMap<String, NSObject> getEntitlements();

  /** SHA1 hashes of the certificates in the "DeveloperCertificates" section. */
  public abstract ImmutableSet<HashCode> getDeveloperCertificateFingerprints();

  /**
   * Takes a application identifier and splits it into prefix and bundle ID.
   *
   * <p>Prefix is always a ten-character alphanumeric sequence. Bundle ID may be a fully-qualified
   * name or a wildcard ending in *.
   */
  public static Pair<String, String> splitAppID(String appID) {
    Matcher matcher = BUNDLE_ID_PATTERN.matcher(appID);
    if (matcher.find()) {
      String prefix = matcher.group(1);
      String bundleID = matcher.group(2);

      return new Pair<>(prefix, bundleID);
    } else {
      throw new IllegalArgumentException("Malformed app ID: " + appID);
    }
  }

  /**
   * Takes an ImmutableMap representing an entitlements file, returns the application prefix if it
   * can be inferred from keys in the entitlement. Otherwise, it returns empty.
   */
  public static Optional<String> prefixFromEntitlements(
      ImmutableMap<String, NSObject> entitlements) {
    try {
      NSArray keychainAccessGroups = ((NSArray) entitlements.get("keychain-access-groups"));
      Preconditions.checkNotNull(keychainAccessGroups);
      String appID = keychainAccessGroups.objectAtIndex(0).toString();
      return Optional.of(splitAppID(appID).getFirst());
    } catch (RuntimeException e) {
      return Optional.empty();
    }
  }

  @Override
  public void appendToRuleKey(RuleKeyObjectSink sink) {
    sink.setReflectively("provisioning-profile-uuid", getUUID());
  }

  public ImmutableMap<String, NSObject> getMergeableEntitlements() {
    final ImmutableSet<String> excludedKeys =
        ImmutableSet.of(
            "com.apple.developer.restricted-resource-mode",
            "inter-app-audio",
            "com.apple.developer.icloud-container-development-container-identifiers",
            "com.apple.developer.homekit",
            "com.apple.developer.healthkit",
            "com.apple.developer.in-app-payments",
            "com.apple.developer.maps",
            "com.apple.external-accessory.wireless-configuration");

    ImmutableMap<String, NSObject> allEntitlements = getEntitlements();
    ImmutableMap.Builder<String, NSObject> filteredEntitlementsBuilder = ImmutableMap.builder();
    for (String key : allEntitlements.keySet()) {
      if (!excludedKeys.contains(key)) {
        filteredEntitlementsBuilder.put(key, allEntitlements.get(key));
      }
    }
    return filteredEntitlementsBuilder.build();
  }
}

@@ -771,33 +771,28 @@
 k.rules.
+AddTo
 RuleKey
-Appendable
 ;%0Aimport
@@ -816,33 +816,29 @@
 k.rules.
+AddsTo
 RuleKey
-ObjectSink
 ;%0Aimport
@@ -1471,25 +1471,21 @@
 nts 
+AddsTo
 RuleKey
-Appendable
  %7B%0A 
@@ -1831,24 +1831,40 @@
 ionDate();%0A%0A
+  @AddToRuleKey%0A
   public abs
@@ -3722,147 +3722,8 @@
  %7D%0A%0A
-  @Override%0A  public void appendToRuleKey(RuleKeyObjectSink sink) %7B%0A    sink.setReflectively(%22provisioning-profile-uuid%22, getUUID());%0A  %7D%0A%0A
   pu

Update AbstractProvisioningProfileMetadata to use AddsToRuleKey instead of RuleKeyAppendable