/*
 * Copyright 2017 Lukas Krejci
 *
 * 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.revapi;

import static org.revapi.Revapi.TIMING_LOG;

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.stream.Stream;

import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Holds the result of the analysis. The outputs of the analysis are generated by the reporters the Revapi instance
 * is configured with and as such are not directly accessible through this object.
 *
 * <p>To properly close the resource acquired by the extensions during the analysis, one has to {@link #close()} this
 * analysis results object.
 *
 * @author Lukas Krejci
 * @since 0.8.0
 */
public final class AnalysisResult implements AutoCloseable {
    private static final Logger LOG = LoggerFactory.getLogger(AnalysisResult.class);

    private final Exception failure;
    private final Extensions extensions;

    AnalysisResult(@Nullable Exception failure, Extensions extensions) {
        this.failure = failure;
        this.extensions = extensions;
    }

    public boolean isSuccess() {
        return failure == null;
    }

    /**
     * @return the error thrown during the analysis or null if the analysis completed without failures
     */
    public @Nullable Exception getFailure() {
        return failure;
    }

    public void throwIfFailed() throws Exception {
        if (failure != null) {
            throw failure;
        }
    }

    /**
     * @return the extension instances run during the analysis, each with its corresponding analysis context containing
     * the configuration used for the extension
     */
    public Extensions getExtensions() {
        return extensions;
    }

    @Override public void close() throws Exception {
        TIMING_LOG.debug("Closing all extensions");
        for (Map.Entry<?, AnalysisContext> e : extensions) {
            Object ext = e.getKey();
            if (!(ext instanceof AutoCloseable)) {
                continue;
            }

            AutoCloseable c = (AutoCloseable) ext;
            try {
                c.close();
            } catch (Exception ex) {
                LOG.warn("Failed to close " + c, ex);
            }
        }
        TIMING_LOG.debug("Extensions closed. Analysis complete.");
        TIMING_LOG.debug(Stats.asString());
    }

    public static final class Extensions implements Iterable<Map.Entry<?, AnalysisContext>> {
        private final Map<ApiAnalyzer, AnalysisContext> analyzers;
        private final Map<ElementFilter, AnalysisContext> filters;
        private final Map<Reporter, AnalysisContext> reporters;
        private final Map<DifferenceTransform<?>, AnalysisContext> transforms;

        Extensions(Map<ApiAnalyzer, AnalysisContext> analyzers, Map<ElementFilter, AnalysisContext> filters,
                          Map<Reporter, AnalysisContext> reporters,
                          Map<DifferenceTransform<?>, AnalysisContext> transforms) {
            this.analyzers = Collections.unmodifiableMap(analyzers);
            this.filters = Collections.unmodifiableMap(filters);
            this.reporters = Collections.unmodifiableMap(reporters);
            this.transforms = Collections.unmodifiableMap(transforms);
        }

        public Map<ApiAnalyzer, AnalysisContext> getAnalyzers() {
            return analyzers;
        }

        public Map<ElementFilter, AnalysisContext> getFilters() {
            return filters;
        }

        public Map<Reporter, AnalysisContext> getReporters() {
            return reporters;
        }

        public Map<DifferenceTransform<?>, AnalysisContext> getTransforms() {
            return transforms;
        }

        public <T> Map<T, AnalysisContext> getExtensions(Class<T> extensionType) {
            IdentityHashMap<T, AnalysisContext> ret = new IdentityHashMap<>();
            stream().filter(e -> extensionType.isAssignableFrom(e.getKey().getClass()))
                    .forEach(e -> ret.put(extensionType.cast(e.getKey()), e.getValue()));
            return ret;
        }

        @Override public Iterator<Map.Entry<?, AnalysisContext>> iterator() {
            return stream().iterator();
        }

        public Stream<Map.Entry<?, AnalysisContext>> stream() {
            return Stream.concat(analyzers.entrySet().stream(),
                    Stream.concat(filters.entrySet().stream(),
                            Stream.concat(reporters.entrySet().stream(),
                                    transforms.entrySet().stream())));
        }
    }
}

@@ -764,24 +764,46 @@
 a.util.Map;%0A
+import java.util.Set;%0A
 import java.
@@ -1537,24 +1537,1010 @@
 xtensions;%0A%0A
+    /**%0A     * A factory method for users that need to report success without actually running any analysis. The returned%0A     * result will be successful, but will not contain the actual configurations of extensions.%0A     *%0A     * @return a %22fake%22 successful analysis result%0A     */%0A    public static AnalysisResult fakeSuccess() %7B%0A        return new AnalysisResult(null, new Extensions(Collections.emptyMap(), Collections.emptyMap(),%0A                Collections.emptyMap(), Collections.emptyMap()));%0A    %7D%0A%0A    /**%0A     * Similar to %7B@link #fakeSuccess()%7D, this returns a failed analysis result without the need to run any analysis.%0A     *%0A     * @param failure the failure to report%0A     * @return a %22fake%22 failed analysis result%0A     */%0A    public static AnalysisResult fakeFailure(Exception failure) %7B%0A        return new AnalysisResult(failure, new Extensions(Collections.emptyMap(), Collections.emptyMap(),%0A                Collections.emptyMap(), Collections.emptyMap()));%0A    %7D%0A%0A
     Analysis
@@ -5363,16 +5363,23 @@
 xtension
+Context
 s(Class%3C
@@ -5682,32 +5682,428 @@
 ret;%0A        %7D%0A%0A
+        public %3CT%3E Set%3CT%3E getExtensionInstances(Class%3CT%3E extensionType) %7B%0A            return getExtensionContexts(extensionType).keySet();%0A        %7D%0A%0A        public %3CT%3E T getFirstExtension(Class%3CT%3E extensionType, T defaultValue) %7B%0A            Set%3CT%3E instances = getExtensionInstances(extensionType);%0A            return instances.isEmpty() ? defaultValue : instances.iterator().next();%0A        %7D%0A%0A
         @Overrid

Add some utility methods to get the configured extension instances out of AnalysisResults.