/*
 * Copyright 2016 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.buildinit.plugins.internal;

import com.google.common.base.Splitter;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import org.gradle.api.GradleException;
import org.gradle.buildinit.plugins.internal.modifiers.BuildInitBuildScriptDsl;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Assembles the parts of a build script.
 */
public class BuildScriptBuilder {
    private final List<String> headerLines = new ArrayList<String>();
    private final ListMultimap<String, DepSpec> dependencies = MultimapBuilder.linkedHashKeys().arrayListValues().build();
    private final Map<String, String> plugins = new LinkedHashMap<String, String>();
    private final List<ConfigSpec> config = new ArrayList<ConfigSpec>();

    /**
     * Adds a comment to the header of the file.
     */
    public BuildScriptBuilder fileComment(String comment) {
        headerLines.addAll(Splitter.on("\n").splitToList(comment));
        return this;
    }

    /**
     * Adds a plugin to be applied
     *
     * @param comment A description of why the plugin is required
     */
    public BuildScriptBuilder plugin(String comment, String pluginId) {
        plugins.put(pluginId, comment);
        return this;
    }

    /**
     * Adds one or more dependency to the specified configuration
     *
     * @param configuration The configuration where the dependency should be added
     * @param comment A description of why the dependencies are required
     * @param dependencies the dependencies
     */
    public BuildScriptBuilder dependency(String configuration, String comment, String... dependencies) {
        this.dependencies.put(configuration, new DepSpec(comment, Arrays.asList(dependencies)));
        return this;
    }

    /**
     * Adds one or more compile dependencies.
     *
     * @param comment A description of why the dependencies are required
     * @param dependencies The dependencies
     */
    public BuildScriptBuilder compileDependency(String comment, String... dependencies) {
        return dependency("compile", comment, dependencies);
    }

    /**
     * Adds one or more test compile dependencies.
     *
     * @param comment A description of why the dependencies are required
     * @param dependencies The dependencies
     */
    public BuildScriptBuilder testCompileDependency(String comment, String... dependencies) {
        return dependency("testCompile", comment, dependencies);
    }

    /**
     * Adds one or more test runtime dependencies.
     *
     * @param comment A description of why the dependencies are required
     * @param dependencies The dependencies
     */
    public BuildScriptBuilder testRuntimeDependency(String comment, String... dependencies) {
        return dependency("testRuntime", comment, dependencies);
    }

    /**
     * Adds some arbitrary configuration to the _end_ of the build script.
     */
    public BuildScriptBuilder configuration(String comment, String config) {
        this.config.add(new ConfigSpec(comment, config));
        return this;
    }

    /**
     * Adds some arbitrary configuration to the _end_ of the build script.
     */
    public BuildScriptBuilder configuration(String comment, Map<BuildInitBuildScriptDsl, String> config) {
        this.config.add(new ConfigSpec(comment, config));
        return this;
    }

    public TemplateOperation create(final BuildInitBuildScriptDsl dsl, final File target) {
        return new TemplateOperation() {
            @Override
            public void generate() {
                try {
                    PrintWriter writer = new PrintWriter(new FileWriter(target));
                    try {
                        // Generate the file header
                        writer.println("/*");
                        writer.println(" * This build file was generated by the Gradle 'init' task.");
                        if (!headerLines.isEmpty()) {
                            writer.println(" *");
                            for (String headerLine : headerLines) {
                                writer.println(" * " + headerLine);
                            }
                        }
                        writer.println(" */");

                        // Plugins
                        if (!plugins.isEmpty()) {
                            writer.println();
                            writer.println("plugins {");
                            for (Iterator<Map.Entry<String, String>> it = plugins.entrySet().iterator(); it.hasNext(); ) {
                                Map.Entry<String, String> entry = it.next();
                                writer.println("    // " + entry.getValue());
                                switch (dsl) {
                                    case KOTLIN:
                                        if (entry.getKey().matches("[a-z]")) {
                                            writer.println("    " + entry.getKey());
                                        } else {
                                            writer.println("    `" + entry.getKey() + "`");
                                        }
                                        break;
                                    case GROOVY:
                                    default:
                                        writer.println("    id '" + entry.getKey() + "'");
                                }
                                if (it.hasNext()) {
                                    writer.println();
                                }
                            }
                            writer.println("}");
                        }

                        // Dependencies and repositories
                        if (!dependencies.isEmpty()) {
                            writer.println();
                            writer.println("// In this section you declare where to find the dependencies of your project");
                            writer.println("repositories {");
                            writer.println("    // Use jcenter for resolving your dependencies.");
                            writer.println("    // You can declare any Maven/Ivy/file repository here.");
                            writer.println("    jcenter()");
                            writer.println("}");
                            writer.println();
                            writer.println("dependencies {");
                            boolean firstDep = true;
                            for (String config : dependencies.keySet()) {
                                for (DepSpec depSpec : dependencies.get(config)) {
                                    if (firstDep) {
                                        firstDep = false;
                                    } else {
                                        writer.println();
                                    }
                                    writer.println("    // " + depSpec.comment);
                                    for (String dep : depSpec.deps) {
                                        switch (dsl) {
                                            case KOTLIN:
                                                writer.println("    " + config + "(\"" + dep + "\")");
                                                break;
                                            case GROOVY:
                                            default:
                                                writer.println("    " + config + " '" + dep + "'");
                                        }
                                    }
                                }
                            }
                            writer.println("}");
                        }

                        for (ConfigSpec configSpec : config) {
                            writer.println();
                            writer.println("// " + configSpec.comment);
                            for (String line : configSpec.configLines.get(dsl)) {
                                writer.println(line);
                            }
                        }

                        writer.println();
                    } finally {
                        writer.close();
                    }
                } catch (Exception e) {
                    throw new GradleException("Could not generate file " + target + ".", e);
                }
            }
        };
    }

    private static class DepSpec {
        private final String comment;
        private final List<String> deps;

        DepSpec(String comment, List<String> deps) {
            this.comment = comment;
            this.deps = deps;
        }
    }

    private static class ConfigSpec {
        private final String comment;
        private final Map<BuildInitBuildScriptDsl, List<String>> configLines;

        ConfigSpec(String comment, String config) {
            this.comment = comment;
            this.configLines = new HashMap<BuildInitBuildScriptDsl, List<String>>();
            List<String> lines = Splitter.on("\n").splitToList(config);
            for (BuildInitBuildScriptDsl scriptDsl : BuildInitBuildScriptDsl.values()) {
                this.configLines.put(scriptDsl, lines);
            }
        }

        ConfigSpec(String comment, Map<BuildInitBuildScriptDsl, String> config) {
            this.comment = comment;
            this.configLines = new HashMap<BuildInitBuildScriptDsl, List<String>>();
            for (BuildInitBuildScriptDsl scriptDsl : BuildInitBuildScriptDsl.values()) {
                this.configLines.put(scriptDsl, Splitter.on("\n").splitToList(config.get(scriptDsl)));
            }
        }
    }
}

@@ -5384,17 +5384,16 @@
 sNext();
- 
 ) %7B%0A    

Make checkstyle happy