/*
 * 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.camel.itest.springboot;

import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeSet;

/**
 * Builder for the {@code ITestConfig} that enforces defaults values.
 */
public class ITestConfigBuilder {

    public static final String CONFIG_PREFIX = "itest.springboot.";

    private final ITestConfig config;

    public ITestConfigBuilder() {
        this.config = new ITestConfig();
    }

    public ITestConfigBuilder(ITestConfig config) {
        this.config = config;
    }

    public ITestConfigBuilder module(String module) {
        config.setModuleName(module);
        return this;
    }

    public ITestConfigBuilder mavenGroup(String mavenGroup) {
        config.setMavenGroup(mavenGroup);
        return this;
    }

    public ITestConfigBuilder mavenVersion(String mavenVersion) {
        config.setMavenVersion(mavenVersion);
        return this;
    }

    public ITestConfigBuilder modulesPath(String path) {
        config.setModulesPath(path);
        return this;
    }

    public ITestConfigBuilder basePath(String basePath) {
        config.setModuleBasePath(basePath);
        return this;
    }

    public ITestConfigBuilder unitTestExpectedNumber(int number) {
        config.setUnitTestsExpectedNumber(number);
        return this;
    }

    public ITestConfigBuilder unitTestBasePackage(String pack) {
        config.setUnitTestBasePackage(pack);
        return this;
    }

    public ITestConfigBuilder unitTestInclusionPattern(String pattern) {
        config.setUnitTestInclusionPattern(pattern);
        return this;
    }

    public ITestConfigBuilder unitTestExclusionPattern(String pattern) {
        config.setUnitTestExclusionPattern(pattern);
        return this;
    }

    public ITestConfigBuilder autostart(Boolean autostart) {
        config.setAutoStartComponent(autostart);
        return this;
    }

    public ITestConfigBuilder resource(String file, String dest) {
        if (config.getResources() == null) {
            config.setResources(new HashMap<>());
        }
        config.getResources().put(file, dest);
        return this;
    }

    public ITestConfigBuilder dependency(String dependencyCanonicalForm) {
        if (config.getAdditionalDependencies() == null) {
            config.setAdditionalDependencies(new HashSet<>());
        }
        config.getAdditionalDependencies().add(dependencyCanonicalForm);
        return this;
    }

    public ITestConfigBuilder exclusion(String exclusionCanonicalForm) {
        if (exclusionCanonicalForm.split(":").length != 2) {
            throw new IllegalArgumentException("Expected exclusion in the form groupId:artifactId, got: " + exclusionCanonicalForm);
        }
        if (config.getMavenExclusions() == null) {
            config.setMavenExclusions(new HashSet<String>());
        }
        config.getMavenExclusions().add(exclusionCanonicalForm);
        return this;
    }

    public ITestConfigBuilder resource(String file) {
        return resource(file, file);
    }

    public ITestConfigBuilder disableJmx(String name) {
        if (config.getJmxDisabledNames() == null) {
            config.setJmxDisabledNames(new TreeSet<String>());
        }
        config.getJmxDisabledNames().add(name);
        return this;
    }

    public ITestConfigBuilder systemProperty(String name, String value) {
        if (config.getSystemProperties() == null) {
            config.setSystemProperties(new HashMap<String, String>());
        }
        config.getSystemProperties().put(name, value);
        return this;
    }

    public ITestConfigBuilder customLog(Boolean value) {
        config.setUseCustomLog(value);
        return this;
    }

    public ITestConfigBuilder ignoreLibraryMismatch(String libraryPrefix) {
        if (config.getIgnoreLibraryMismatch() == null) {
            config.setIgnoreLibraryMismatch(new HashSet<>());
        }
        config.getIgnoreLibraryMismatch().add(libraryPrefix);
        return this;
    }

    public ITestConfigBuilder testLibraryVersion(String groupIdArtifactId, String version) {
        if (config.getTestLibraryVersions() == null) {
            config.setTestLibraryVersions(new HashMap<>());
        }
        config.getTestLibraryVersions().put(groupIdArtifactId, version);
        return this;
    }

    public ITestConfigBuilder includeTestDependencies(Boolean includeTestDependencies) {
        config.setIncludeTestDependencies(includeTestDependencies);
        return this;
    }

    public ITestConfigBuilder unitTestsEnabled(Boolean unitTestsEnabled) {
        config.setUnitTestEnabled(unitTestsEnabled);
        return this;
    }

    public ITestConfigBuilder springBootVersion(String springBootVersion) {
        config.setSpringBootVersion(springBootVersion);
        return this;
    }
    
