/*
 * Copyright 2018, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.tools.gradle.compiler;

import com.google.common.collect.ImmutableSet;
import io.spine.tools.compiler.type.DescriptorSetFiles;
import io.spine.tools.gradle.SpinePlugin;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;

import java.io.File;
import java.util.Collection;

import static io.spine.code.proto.FileDescriptors.KNOWN_TYPES;
import static io.spine.tools.gradle.TaskName.GENERATE_PROTO;
import static io.spine.tools.gradle.TaskName.GENERATE_TEST_PROTO;
import static io.spine.tools.gradle.TaskName.MERGE_DESCRIPTOR_SET;
import static io.spine.tools.gradle.TaskName.MERGE_TEST_DESCRIPTOR_SET;
import static io.spine.tools.gradle.TaskName.PROCESS_RESOURCES;
import static io.spine.tools.gradle.TaskName.PROCESS_TEST_RESOURCES;
import static io.spine.tools.gradle.compiler.Extension.getMainDescriptorSetPath;
import static io.spine.tools.gradle.compiler.Extension.getTestDescriptorSetPath;
import static java.util.stream.Collectors.toList;

/**
 * @author Dmytro Dashenkov
 */
public class DescriptorSetMergerPlugin extends SpinePlugin {

    @Override
    public void apply(Project project) {
        createMainTask(project);
        createTestTask(project);
    }

    private void createMainTask(Project project) {
        logDependingTask(MERGE_DESCRIPTOR_SET, PROCESS_RESOURCES, GENERATE_PROTO);
        newTask(MERGE_DESCRIPTOR_SET,
                createMergingAction(configuration(project, "runtime"),
                                    getMainDescriptorSetPath(project)))
                .insertAfterTask(GENERATE_PROTO)
                .insertBeforeTask(PROCESS_RESOURCES)
                .applyNowTo(project);
    }

    private void createTestTask(Project project) {
        logDependingTask(MERGE_TEST_DESCRIPTOR_SET, PROCESS_TEST_RESOURCES, GENERATE_TEST_PROTO);
        newTask(MERGE_TEST_DESCRIPTOR_SET,
                createMergingAction(configuration(project, "testRuntime"),
                                    getTestDescriptorSetPath(project)))
                .insertAfterTask(GENERATE_TEST_PROTO)
                .insertBeforeTask(PROCESS_TEST_RESOURCES)
                .applyNowTo(project);
    }

    private static Action<Task> createMergingAction(Configuration configuration,
                                                    String descriptorSetPath) {
        return task -> {
            File descriptorSet = new File(descriptorSetPath);
            Collection<File> descriptors =
                    configuration.getFiles()
                                 .stream()
                                 .filter(file -> KNOWN_TYPES.equals(file.getName()))
                                 .collect(toList());
            ImmutableSet.Builder<File> files = ImmutableSet
                    .<File>builder()
                    .addAll(descriptors);
            if (descriptorSet.exists()) {
                files.add(descriptorSet);
            }
            DescriptorSetFiles.merge(files.build())
                              .writeTo(descriptorSet);
        };
    }

    private static Configuration configuration(Project project, String name) {
        return project.getConfigurations()
                      .getByName(name);
    }
}

@@ -1094,16 +1094,60 @@
 bleSet;%0A
+import io.spine.code.proto.FileDescriptors;%0A
 import i
@@ -1376,28 +1376,144 @@
 on;%0A
-%0Aimport java.io.File
+import org.gradle.jvm.tasks.Jar;%0A%0Aimport java.io.File;%0Aimport java.io.IOException;%0Aimport java.nio.file.Path;%0Aimport java.nio.file.Paths
 ;%0Aim
@@ -2156,32 +2156,142 @@
 criptorSetPath;%0A
+import static io.spine.util.Exceptions.illegalStateWithCauseOf;%0Aimport static java.nio.file.Files.isSameFile;%0A
 import static ja
@@ -2535,32 +2535,963 @@
 stTask(project);
+%0A%0A        project.getTasks()%0A               .withType(Jar.class)%0A               .forEach(task -%3E stripOffKnownTypes(project, task));%0A    %7D%0A%0A    private static void stripOffKnownTypes(Project project, Jar jarTask) %7B%0A        Path ownMainFile = Paths.get(getTestDescriptorSetPath(project));%0A        Path ownTestFile = Paths.get(getMainDescriptorSetPath(project));%0A%0A        jarTask.exclude(file -%3E %7B%0A            boolean knownTypes = FileDescriptors.KNOWN_TYPES.equals(file.getName());%0A            if (!knownTypes) %7B%0A                return false;%0A            %7D%0A            boolean domesticFile;%0A            try %7B%0A                Path path = file.getFile().toPath();%0A                domesticFile = isSameFile(path, ownMainFile)%0A                            %7C%7C isSameFile(path, ownTestFile);%0A            %7D catch (IOException e) %7B%0A                throw illegalStateWithCauseOf(e);%0A            %7D%0A            return !domesticFile;%0A        %7D);
 %0A    %7D%0A%0A    priv

Exclude redundant `known_type.desc` files from artifacts.