/*
 * 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 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;
    }

    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);
    }
}

@@ -3017,32 +3017,420 @@
 build();%0A    %7D%0A%0A
+    private void addColumnGetters(TypeSpec.Builder spec) %7B%0A        columns.forEach(column -%3E addColumnGetter(spec, column));%0A    %7D%0A%0A    private static void addColumnGetter(TypeSpec.Builder spec, FieldDeclaration fieldDeclaration) %7B%0A        // TODO:2019-10-14:dmytro.kuzmin:WIP Add method doc.%0A        MethodSpec getter = toGetter(fieldDeclaration);%0A        spec.addMethod(getter);%0A    %7D%0A%0A
     private stat
@@ -5687,395 +5687,7 @@
    %7D
-%0A%0A    private void addColumnGetters(TypeSpec.Builder spec) %7B%0A        columns.forEach(column -%3E addColumnGetter(spec, column));%0A    %7D%0A%0A    private static void addColumnGetter(TypeSpec.Builder spec, FieldDeclaration fieldDeclaration) %7B%0A        // TODO:2019-10-14:dmytro.kuzmin:WIP Add method doc.%0A        MethodSpec getter = toGetter(fieldDeclaration);%0A        spec.addMethod(getter);%0A    %7D
 %0A%7D%0A

Rearrange methods