/*
 * Copyright 2017, TeamDev Ltd. 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.gradle.compiler.rejection;

import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import io.spine.gradle.SpinePlugin;
import io.spine.gradle.compiler.message.MessageTypeCache;
import io.spine.tools.java.PackageName;
import io.spine.tools.java.SimpleClassName;
import io.spine.tools.proto.FileDescriptors;
import io.spine.tools.proto.Rejections;
import io.spine.tools.proto.SourceFile;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

import static io.spine.gradle.TaskName.COMPILE_JAVA;
import static io.spine.gradle.TaskName.COMPILE_TEST_JAVA;
import static io.spine.gradle.TaskName.GENERATE_PROTO;
import static io.spine.gradle.TaskName.GENERATE_REJECTIONS;
import static io.spine.gradle.TaskName.GENERATE_TEST_PROTO;
import static io.spine.gradle.TaskName.GENERATE_TEST_REJECTIONS;
import static io.spine.gradle.compiler.Extension.getMainDescriptorSetPath;
import static io.spine.gradle.compiler.Extension.getTargetGenRejectionsRootDir;
import static io.spine.gradle.compiler.Extension.getTargetTestGenRejectionsRootDir;
import static io.spine.gradle.compiler.Extension.getTestDescriptorSetPath;

/**
 * Plugin which generates Rejections declared in {@code rejections.proto} files.
 *
 * <p>Uses generated proto descriptors.
 *
 * <p>Logs a warning if there are no protobuf descriptors generated.
 *
 * @author Mikhail Mikhaylov
 * @author Alexander Yevsyukov
 * @author Alexander Litus
 * @author Alex Tymchenko
 */
public class RejectionGenPlugin extends SpinePlugin {

    /** A map from Protobuf type name to Java class FQN. */
    private final MessageTypeCache messageTypeCache = new MessageTypeCache();

    /**
     * Applies the plug-in to a project.
     *
     * <p>Adds {@code :generateRejections} and {@code :generateTestRejections} tasks.
     *
     * <p>Tasks depend on corresponding {@code :generateProto} tasks and are executed
     * before corresponding {@code :compileJava} tasks.
     */
    @Override
    public void apply(final Project project) {
        final Logger log = log();

        log.trace("Preparing to generate rejections");
        final Action<Task> mainScopeAction = new Action<Task>() {
            @Override
            public void execute(Task task) {
                final String path = getMainDescriptorSetPath(project);
                log.debug("Generating rejections from {}", path);
                final Collection<FileDescriptorProto> allFiles = FileDescriptors.parse(path);
                collectAllMessageTypes(allFiles);
                final List<FileDescriptorProto> rejectionFiles = Rejections.collect(allFiles);
                generate(rejectionFiles, getTargetGenRejectionsRootDir(project));
            }
        };

        logDependingTask(log, GENERATE_REJECTIONS, COMPILE_JAVA, GENERATE_PROTO);
        final GradleTask generateRejections =
                newTask(GENERATE_REJECTIONS, mainScopeAction)
                        .insertAfterTask(GENERATE_PROTO)
                        .insertBeforeTask(COMPILE_JAVA)
                        .applyNowTo(project);

        log.trace("Preparing to generate test rejections");
        final Action<Task> testScopeAction = new Action<Task>() {
            @Override
            public void execute(Task task) {
                final String path = getTestDescriptorSetPath(project);
                log.debug("Generating test rejections from {}", path);
                final Collection<FileDescriptorProto> allFiles = FileDescriptors.parse(path);
                collectAllMessageTypes(allFiles);
                final List<FileDescriptorProto> rejectionFiles = Rejections.collect(allFiles);
                generate(rejectionFiles, getTargetTestGenRejectionsRootDir(project));
            }
        };

        logDependingTask(log, GENERATE_TEST_REJECTIONS, COMPILE_TEST_JAVA, GENERATE_TEST_PROTO);

        final GradleTask generateTestRejections =
                newTask(GENERATE_TEST_REJECTIONS, testScopeAction)
                        .insertAfterTask(GENERATE_TEST_PROTO)
                        .insertBeforeTask(COMPILE_TEST_JAVA)
                        .applyNowTo(project);
        log.debug("Rejection generation phase initialized with tasks: {}, {}",
                    generateRejections,
                    generateTestRejections);
    }

    private void collectAllMessageTypes(Iterable<FileDescriptorProto> files) {
        for (FileDescriptorProto file : files) {
            messageTypeCache.cacheTypes(file);
        }
    }

    private void generate(List<FileDescriptorProto> files, String rejectionsRootDir) {
        final Logger log = log();
        log.debug("Processing the file descriptors for the rejections {}", files);
        for (FileDescriptorProto file : files) {
            if (SourceFile.isRejectionsFile(file)) {
                generateRejections(file, messageTypeCache.getCachedTypes(), rejectionsRootDir);
            } else {
                log.error("Invalid rejections file: {}", file.getName());
            }
        }
    }

    private static void generateRejections(FileDescriptorProto file,
                                           Map<String, String> messageTypeMap,
                                           String rejectionsRootDir) {
        final Logger log = log();
        log.debug("Generating rejections from file {}", file.getName());

        if (log.isTraceEnabled()) {
            log.trace("javaPackage: {}, javaOuterClassName: {}",
                      PackageName.resolve(file),
                      SimpleClassName.outerOf(file));
        }

        final List<DescriptorProto> rejections = file.getMessageTypeList();
        for (DescriptorProto rejection : rejections) {
            // The name of the generated `ThrowableMessage` will be the same
            // as for the Protobuf message.
            log.trace("Processing rejection '{}'", rejection.getName());

            final RejectionMetadata metadata = new RejectionMetadata(rejection, file);
            final File outputDir = new File(rejectionsRootDir);
            final RejectionWriter writer = new RejectionWriter(metadata, outputDir, messageTypeMap);
            writer.write();
        }
    }

    private static Logger log() {
        return LogSingleton.INSTANCE.value;
    }

    private enum LogSingleton {
        INSTANCE;
        @SuppressWarnings("NonSerializableFieldInSerializableClass")
        private final Logger value = LoggerFactory.getLogger(RejectionGenPlugin.class);
    }
}

@@ -3305,63 +3305,8 @@
 );%0A%0A
-        log.trace(%22Preparing to generate rejections%22);%0A
     
@@ -3359,32 +3359,32 @@
 ction%3CTask%3E() %7B%0A
+
             @Ove
@@ -4272,68 +4272,8 @@
 );%0A%0A
-        log.trace(%22Preparing to generate test rejections%22);%0A
     
@@ -5228,24 +5228,24 @@
 _TEST_JAVA)%0A
-
             
@@ -5270,32 +5270,33 @@
 NowTo(project);%0A
+%0A
         log.debu

Remove misleading logging