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

import com.google.common.base.Predicate;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static com.google.common.collect.Lists.newLinkedList;

/**
 * Utilities for working with {@linkplain MessageDeclaration message declarations}.
 *
 * @author Dmytro Grankin
 */
public class MessageDeclarations {

    private MessageDeclarations() {
        // Prevent instantiation of this utility class.
    }

    /**
     * Obtains message declarations, that match the specified {@link Predicate}.
     *
     * @param fileDescriptors the file descriptors to scan
     * @param predicate       the predicate to test a message
     * @return the message declarations
     */
    public static Collection<MessageDeclaration> find(Collection<FileDescriptorProto> fileDescriptors,
                                                      Predicate<DescriptorProto> predicate) {
        final List<MessageDeclaration> result = newLinkedList();
        for (FileDescriptorProto fileDescriptor : fileDescriptors) {
            final Collection<MessageDeclaration> declarationsFromFile = scanFile(fileDescriptor,
                                                                                 predicate);
            result.addAll(declarationsFromFile);
        }
        return result;
    }

    private static Collection<MessageDeclaration> scanFile(FileDescriptorProto fileDescriptor,
                                                           Predicate<DescriptorProto> predicate) {
        final List<MessageDeclaration> result = newLinkedList();
        for (DescriptorProto messageDescriptor : fileDescriptor.getMessageTypeList()) {
            final MessageDeclaration declaration = MessageDeclaration.create(messageDescriptor,
                                                                             fileDescriptor);
            if (predicate.apply(messageDescriptor)) {
                result.add(declaration);
            }
            final Collection<MessageDeclaration> nestedDeclarations =
                    scanNestedTypesRecursively(declaration, predicate);
            result.addAll(nestedDeclarations);
        }
        return result;
    }

    private static Collection<MessageDeclaration> scanNestedTypesRecursively(
            MessageDeclaration declaration, Predicate<DescriptorProto> predicate) {
        final List<DescriptorProto> nestedTypes = declaration.getDescriptor()
                                                             .getNestedTypeList();
        if (nestedTypes.isEmpty()) {
            return Collections.emptyList();
        }

        final List<MessageDeclaration> result = newLinkedList();
        for (DescriptorProto nestedType : nestedTypes) {
            final MessageDeclaration nestedTypeDeclaration = declaration.forNested(nestedType);
            if (predicate.apply(nestedType)) {
                result.add(nestedTypeDeclaration);
            }

            final Collection<MessageDeclaration> nestedDeclaration =
                    scanNestedTypesRecursively(nestedTypeDeclaration, predicate);
            result.addAll(nestedDeclaration);
        }
        return result;
    }
}

@@ -1267,27 +1267,21 @@
 va.util.
-Collections
+Deque
 ;%0Aimport
@@ -1935,16 +1935,29 @@
 n%3E find(
+%0A            
 Collecti
@@ -2000,62 +2000,8 @@
 ors,
-%0A                                                     
  Pre
@@ -3479,32 +3479,97 @@
 o%3E predicate) %7B%0A
+        final List%3CMessageDeclaration%3E result = newLinkedList();%0A
         final Li
@@ -3725,213 +3725,177 @@
     
-if (nestedTypes.isEmpty()) %7B%0A            return Collections.emptyList();%0A        %7D%0A%0A        final List%3CMessageDeclaration%3E result = newLinkedList();%0A        for (DescriptorProto nestedType : nestedTypes) %7B
+final Deque%3CDescriptorProto%3E deque = newLinkedList(nestedTypes);%0A%0A        while (!deque.isEmpty()) %7B%0A            final DescriptorProto nestedMessage = deque.pollFirst();
 %0A   
@@ -3930,28 +3930,24 @@
 ation nested
-Type
 Declaration 
@@ -3980,15 +3980,19 @@
 sted
-Typ
+Messag
 e);%0A
+%0A
     
@@ -4025,19 +4025,22 @@
 y(nested
-Typ
+Messag
 e)) %7B%0A  
@@ -4066,28 +4066,24 @@
 t.add(nested
-Type
 Declaration)
@@ -4115,190 +4115,54 @@
     
-final Collection%3CMessageDeclaration%3E nestedDeclaration =%0A                    scanNestedTypesRecursively(nestedTypeDeclaration, predicate);%0A            result.addAll(nestedDeclaration
+deque.addAll(nestedMessage.getNestedTypeList()
 );%0A 

Replace recursion in `MessageDeclarations`