    public ITestConfigBuilder mavenOfflineResolution(Boolean offlineResolution) {
        config.setMavenOfflineResolution(offlineResolution);
        return this;
    }

    public ITestConfigBuilder debugEnabled(Boolean debugEnabled) {
        config.setDebugEnabled(debugEnabled);
        return this;
    }

    public ITestConfigBuilder failOnRelatedLibraryMismatch(Boolean failOnRelatedLibraryMismatch) {
        config.setFailOnRelatedLibraryMismatch(failOnRelatedLibraryMismatch);
        return this;
    }

    public ITestConfig build() {

        // Checking conditions
        if (config.getModuleName() == null) {
            fail("ModuleName is required");
        }

        // Set the defaults
        if (config.getUnitTestEnabled() == null) {
            config.setUnitTestEnabled(booleanPropertyOr("unitTestEnabled", true));
        }

        if (config.getMavenGroup() == null) {
            config.setMavenGroup(propertyOr("mavenGroup", "org.apache.camel"));
        }

        if (config.getMavenVersion() == null) {
            config.setMavenVersion(propertyOr("mavenVersion", null));
        }

        if (config.getMavenOfflineResolution() == null) {
            config.setMavenOfflineResolution(booleanPropertyOr("mavenOfflineResolution", false));
        }

        if (config.getUnitTestInclusionPattern() == null) {
            config.setUnitTestInclusionPattern(propertyOr("unitTestInclusionPattern", "^.*Test$")); // All tests
        }

        if (config.getUnitTestExclusionPattern() == null) {
            config.setUnitTestExclusionPattern(propertyOr("unitTestExclusionPattern", ".*(\\.integration\\..*|IntegrationTest$)")); // Integration test
        }

        if (config.getIncludeTestDependencies() == null) {
            config.setIncludeTestDependencies(booleanPropertyOr("includeTestDependencies", config.getUnitTestEnabled()));
        }

        if (config.getIncludeProvidedDependencies() == null) {
            config.setIncludeProvidedDependencies(booleanPropertyOr("includeProvidedDependencies", false));
        }

        if (config.getModulesPath() == null) {
            config.setModulesPath(propertyOr("modulesPath", "../../components-starter/"));
        }

        if (config.getModuleBasePath() == null) {
            config.setModuleBasePath(config.getModulesPath() + config.getModuleName() + "-starter");
        }

        if (config.getUnitTestBasePackage() == null) {
            config.setUnitTestBasePackage(propertyOr("unitTestBasePackage", "org.apache.camel"));
        }

        if (config.getAutoStartComponent() == null) {
            config.setAutoStartComponent(booleanPropertyOr("autostartComponent", true));
        }

        if (config.getResources() == null) {
            config.setResources(new HashMap<>());
        }

        if (config.getAdditionalDependencies() == null) {
            config.setAdditionalDependencies(new HashSet<>());
        }

        if (config.getMavenExclusions() == null) {
            config.setMavenExclusions(new HashSet<>());
        }

        if (config.getJmxDisabledNames() == null) {
            config.setJmxDisabledNames(new HashSet<>());
        }

        if (config.getSystemProperties() == null) {
            config.setSystemProperties(new HashMap<>());
        }

        if (config.getUseCustomLog() == null) {
            config.setUseCustomLog(booleanPropertyOr("useCustomLog", true));
        }

        if (config.getIgnoreLibraryMismatch() == null) {
            config.setIgnoreLibraryMismatch(new HashSet<>());
        }

        if (config.getTestLibraryVersions() == null) {
            config.setTestLibraryVersions(new HashMap<>());
        }

        if (config.getSpringBootVersion() == null) {
            config.setSpringBootVersion(propertyOr("springBootVersion", null));
        }

        if (config.getDebugEnabled() == null) {
            config.setDebugEnabled(booleanPropertyOr("debugEnabled", false));
        }

        if (config.getFailOnRelatedLibraryMismatch() == null) {
            config.setFailOnRelatedLibraryMismatch(booleanPropertyOr("failOnRelatedLibraryMismatch", true));
        }
        return config;
    }

    private void fail(String msg) {
        throw new IllegalStateException("Configuration is not complete: " + msg);
    }

    private String propertyOr(String name, String defaultVal) {
        String res = System.getProperty(CONFIG_PREFIX + name);
        if (res == null) {
            res = defaultVal;
        }
        return res;
    }

    private Boolean booleanPropertyOr(String name, Boolean defaultVal) {
        String prop = propertyOr(name, null);
        Boolean res = defaultVal;
        if (prop != null) {
            res = Boolean.valueOf(prop);
        }

        return res;
    }
}
