/*
 * Copyright 2020, 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.code.gen.java.field;

import io.spine.code.proto.FieldDeclaration;
import io.spine.type.MessageType;

import java.util.List;

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

/**
 * A recursive collector of {@link com.google.protobuf.Message Message}-typed fields from
 * a message.
 */
final class NestedFieldScanner {

    private final MessageType messageType;

    NestedFieldScanner(MessageType messageType) {
        this.messageType = messageType;
    }

    /**
     * Traverses all top-level and nested fields of the enclosed message type to retrieve those
     * that are singular {@link com.google.protobuf.Message Message}-typed fields.
     */
    List<MessageType> scan() {
        List<MessageType> result = newLinkedList();
        int index = -1;
        while (index < result.size()) {
            if (index == -1) {
                addMessageFields(result, this.messageType);
            } else {
                MessageType messageType = result.get(index);
                addMessageFields(result, messageType);
            }
            index++;
        }
        return result;
    }

    private static void addMessageFields(List<MessageType> result, MessageType messageType) {
        messageType.fields()
                   .stream()
                   .filter(FieldDeclaration::isSingularMessage)
                   .map(FieldDeclaration::messageType)
                   .filter(type -> !containsTypeWithSameName(result, type))
                   .forEach(result::add);
    }

    private static boolean containsTypeWithSameName(List<MessageType> result, MessageType type) {
        return result.stream()
                     .map(MessageType::simpleJavaClassName)
                     .anyMatch(name -> name.equals(type.simpleJavaClassName()));
    }
}

@@ -1045,16 +1045,64 @@
 field;%0A%0A
+import com.google.common.collect.ImmutableList;%0A
 import i
@@ -1196,16 +1196,111 @@
 l.List;%0A
+import java.util.Queue;%0A%0Aimport static com.google.common.collect.ImmutableList.toImmutableList;
 %0Aimport 
@@ -1899,16 +1899,21 @@
 %3E result
+Types
  = newLi
@@ -1936,141 +1936,304 @@
     
-int index = -1;%0A        while (index %3C result.size()) %7B%0A            if (index == -1) %7B%0A                addMessageFields(result, this.
+Queue%3CMessageType%3E typesToScan = newLinkedList(ImmutableList.of(messageType));%0A        while (!typesToScan.isEmpty()) %7B%0A            MessageType type = typesToScan.poll();%0A            ImmutableList%3CMessageType%3E messageTypes = uniqueMessageFields(type, resultTypes);%0A            resultTypes.addAll(
 mess
@@ -2231,32 +2231,33 @@
 dAll(messageType
+s
 );%0A            %7D
@@ -2259,16 +2259,41 @@
     
-%7D else %7B
+typesToScan.addAll(messageTypes);
 %0A   
@@ -2293,32 +2293,34 @@
 s);%0A        
+%7D%0A
         
 MessageType 
@@ -2311,250 +2311,604 @@
     
-MessageType messageType = result.get(index)
+return resultTypes
 ;%0A    
+%7D%0A%0A
     
-  
+/**%0A
      
- addMessageFields(result, messageType);%0A            %7D
+* Obtains %7B@code Message%7D-typed fields of a message with names that not yet occur among%0A     * the result types.
 %0A     
+*%0A
      
-  index++;%0A        %7D%0A        return result;%0A    %7D%0A%0A    private static void addMessageFields(List%3CMessageType%3E result, 
+* %3Cp%3EThe reason uniqueness-by-name is used is it being a requirement for the uniqueness of%0A     * the generated types in the field generation process.%0A     *%0A     * @see FieldsSpec%0A     * @see MessageTypedField%0A     */%0A    private static ImmutableList%3CMessageType%3E uniqueMessageFields(MessageType messageType,%0A                                                                  List%3CMessageType%3E resultTypes) %7B%0A        ImmutableList%3C
 Mess
@@ -2906,34 +2906,41 @@
 List%3CMessageType
- m
+%3E uniqueM
 essageType) %7B%0A  
@@ -2933,28 +2933,30 @@
 sageType
-) %7B%0A
+s =%0A  
         
 messageT
@@ -2947,16 +2947,22 @@
         
+      
 messageT
@@ -2993,16 +2993,24 @@
         
+        
 .stream(
@@ -3022,32 +3022,40 @@
                 
+  
+      
 .filter(FieldDec
@@ -3098,24 +3098,32 @@
             
+        
 .map(FieldDe
@@ -3157,32 +3157,40 @@
                 
+ 
+       
 .filter(type -%3E 
@@ -3221,16 +3221,21 @@
 e(result
+Types
 , type))
@@ -3258,29 +3258,78 @@
     
+    
-.forEach(result::add)
+    .collect(toImmutableList());%0A        return uniqueMessageTypes
 ;%0A  
@@ -3435,30 +3435,42 @@
 ) %7B%0A        
-return
+boolean contains =
  result.stre
@@ -3466,32 +3466,44 @@
 result.stream()%0A
+            
                 
@@ -3567,16 +3567,28 @@
         
+       
+     
 .anyMatc
@@ -3635,20 +3635,45 @@
 ssName()));%0A
+        return contains;%0A
     %7D%0A%7D%0A

Reorganize the recursive field scan to get rid of convoluted index logic