/*
 * 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.api.internal.artifacts.ivyservice.resolveengine.excludes.factories;

import org.gradle.api.internal.artifacts.ivyservice.resolveengine.excludes.specs.ExcludeSpec;
import org.gradle.internal.Factory;
import org.gradle.internal.UncheckedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

public class LoggingExcludeFactory extends DelegatingExcludeFactory {
    private final static Logger LOGGER = LoggerFactory.getLogger(LoggingExcludeFactory.class);

    LoggingExcludeFactory(ExcludeFactory delegate) {
        super(delegate);
    }

    public static ExcludeFactory maybeLog(ExcludeFactory factory) {
        if (LOGGER.isDebugEnabled()) {
            return new LoggingExcludeFactory(factory);
        }
        return factory;
    }

    @Override
    public ExcludeSpec anyOf(ExcludeSpec one, ExcludeSpec two) {
        return log("anyOf", () -> super.anyOf(one, two), one, two);
    }

    @Override
    public ExcludeSpec allOf(ExcludeSpec one, ExcludeSpec two) {
        return log("allOf", () -> super.allOf(one, two), one, two);
    }

    @Override
    public ExcludeSpec anyOf(Set<ExcludeSpec> specs) {
        return log("anyOf", () -> super.anyOf(specs), specs);
    }

    @Override
    public ExcludeSpec allOf(Set<ExcludeSpec> specs) {
        return log("allOf", () -> super.allOf(specs), specs);
    }

    private static ExcludeSpec log(String operationName, Factory<ExcludeSpec> factory, Object... operands) {
        ExcludeSpec spec;
        try {
            spec = factory.create();
        } catch (StackOverflowError e) {
            StringWriter sw = new StringWriter();
            sw.append("{\"stackoverflow\": [");
            PrintWriter printWriter = new PrintWriter(sw);
            StackTraceElement[] stackTrace = e.getStackTrace();
            printWriter.print(Arrays.stream(stackTrace)
                .limit(100)
                .map(d -> {
                    return "\"" + d.toString() + "\"";
                })
                .collect(Collectors.joining(", "))
            );
            sw.append("]}");
            LOGGER.debug("{\"operation\": { \"name\": \"{}\", \"operands\": {}, \"result\": {} } }", operationName, toList(operands), sw.toString());
            throw UncheckedException.throwAsUncheckedException(e);
        }
        LOGGER.debug("{\"operation\": { \"name\": \"{}\", \"operands\": {}, \"result\": {} } }", operationName, toList(operands), spec);
        return spec;
    }

    private static Collection<?> toList(Object[] operands) {
        return singleCollection(operands) ? (Collection<?>) operands[0] : Arrays.asList(operands);
    }

    private static boolean singleCollection(Object[] operands) {
        return operands.length== 1 && operands[0] instanceof Collection;
    }
}

@@ -1268,16 +1268,52 @@
 lass);%0A%0A
+    private final Subject subject;%0A%0A
     Logg
@@ -1378,24 +1378,299 @@
 (delegate);%0A
+        this.subject = computeWhatToLog();%0A    %7D%0A%0A    private static Subject computeWhatToLog() %7B%0A        String subjectString = System.getProperty(%22org.gradle.internal.dm.trace.excludes%22, Subject.all.toString());%0A        return Subject.valueOf(subjectString.toLowerCase());%0A
     %7D%0A%0A    p
@@ -2463,23 +2463,16 @@
 private 
-static 
 ExcludeS
@@ -2667,24 +2667,79 @@
 wError e) %7B%0A
+            if (subject.isTraceStackOverflows()) %7B%0A    
             
@@ -2772,24 +2772,28 @@
 ngWriter();%0A
+    
             
@@ -2824,24 +2824,28 @@
 low%5C%22: %5B%22);%0A
+    
             
@@ -2903,16 +2903,20 @@
         
+    
 StackTra
@@ -2971,16 +2971,20 @@
         
+    
 printWri
@@ -3019,16 +3019,20 @@
 kTrace)%0A
+    
         
@@ -3067,16 +3067,20 @@
         
+    
 .map(d -
@@ -3083,16 +3083,20 @@
 (d -%3E %7B%0A
+    
         
@@ -3142,16 +3142,20 @@
 + %22%5C%22%22;%0A
+    
         
@@ -3181,16 +3181,20 @@
         
+    
 .collect
@@ -3232,19 +3232,27 @@
         
-);%0A
+    );%0A    
         
@@ -3276,32 +3276,36 @@
 %22);%0A            
+    
 LOGGER.debug(%22%7B%5C
@@ -3418,32 +3418,46 @@
 sw.toString());%0A
+            %7D%0A
             thro
@@ -3509,32 +3509,79 @@
 n(e);%0A        %7D%0A
+        if (subject.isTraceOperations()) %7B%0A    
         LOGGER.d
@@ -3701,16 +3701,26 @@
  spec);%0A
+        %7D%0A
         
@@ -4049,11 +4049,625 @@
 n;%0A    %7D
+%0A%0A    private enum Subject %7B%0A        all(true, true),%0A        stackoverflow(false, true),%0A        operations(true, false);%0A%0A        private final boolean traceOperations;%0A        private final boolean traceStackOverflows;%0A%0A        Subject(boolean traceOperations, boolean traceStackOverflows) %7B%0A            this.traceOperations = traceOperations;%0A            this.traceStackOverflows = traceStackOverflows;%0A        %7D%0A%0A        public boolean isTraceOperations() %7B%0A            return traceOperations;%0A        %7D%0A%0A        public boolean isTraceStackOverflows() %7B%0A            return traceStackOverflows;%0A        %7D%0A    %7D
 %0A%7D%0A

Add ability to trace stack overflows only