/*
 * 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.compiler.gen.column;

import com.google.common.collect.ImmutableList;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import io.spine.base.EntityWithColumns;
import io.spine.code.java.ClassName;
import io.spine.code.java.PackageName;
import io.spine.code.proto.FieldDeclaration;
import io.spine.code.proto.FieldName;
import io.spine.tools.compiler.gen.GeneratedTypeSpec;
import io.spine.tools.compiler.gen.JavaPoetName;
import io.spine.type.MessageType;

import static io.spine.code.proto.ColumnOption.columnsOf;
import static io.spine.code.proto.ScalarType.isScalarType;
import static io.spine.code.proto.ScalarType.javaType;
import static io.spine.tools.compiler.annotation.Annotations.generatedBySpineModelCompiler;
import static java.lang.String.format;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.PUBLIC;

public final class EntityWithColumnsSpec implements GeneratedTypeSpec {

    private static final String NAME_FORMAT = "%sWithColumns";

    private final MessageType messageType;
    private final ImmutableList<FieldDeclaration> columns;

    public EntityWithColumnsSpec(MessageType messageType) {
        this.messageType = messageType;
        this.columns = columnsOf(messageType);
    }

    @Override
    public PackageName packageName() {
        return messageType.javaPackage();
    }

    @Override
    public TypeSpec typeSpec() {
        TypeSpec.Builder builder =
                TypeSpec.interfaceBuilder(className())
                        .addJavadoc(classJavadoc())
                        .addAnnotation(generatedBySpineModelCompiler())
                        .addModifiers(PUBLIC)
                        .addSuperinterface(EntityWithColumns.class);
        addColumnGetters(builder);
        return builder.build();
    }

    private void addColumnGetters(TypeSpec.Builder spec) {
        columns.forEach(column -> addColumnGetter(spec, column));
    }

    private static void addColumnGetter(TypeSpec.Builder spec, FieldDeclaration fieldDeclaration) {
        // TODO:2019-10-14:dmytro.kuzmin:WIP Add method doc.
        MethodSpec getter = toGetter(fieldDeclaration);
        spec.addMethod(getter);
    }

    private static MethodSpec toGetter(FieldDeclaration declaration) {
        FieldName fieldName = declaration.name();
        String methodName = "get" + fieldName.toCamelCase();
        JavaPoetName fieldTypeName = fieldType(declaration);
        TypeName returnType = fieldTypeName.value();
        MethodSpec result = MethodSpec.methodBuilder(methodName)
                                      .addModifiers(PUBLIC, ABSTRACT)
                                      .returns(returnType)
                                      .build();
        return result;
    }

    private static JavaPoetName fieldType(FieldDeclaration declaration) {
        if (isScalarType(declaration)) {
            return scalarTypeName(declaration);
        }
        return className(declaration);
    }

    private static JavaPoetName scalarTypeName(FieldDeclaration declaration) {
        FieldDescriptorProto.Type protoType = declaration.descriptor()
                                                         .toProto()
                                                         .getType();
        Class<?> scalarType = javaType(protoType);
        JavaPoetName result = JavaPoetName.of(scalarType);
        return result;
    }

    private static JavaPoetName className(FieldDeclaration declaration) {
        String javaTypeName = declaration.javaTypeName();
        ClassName className = ClassName.of(javaTypeName);
        JavaPoetName result = JavaPoetName.of(className);
        return result;
    }

    /**
     * A Javadoc content for the rejection.
     *
     * @return the class-level Javadoc content
     */
    private CodeBlock classJavadoc() {
        // TODO:2019-10-08:dmytro.kuzmin:WIP Improve class-level doc.
        CodeBlock value = CodeBlock
                .builder()
                .add("Entity Columns of proto type ")
                .add("{@code $L.$L}.", packageName(), messageType.simpleJavaClassName())
                .build();
        return value;
    }

    private String className() {
        String messageTypeName = messageType.javaClassName()
                                            .toSimple()
                                            .value();
        String result = format(NAME_FORMAT, messageTypeName);
        return result;
    }
}

@@ -3346,24 +3346,26 @@
 etter = 
-toG
+g
 etter
+Spec
 (fieldDe
@@ -3407,32 +3407,232 @@
 getter);%0A    %7D%0A%0A
+    /**%0A     * @implNote All interface methods in Java Poet have to be marked as %7B@code PUBLIC%7D and%0A     *         %7B@code ABSTRACT%7D, these modifiers are actually absent in the generated code.%0A     */%0A
     private stat
@@ -3649,16 +3649,18 @@
 pec 
-toG
+g
 etter
+Spec
 (Fie

Improve naming and doc