/*
 * Copyright 2019 the original author or authors.
 *
 * 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 org.gradle.internal.execution.steps;

import com.google.common.collect.Iterables;
import org.gradle.internal.execution.BeforeExecutionContext;
import org.gradle.internal.execution.InputChangesContext;
import org.gradle.internal.execution.Result;
import org.gradle.internal.execution.Step;
import org.gradle.internal.execution.UnitOfWork;
import org.gradle.internal.execution.history.BeforeExecutionState;
import org.gradle.internal.execution.impl.OutputsCleaner;
import org.gradle.internal.file.Deleter;
import org.gradle.internal.fingerprint.FileCollectionFingerprint;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashSet;
import java.util.Set;

public class CleanupOutputsStep<C extends InputChangesContext, R extends Result> implements Step<C, R> {

    private final Deleter deleter;
    private final Step<? super C, ? extends R> delegate;

    public CleanupOutputsStep(
        Deleter deleter,
        Step<? super C, ? extends R> delegate
    ) {
        this.deleter = deleter;
        this.delegate = delegate;
    }

    @Override
    public R execute(C context) {
        if (!context.isIncrementalExecution()) {
            UnitOfWork work = context.getWork();
            if (work.shouldCleanupOutputsOnNonIncrementalExecution()) {
                boolean hasOverlappingOutputs = context.getBeforeExecutionState()
                    .flatMap(BeforeExecutionState::getDetectedOverlappingOutputs)
                    .isPresent();
                if (hasOverlappingOutputs) {
                    cleanupOverlappingOutputs(context, work);
                } else {
                    cleanupExclusiveOutputs(work);
                }
            }
        }
        return delegate.execute(context);
    }

    private void cleanupOverlappingOutputs(BeforeExecutionContext context, UnitOfWork work) {
        context.getAfterPreviousExecutionState().ifPresent(previousOutputs -> {
            Set<File> outputDirectoriesToPreserve = new HashSet<>();
            work.visitOutputProperties((name, type, roots) -> {
                switch (type) {
                    case FILE:
                        for (File root : roots) {
                            File parentFile = root.getParentFile();
                            if (parentFile != null) {
                                outputDirectoriesToPreserve.add(parentFile);
                            }
                        }
                        break;
                    case DIRECTORY:
                        Iterables.addAll(outputDirectoriesToPreserve, roots);
                        break;
                    default:
                        throw new AssertionError();
                }
            });
            OutputsCleaner cleaner = new OutputsCleaner(file -> true, dir -> !outputDirectoriesToPreserve.contains(dir));
            for (FileCollectionFingerprint fileCollectionFingerprint : previousOutputs.getOutputFileProperties().values()) {
                try {
                    cleaner.cleanupOutputs(fileCollectionFingerprint);
                } catch (IOException e) {
                    throw new UncheckedIOException("Failed to clean up output files for " + work.getDisplayName(), e);
                }
            }
        });
    }

    private void cleanupExclusiveOutputs(UnitOfWork work) {
        work.visitOutputProperties((name, type, roots) -> {
            for (File root : roots) {
                if (root.exists()) {
                    switch (type) {
                        case FILE:
                            deleter.delete(root);
                            break;
                        case DIRECTORY:
                            try {
                                deleter.ensureEmptyDirectory(root, true);
                            } catch (IOException ex) {
                                throw new UncheckedIOException(ex);
                            }
                            break;
                        default:
                            throw new AssertionError();
                    }
                }
            }
        });
    }
}

@@ -4105,24 +4105,54 @@
 exists()) %7B%0A
+                    try %7B%0A    
             
@@ -4191,32 +4191,36 @@
                 
+ 
+   
 case FILE:%0A     
@@ -4206,32 +4206,36 @@
      case FILE:%0A
+    
                 
@@ -4288,39 +4288,47 @@
                 
+    
 break;%0A
+    
                 
@@ -4355,42 +4355,8 @@
 RY:%0A
-                            try %7B%0A
     
@@ -4457,34 +4457,14 @@
     
-%7D catch (IOException ex) %7B
+break;
 %0A   
@@ -4492,47 +4492,16 @@
     
-    throw new UncheckedIOException(ex);
+default:
 %0A   
@@ -4521,32 +4521,56 @@
             
-%7D%0A
     
-  
+throw new AssertionError();%0A
             
@@ -4571,38 +4571,35 @@
                 
-break;
+  %7D
 %0A               
@@ -4603,33 +4603,43 @@
         
-    default:%0A    
+%7D catch (IOException ex) %7B%0A
         
@@ -4660,39 +4660,47 @@
   throw new 
-AssertionError(
+UncheckedIOException(ex
 );%0A         

Simplify CleanupOutputsStep