/*
 * 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 java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Assembles the parts of a build script.
 */
public class BuildScriptBuilder {
    private final File target;
    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>();

    public BuildScriptBuilder(File target) {
        this.target = target;
    }

    /**
     * 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, Splitter.on("\n").splitToList(config)));
        return this;
    }

    public TemplateOperation create() {
        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
                        for (Map.Entry<String, String> entry : plugins.entrySet()) {
                            writer.println();
                            writer.println("// " + entry.getValue());
                            writer.println("apply plugin: '" + entry.getKey() + "'");
                        }

                        // 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) {
                                        writer.println("    " + config + " '" + dep + "'");
                                    }
                                }
                            }
                            writer.println("}");
                        }

                        for (ConfigSpec configSpec : config) {
                            writer.println();
                            writer.println("// " + configSpec.comment);
                            for (String line : configSpec.configLines) {
                                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 List<String> configLines;

        public ConfigSpec(String comment, List<String> configLines) {
            this.comment = comment;
            this.configLines = configLines;
        }
    }
}
