/*
 * 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.insert;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File;
import io.spine.code.proto.MessageType;
import io.spine.code.proto.Type;
import io.spine.option.IsOption;
import io.spine.option.Options;
import io.spine.tools.protoc.CompilerOutput;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Collection;
import java.util.Optional;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;
import static io.spine.option.OptionsProto.everyIs;
import static io.spine.option.OptionsProto.is;
import static io.spine.tools.protoc.insert.MessageInterfaceSpec.prepareInterface;

/**
 * A tuple of two {@link File} instances representing a message and the interface resolved for that
 * message.
 */
final class MessageAndInterface {

    private final InsertionPoint messageFile;
    private final @Nullable CustomMessageInterface interfaceFile;

    private MessageAndInterface(InsertionPoint messageFile,
                                @Nullable CustomMessageInterface interfaceFile) {
        this.messageFile = checkNotNull(messageFile);
        this.interfaceFile = interfaceFile;
    }

    /**
     * Scans the given {@linkplain FileDescriptorProto file} for the {@code (every_is)} option.
     */
    static Collection<CompilerOutput> scanFileOption(MessageType type) {
        Set<CompilerOutput> files = getEveryIs(type)
                .map(option -> generateFile(type, option))
                .map(MessageAndInterface::asSet)
                .orElseGet(ImmutableSet::of);
        return files;
    }

    private static Optional<IsOption> getEveryIs(MessageType type) {
        Descriptors.FileDescriptor descriptor = type.descriptor()
                                                    .getFile();
        Optional<IsOption> value = Options.option(descriptor, everyIs);
        return value;
    }

    /**
     * Scans the given {@linkplain DescriptorProto message} for the {@code (is)} option.
     */
    static Collection<CompilerOutput> scanMsgOption(MessageType type) {
        Set<CompilerOutput> files = getIs(type)
                .map(option -> generateFile(type, option))
                .map(MessageAndInterface::asSet)
                .orElseGet(ImmutableSet::of);
        return files;
    }

    private static Optional<IsOption> getIs(Type<?, ?> type) {
        return type instanceof MessageType
               ? Options.option((Descriptor) type.descriptor(), is)
               : Optional.empty();
    }

    private static MessageAndInterface generateFile(MessageType type,
                                                    IsOption optionValue) {
        MessageInterfaceSpec interfaceSpec = prepareInterface(optionValue, type);
        CustomMessageInterface messageInterface = CustomMessageInterface.from(interfaceSpec);
        InsertionPoint message = InsertionPoint.implementInterface(type, messageInterface);
        CustomMessageInterface interfaceToGenerate = optionValue.getGenerate()
                                                     ? messageInterface
                                                     : null;
        MessageAndInterface result = new MessageAndInterface(message, interfaceToGenerate);
        return result;
    }

    /**
     * Converts the instance into the pair containing a message file and an interface file.
     */
    private Set<CompilerOutput> asSet() {
        return interfaceFile == null
               ? ImmutableSet.of(messageFile)
               : ImmutableSet.of(messageFile, interfaceFile);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        MessageAndInterface that = (MessageAndInterface) o;
        return Objects.equal(messageFile, that.messageFile) &&
                Objects.equal(interfaceFile, that.interfaceFile);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(messageFile, interfaceFile);
    }
}

@@ -1301,59 +1301,8 @@
 rs;%0A
-import com.google.protobuf.Descriptors.Descriptor;%0A
 impo
@@ -1434,88 +1434,23 @@
 ine.
-code.proto.Type;%0Aimport io.spine.option.IsOption;%0Aimport io.spine.option.Options
+option.IsOption
 ;%0Aim
@@ -1697,16 +1697,62 @@
 otNull;%0A
+import static io.spine.option.Options.option;%0A
 import s
@@ -3095,24 +3095,16 @@
 value = 
-Options.
 option(d
@@ -3609,18 +3609,19 @@
 tIs(
-Type%3C?, ?%3E
+MessageType
  typ
@@ -3644,136 +3644,36 @@
 urn 
-type instanceof MessageType%0A               ? Options.option((Descriptor) type.descriptor(), is)%0A               : Optional.empty(
+option(type.descriptor(), is
 );%0A 

Delete a "dead" branch.