/*
 * 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.server.entity.storage;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.Message;
import io.spine.code.java.PackageName;
import io.spine.code.proto.ColumnOption;
import io.spine.code.proto.FieldDeclaration;
import io.spine.server.entity.model.EntityClass;
import io.spine.type.MessageType;

import java.lang.reflect.Method;

import static io.spine.reflect.Types.allImplementedInterfaces;
import static io.spine.util.Exceptions.newIllegalStateException;
import static java.lang.String.format;

final class ColumnIntrospector {

    private static final String ENTITY_WITH_COLUMNS_NAME_PATTERN = "%sWithColumns";

    private final EntityClass<?> entityClass;

    private ColumnIntrospector(EntityClass<?> aClass) {
        entityClass = aClass;
    }

    static ImmutableMap<String, Column> columnsOf(EntityClass<?> entityClass) {
        ColumnIntrospector introspector = new ColumnIntrospector(entityClass);
        ImmutableMap<String, Column> columns = introspector.columns();
        return columns;
    }

    private ImmutableMap<String, Column> columns() {
        ImmutableMap.Builder<String, Column> columns = ImmutableMap.builder();
        MessageType stateType = entityClass.stateType();
        ImmutableList<FieldDeclaration> columnFields = ColumnOption.columnsOf(stateType);
        columnFields.forEach(field -> addToMap(field, columns, entityClass));
        ImmutableMap<String, Column> result = columns.build();
        return result;
    }

    private static void addToMap(FieldDeclaration field,
                                 ImmutableMap.Builder<String, Column> columns,
                                 EntityClass<?> entityClass) {
        String columnName = field.name()
                                 .value();
        Class<?> theEntityClass = entityClass.value();
        ImmutableSet<Class<?>> interfaces = allImplementedInterfaces(theEntityClass);
        Class<? extends Message> stateClass = entityClass.stateClass();
        String interfaceName =
                format(ENTITY_WITH_COLUMNS_NAME_PATTERN, stateClass.getSimpleName());

        // The generated `...WithColumns` interface will have the same package as the state type.
        PackageName javaPackage = entityClass.stateType()
                                             .javaPackage();
        String fullyQualifiedName = format("%s.%s", javaPackage, interfaceName);

        @SuppressWarnings("LocalVariableNamingConvention")
        boolean implementsEntityWithColumns = interfaces
                .stream()
                .anyMatch(clazz -> fullyQualifiedName.equals(clazz.getSimpleName()));
        String getterName = getterName(field);
        try {
            Method method = implementsEntityWithColumns
                            ? theEntityClass.getMethod(getterName)
                            : stateClass.getMethod(getterName);
            Class<?> columnType = method.getReturnType();
            Column.Getter columnGetter = entity -> method.invoke(entity);
            Column column = new Column(columnName, columnType, columnGetter);
            columns.put(columnName, column);
        } catch (NoSuchMethodException e) {
            throw newIllegalStateException(e,
                                           "Getter with name %s not found in entity class %s.",
                                           getterName, entityClass.typeName());
        }
    }

    private static String getterName(FieldDeclaration field) {
        String fieldNameCamelCase = field.name()
                                         .toCamelCase();
        return format("get%s", fieldNameCamelCase);
    }
}

@@ -1146,55 +1146,8 @@
 ap;%0A
-import com.google.common.collect.ImmutableSet;%0A
 impo
@@ -1198,29 +1198,30 @@
 ine.
-code.java.PackageName
+base.EntityWithColumns
 ;%0Aim
@@ -1426,71 +1426,8 @@
 d;%0A%0A
-import static io.spine.reflect.Types.allImplementedInterfaces;%0A
 impo
@@ -1565,93 +1565,8 @@
  %7B%0A%0A
-    private static final String ENTITY_WITH_COLUMNS_NAME_PATTERN = %22%25sWithColumns%22;%0A%0A
     
@@ -2762,571 +2762,69 @@
     
-ImmutableSet%3CClass%3C?%3E%3E interfaces = allImplementedInterfaces(theEntityClass);%0A        Class%3C? extends Message%3E stateClass = entityClass.stateClass();%0A        String interfaceName =%0A                format(ENTITY_WITH_COLUMNS_NAME_PATTERN, stateClass.getSimpleName());%0A%0A        // The generated %60...WithColumns%60 interface will have the same package as the state type.%0A        PackageName javaPackage = entityClass.stateType()%0A                                             .javaPackage();%0A        String fullyQualifiedName = format(%22%25s.%25s%22, javaPackage, interfaceName
+Class%3C? extends Message%3E stateClass = entityClass.stateClass(
 );%0A%0A
@@ -2931,19 +2931,8 @@
 ns =
- interfaces
 %0A   
@@ -2948,101 +2948,63 @@
     
-.stream()%0A                .anyMatch(clazz -%3E fullyQualifiedName.equals(clazz.getSimpleName())
+EntityWithColumns.class.isAssignableFrom(theEntityClass
 );%0A 

Improve the `ColumnIntrospector` check