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

import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Message;
import io.spine.base.EntityState;
import io.spine.base.Field;
import io.spine.base.FieldPath;
import io.spine.type.TypeUrl;

import static io.spine.util.Exceptions.newIllegalArgumentException;

public interface FilterMixin {

    @SuppressWarnings("override") // Implemented in the generated code.
    FieldPath getFieldPath();

    default Field field() {
        FieldPath fieldPath = getFieldPath();
        return Field.withPath(fieldPath);
    }

    default boolean fieldPresentIn(Descriptor message) {
        Field field = field();
        boolean result = field.presentIn(message);
        return result;
    }

    default void checkFieldPresentIn(Descriptor message) {
        if (!fieldPresentIn(message)) {
            throw newIllegalArgumentException(
                    "The field with path `%s` is not present in message type `%s`.",
                    field(), message.getFullName());
        }
    }

    default boolean fieldIsTopLevel() {
        return !field().isNested();
    }

    default void checkFieldIsTopLevel() {
        if (!fieldIsTopLevel()) {
            throw newIllegalArgumentException(
                    "The entity filter contains a nested entity column `%s`. " +
                            "The nested entity columns are currently not supported.",
                    field()
            );
        }
    }

    default void validateAgainst(TypeUrl targetType) {
        Class<Message> javaClass = targetType.getMessageClass();
        if (EntityState.class.isAssignableFrom(javaClass)) {
            checkFieldIsTopLevel();
        }
        Descriptor descriptor = targetType.toTypeName()
                                          .messageDescriptor();
        checkFieldPresentIn(descriptor);
    }
}

@@ -1110,23 +1110,122 @@
 rotobuf.
-Message
+Descriptors.FieldDescriptor;%0Aimport com.google.protobuf.Message;%0Aimport io.spine.annotation.GeneratedMixin
 ;%0Aimport
@@ -1312,16 +1312,102 @@
 ldPath;%0A
+import io.spine.code.proto.ColumnOption;%0Aimport io.spine.code.proto.FieldDeclaration;%0A
 import i
@@ -1429,16 +1429,44 @@
 peUrl;%0A%0A
+import java.util.Optional;%0A%0A
 import s
@@ -1526,16 +1526,32 @@
 ption;%0A%0A
+@GeneratedMixin%0A
 public i
@@ -2262,32 +2262,834 @@
        %7D%0A    %7D%0A%0A
+    default boolean fieldIsColumnIn(Descriptor message) %7B%0A        Optional%3CFieldDescriptor%3E fieldDescriptor = field().findDescriptor(message);%0A        if (!fieldDescriptor.isPresent()) %7B%0A            return false;%0A        %7D%0A        FieldDeclaration declaration = new FieldDeclaration(fieldDescriptor.get());%0A        boolean result = ColumnOption.isColumn(declaration);%0A        return result;%0A    %7D%0A%0A    default void checkFieldIsColumnIn(Descriptor message) %7B%0A        if (!fieldIsColumnIn(message)) %7B%0A            throw newIllegalArgumentException(%0A                    %22The entity column %60%25s%60 is not found in entity state type %60%25s%60. %22 +%0A                            %22Check the proto field exists and is marked with %60(column)%60 option.%22,%0A                    field(), message.getFullName());%0A        %7D%0A    %7D%0A%0A
     default bool
@@ -3621,24 +3621,144 @@
 ageClass();%0A
+        Descriptor descriptor = targetType.toTypeName()%0A                                          .messageDescriptor();%0A
         if (
@@ -3854,130 +3854,67 @@
     
-%7D%0A
     
-    Descriptor descriptor = targetType.toTypeName()%0A                                          .messageDescriptor();%0A
+checkFieldIsColumnIn(descriptor);%0A        %7D else %7B%0A    
     
@@ -3946,20 +3946,30 @@
 escriptor);%0A
+        %7D%0A
     %7D%0A%7D%0A

Add check for the field being an entity column in entity state filter