/*
 * 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.artifact_cache;

import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.io.BorrowablePath;
import com.facebook.buck.io.LazyPath;
import com.facebook.buck.log.Logger;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.slb.NoHealthyServersException;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;

public class RetryingCacheDecorator implements ArtifactCache, CacheDecorator {

  private static final Logger LOG = Logger.get(RetryingCacheDecorator.class);

  private final ArtifactCache delegate;
  private final int maxFetchRetries;
  private final BuckEventBus buckEventBus;
  private final ArtifactCacheMode cacheMode;

  public RetryingCacheDecorator(
      ArtifactCacheMode cacheMode,
      ArtifactCache delegate,
      int maxFetchRetries,
      BuckEventBus buckEventBus) {
    Preconditions.checkArgument(maxFetchRetries > 0);

    this.cacheMode = cacheMode;
    this.delegate = delegate;
    this.maxFetchRetries = maxFetchRetries;
    this.buckEventBus = buckEventBus;
  }

  @Override
  public ListenableFuture<CacheResult> fetchAsync(RuleKey ruleKey, LazyPath output) {
    return Futures.immediateFuture(fetch(ruleKey, output));
  }

  private CacheResult fetch(RuleKey ruleKey, LazyPath output) {
    // TODO(cjhopman): This decorator de-asyncifies the delegate.
    List<String> allCacheErrors = new ArrayList<>();
    CacheResult lastCacheResult = null;
    for (int retryCount = 0; retryCount < maxFetchRetries; retryCount++) {
      CacheResult cacheResult = Futures.getUnchecked(delegate.fetchAsync(ruleKey, output));
      if (cacheResult.getType() != CacheResultType.ERROR) {
        return cacheResult;
      }
      cacheResult.cacheError().ifPresent(allCacheErrors::add);
      LOG.debug(
          "Failed to fetch %s after %d/%d attempts, exception: %s",
          ruleKey, retryCount + 1, maxFetchRetries, cacheResult.cacheError());
      lastCacheResult = cacheResult;
    }
    Preconditions.checkNotNull(
        lastCacheResult,
        "One error should have happened, therefore lastCacheResult should be non null.");
    String msg = String.join("\n", allCacheErrors);
    if (!msg.contains(NoHealthyServersException.class.getName())) {
      buckEventBus.post(
          ConsoleEvent.warning(
              "Failed to fetch %s over %s after %d attempts.",
              ruleKey, cacheMode.name(), maxFetchRetries));
    }
    return CacheResult.builder().from(lastCacheResult).setCacheError(msg).build();
  }

  @Override
  public ArtifactCache getDelegate() {
    return delegate;
  }

  @Override
  public ListenableFuture<Void> store(ArtifactInfo info, BorrowablePath output) {
    return delegate.store(info, output);
  }

  @Override
  public CacheReadMode getCacheReadMode() {
    return delegate.getCacheReadMode();
  }

  @Override
  public void close() {
    delegate.close();
  }
}

@@ -1949,290 +1949,142 @@
     
-return Futures.immediateFuture(fetch(ruleKey, output));%0A  %7D%0A%0A  private CacheResult fetch(RuleKey ruleKey, LazyPath output) %7B%0A    // TODO(cjhopman): This decorator de-asyncifies the delegate.%0A    List%3CString%3E allCacheErrors = new ArrayList%3C%3E();%0A    CacheResult lastCacheResult = null
+List%3CString%3E allCacheErrors = new ArrayList%3C%3E();%0A    ListenableFuture%3CCacheResult%3E resultFuture = delegate.fetchAsync(ruleKey, output)
 ;%0A  
@@ -2107,17 +2107,17 @@
 Count = 
-0
+1
 ; retryC
@@ -2166,110 +2166,176 @@
     
-CacheResult cacheResult = Futures.getUnchecked(delegate.fetchAsync(ruleKey, output));%0A      if (cacheR
+int retryCountForLambda = retryCount;%0A      resultFuture =%0A          Futures.transformAsync(%0A              resultFuture,%0A              result -%3E %7B%0A                if (r
 esul
@@ -2386,28 +2386,68 @@
     
+      
+    
 return 
-cacheR
+Futures.immediateFuture(r
 esult
+)
 ;%0A
+          
     
@@ -2456,22 +2456,27 @@
 %7D%0A      
-cacheR
+          r
 esult.ca
@@ -2528,19 +2528,34 @@
     
-LOG.debug(%0A
+          LOG.info(%0A      
     
@@ -2552,32 +2552,36 @@
                 
+    
 %22Failed to fetch
@@ -2622,16 +2622,26 @@
 n: %25s%22,%0A
+          
         
@@ -2661,16 +2661,25 @@
 tryCount
+ForLambda
  + 1, ma
@@ -2693,22 +2693,17 @@
 etries, 
-cacheR
+r
 esult.ca
@@ -2725,192 +2725,263 @@
     
-lastCacheResult = cacheResult;%0A    %7D%0A    Preconditions.checkNotNull(%0A        lastCacheResult,%0A        %22One error should have happened, therefore lastCacheResult should be non null.%22);%0A
+          return delegate.fetchAsync(ruleKey, output);%0A              %7D);%0A    %7D%0A    return Futures.transform(%0A        resultFuture,%0A        result -%3E %7B%0A          if (result.getType() != CacheResultType.ERROR) %7B%0A            return result;%0A          %7D%0A      
     
@@ -3028,16 +3028,22 @@
 rrors);%0A
+      
     if (
@@ -3108,16 +3108,22 @@
 %7B%0A      
+    
+  
 buckEven
@@ -3143,16 +3143,22 @@
         
+     
+ 
 ConsoleE
@@ -3171,16 +3171,22 @@
 arning(%0A
+      
         
@@ -3246,32 +3246,38 @@
 ,%0A              
+      
 ruleKey, cacheMo
@@ -3306,26 +3306,38 @@
 ries));%0A    
-%7D%0A
+      %7D%0A      
     return C
@@ -3366,18 +3366,9 @@
 rom(
-lastCacheR
+r
 esul
@@ -3394,24 +3394,36 @@
 g).build();%0A
+        %7D);%0A
   %7D%0A%0A  @Over

Make RetryingCacheDecorator not syncify its delegate