/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.maven.surefire.junitcore;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import org.apache.maven.surefire.api.booter.ProviderParameterNames;

/**
 * @author Kristian Rosenvold
 */
public final class JUnitCoreParameters {
    public static final String PARALLEL_KEY = ProviderParameterNames.PARALLEL_PROP;

    public static final String PERCORETHREADCOUNT_KEY = "perCoreThreadCount";

    public static final String THREADCOUNT_KEY = ProviderParameterNames.THREADCOUNT_PROP;

    public static final String THREADCOUNTSUITES_KEY = ProviderParameterNames.THREADCOUNTSUITES_PROP;

    public static final String THREADCOUNTCLASSES_KEY = ProviderParameterNames.THREADCOUNTCLASSES_PROP;

    public static final String THREADCOUNTMETHODS_KEY = ProviderParameterNames.THREADCOUNTMETHODS_PROP;

    public static final String USEUNLIMITEDTHREADS_KEY = "useUnlimitedThreads";

    public static final String PARALLEL_TIMEOUT_KEY = ProviderParameterNames.PARALLEL_TIMEOUT_PROP;

    public static final String PARALLEL_TIMEOUTFORCED_KEY = ProviderParameterNames.PARALLEL_TIMEOUTFORCED_PROP;

    public static final String PARALLEL_OPTIMIZE_KEY = ProviderParameterNames.PARALLEL_OPTIMIZE_PROP;

    public static final String ENABLE_OUT_ERR_ELEMENTS_KEY = ProviderParameterNames.ENABLE_OUT_ERR_ELEMENTS_PROP;

    public static final String ENABLE_PROPERTIES_ELEMENT_KEY = ProviderParameterNames.ENABLE_PROPERTIES_ELEMENT_PROP;

    private final String parallel;

    private final boolean perCoreThreadCount;

    private final int threadCount;

    private final int threadCountSuites;

    private final int threadCountClasses;

    private final int threadCountMethods;

    private final double parallelTestsTimeoutInSeconds;

    private final double parallelTestsTimeoutForcedInSeconds;

    private final boolean useUnlimitedThreads;

    private final boolean parallelOptimization;

    private final boolean enableOutErrElements;

    private final boolean enablePropertiesElement;

    public JUnitCoreParameters(Map<String, String> properties) {
        parallel = property(properties, PARALLEL_KEY, "none").toLowerCase();
        perCoreThreadCount = property(properties, PERCORETHREADCOUNT_KEY, true);
        threadCount = property(properties, THREADCOUNT_KEY, 0);
        threadCountMethods = property(properties, THREADCOUNTMETHODS_KEY, 0);
        threadCountClasses = property(properties, THREADCOUNTCLASSES_KEY, 0);
        threadCountSuites = property(properties, THREADCOUNTSUITES_KEY, 0);
        useUnlimitedThreads = property(properties, USEUNLIMITEDTHREADS_KEY, false);
        parallelTestsTimeoutInSeconds = Math.max(property(properties, PARALLEL_TIMEOUT_KEY, 0d), 0);
        parallelTestsTimeoutForcedInSeconds = Math.max(property(properties, PARALLEL_TIMEOUTFORCED_KEY, 0d), 0);
        parallelOptimization = property(properties, PARALLEL_OPTIMIZE_KEY, true);
        enableOutErrElements = property(properties, ENABLE_OUT_ERR_ELEMENTS_KEY, true);
        enablePropertiesElement = property(properties, ENABLE_PROPERTIES_ELEMENT_KEY, true);
    }

    private static Collection<String> lowerCase(String... elements) {
        ArrayList<String> lowerCase = new ArrayList<>();
        for (String element : elements) {
            lowerCase.add(element.toLowerCase());
        }
        return lowerCase;
    }

    private boolean isAllParallel() {
        return "all".equals(parallel);
    }

    public boolean isParallelMethods() {
        return isAllParallel()
                || lowerCase("both", "methods", "suitesAndMethods", "classesAndMethods")
                        .contains(parallel);
    }

    public boolean isParallelClasses() {
        return isAllParallel()
                || lowerCase("both", "classes", "suitesAndClasses", "classesAndMethods")
                        .contains(parallel);
    }

    public boolean isParallelSuites() {
        return isAllParallel()
                || lowerCase("suites", "suitesAndClasses", "suitesAndMethods").contains(parallel);
    }

    /**
     * @deprecated Instead use the expression isParallelMethods() &amp;&amp; isParallelClasses().
     * @return {@code true} if classes and methods are both parallel
     */
    @Deprecated
    @SuppressWarnings("unused")
    public boolean isParallelBoth() {
        return isParallelMethods() && isParallelClasses();
    }

    public boolean isPerCoreThreadCount() {
        return perCoreThreadCount;
    }

    public int getThreadCount() {
        return threadCount;
    }

    public int getThreadCountMethods() {
        return threadCountMethods;
    }

    public int getThreadCountClasses() {
        return threadCountClasses;
    }

    public int getThreadCountSuites() {
        return threadCountSuites;
    }

    public boolean isUseUnlimitedThreads() {
        return useUnlimitedThreads;
    }

    public double getParallelTestsTimeoutInSeconds() {
        return parallelTestsTimeoutInSeconds;
    }

    public double getParallelTestsTimeoutForcedInSeconds() {
        return parallelTestsTimeoutForcedInSeconds;
    }

    public boolean isNoThreading() {
        return !isParallelismSelected();
    }

    public boolean isParallelismSelected() {
        return isParallelSuites() || isParallelClasses() || isParallelMethods();
    }

    public boolean isParallelOptimization() {
        return parallelOptimization;
    }

    public boolean isEnableOutErrElements() {
        return enableOutErrElements;
    }

    public boolean isEnablePropertiesElement() {
        return enablePropertiesElement;
    }

    @Override
    public String toString() {
        return "parallel='" + parallel + '\'' + ", perCoreThreadCount=" + perCoreThreadCount + ", threadCount="
                + threadCount + ", useUnlimitedThreads=" + useUnlimitedThreads + ", threadCountSuites="
                + threadCountSuites
                + ", threadCountClasses=" + threadCountClasses + ", threadCountMethods=" + threadCountMethods
                + ", parallelOptimization=" + parallelOptimization
                + ", enableOutErrElements=" + enableOutErrElements
                + ", enablePropertiesElement=" + enablePropertiesElement;
    }

    private static boolean property(Map<String, String> properties, String key, boolean fallback) {
        return properties.containsKey(key) ? Boolean.valueOf(properties.get(key)) : fallback;
    }

    private static String property(Map<String, String> properties, String key, String fallback) {
        return properties.containsKey(key) ? properties.get(key) : fallback;
    }

    private static int property(Map<String, String> properties, String key, int fallback) {
        return properties.containsKey(key) ? Integer.valueOf(properties.get(key)) : fallback;
    }

    private static double property(Map<String, String> properties, String key, double fallback) {
        return properties.containsKey(key) ? Double.valueOf(properties.get(key)) : fallback;
    }
}
