/*
 * Copyright 2019, 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.protoc;

import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.compiler.PluginProtos;
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest;
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse;
import io.spine.code.proto.MessageType;
import io.spine.option.OptionExtensionRegistry;
import io.spine.option.OptionsProto;
import io.spine.protoc.MethodBody;
import io.spine.protoc.MethodFactory;
import io.spine.tools.gradle.compiler.protoc.GeneratedInterfaces;
import io.spine.tools.gradle.compiler.protoc.GeneratedMethods;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Base64;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

@DisplayName("Plugin should")
final class PluginTest {

    private static final String TEST_PROTO_POSTFIX = "_generators.proto";
    private static final String TEST_PROTO_FILE = "spine/tools/protoc/test_generators.proto";

    @DisplayName("process code generation request")
    @Test
    void processCodeGenerationRequest() {
        GeneratedInterfaces interfaces = GeneratedInterfaces.withDefaults();
        interfaces.filePattern().endsWith(TEST_PROTO_POSTFIX)
                  .markWith(TestInterface.class.getName());
        GeneratedMethods methods = GeneratedMethods.withDefaults();
        methods.filePattern().endsWith(TEST_PROTO_POSTFIX)
               .withMethodFactory(TestMethodFactory.class.getName());
        CodeGeneratorRequest request = requestBuilder()
                .addProtoFile(TestGeneratorsProto.getDescriptor()
                                                 .toProto())
                .addFileToGenerate(TEST_PROTO_FILE)
                .setParameter(encodedProtocConfig(interfaces, methods))
                .build();

        CodeGeneratorResponse response = runPlugin(request);

        assertEquals(2, response.getFileCount());
        CodeGeneratorResponse.File messageInterface = response.getFile(0);
        CodeGeneratorResponse.File messageMethod = response.getFile(1);
        assertEquals(TestInterface.class.getName() + ',', messageInterface.getContent());
        assertEquals(TestMethodFactory.TEST_METHOD.value(), messageMethod.getContent());
    }

    private static String encodedProtocConfig(GeneratedInterfaces interfaces,
                                              GeneratedMethods methods) {
        SpineProtocConfig protocConfig = SpineProtocConfig
                .newBuilder()
                .setGeneratedInterfaces(interfaces.asProtocConfig())
                .setGeneratedMethods(methods.asProtocConfig())
                .build();
        return Base64.getEncoder()
                     .encodeToString(protocConfig.toByteArray());
    }

    private static CodeGeneratorResponse runPlugin(CodeGeneratorRequest request) {
        try (InputStream testInput = new ByteArrayInputStream(request.toByteArray());
             ByteArrayOutputStream bos = new ByteArrayOutputStream();
             PrintStream testOutput = new PrintStream(bos)
        ) {
            //noinspection ZeroLengthArrayAllocation
            withSystemStreams(testInput, testOutput, () -> Plugin.main(new String[]{}));
            return CodeGeneratorResponse.parseFrom(bos.toByteArray(),
                                                   OptionExtensionRegistry.instance());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("UseOfSystemOutOrSystemErr") // Required by the protoc API.
    private static void withSystemStreams(InputStream in, PrintStream os, Runnable action) {
        InputStream oldIn = System.in;
        PrintStream oldOut = System.out;
        try {
            System.setIn(in);
            System.setOut(os);
            action.run();
        } finally {
            System.setIn(oldIn);
            System.setOut(oldOut);
        }
    }

    private static CodeGeneratorRequest.Builder requestBuilder() {
        return CodeGeneratorRequest
                .newBuilder()
                .addProtoFile(descriptorProto())
                .addProtoFile(spineOptionsProto())
                .setCompilerVersion(compilerVersion());
    }

    private static PluginProtos.Version compilerVersion() {
        return PluginProtos.Version.newBuilder()
                                   .setMajor(3)
                                   .setMajor(6)
                                   .setPatch(1)
                                   .setSuffix("")
                                   .build();
    }

    private static DescriptorProtos.FileDescriptorProto spineOptionsProto() {
        return OptionsProto.getDescriptor()
                           .toProto();
    }

    private static DescriptorProtos.FileDescriptorProto descriptorProto() {
        return DescriptorProtos.getDescriptor()
                               .toProto();
    }

    @SuppressWarnings("InterfaceNeverImplemented")
    public interface TestInterface {

    }

    @Immutable
    public static class TestMethodFactory implements MethodFactory {

        private static final MethodBody TEST_METHOD = MethodBody.of("public void test(){}");

        @Override
        public List<MethodBody> newMethodsFor(MessageType messageType) {
            return ImmutableList.of(TEST_METHOD);
        }
    }
}

@@ -1703,16 +1703,79 @@
 ethods;%0A
+import io.spine.tools.protoc.messageinterface.TestEventsProto;%0A
 import o
@@ -1808,16 +1808,16 @@
 ayName;%0A
-
 import o
@@ -2544,32 +2544,51 @@
 es.filePattern()
+%0A                  
 .endsWith(TEST_P
@@ -2758,16 +2758,32 @@
 attern()
+%0A               
 .endsWit
@@ -3553,24 +3553,24 @@
 Content());%0A
-
         asse
@@ -3645,32 +3645,994 @@
 tent());%0A    %7D%0A%0A
+    @DisplayName(%22skip generation of standard interfaces if they are %60ignored%60%22)%0A    @Test%0A    void skipStandardInterfacesIfIgnored() %7B%0A        GeneratedInterfaces interfaces = GeneratedInterfaces.withDefaults();%0A        interfaces.filePattern()%0A                  .endsWith(%22events.proto%22)%0A                  .ignore();%0A        CodeGeneratorRequest request = requestBuilder()%0A                .addProtoFile(TestEventsProto.getDescriptor()%0A                                             .toProto())%0A                .addFileToGenerate(%22spine/tools/protoc/messageinterface/test_events.proto%22)%0A                .setParameter(encodedProtocConfig(interfaces))%0A                .build();%0A        CodeGeneratorResponse response = runPlugin(request);%0A%0A        assertEquals(0, response.getFileCount());%0A    %7D%0A%0A    private static String encodedProtocConfig(GeneratedInterfaces interfaces) %7B%0A        return encodedProtocConfig(interfaces, GeneratedMethods.withDefaults());%0A    %7D%0A%0A
     private stat

Add test for the defaults override functionality