package com.helospark.spark.builder.handlers.codegenerator.builderfieldcollector;

import static com.helospark.spark.builder.preferences.PluginPreferenceList.INCLUDE_SETTER_FIELDS_FROM_SUPERCLASS;
import static java.util.Collections.emptyList;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import com.helospark.spark.builder.handlers.codegenerator.component.helper.CamelCaseConverter;
import com.helospark.spark.builder.handlers.codegenerator.component.helper.TypeDeclarationFromSuperclassExtractor;
import com.helospark.spark.builder.handlers.codegenerator.domain.BuilderField;
import com.helospark.spark.builder.handlers.codegenerator.domain.SuperSetterBasedBuilderField;
import com.helospark.spark.builder.preferences.PreferencesManager;

/**
 * Collects fields with setters from superclass. 
 * @author helospark
 */
public class SuperClassSetterFieldCollector implements FieldCollectorChainItem {
    private static final String SETTER_METHOD_PREFIX = "set";

    private PreferencesManager preferencesManager;
    private TypeDeclarationFromSuperclassExtractor typeDeclarationFromSuperclassExtractor;
    private CamelCaseConverter camelCaseConverter;

    public SuperClassSetterFieldCollector(PreferencesManager preferencesManager, TypeDeclarationFromSuperclassExtractor typeDeclarationFromSuperclassExtractor,
            CamelCaseConverter camelCaseConverter) {
        this.preferencesManager = preferencesManager;
        this.typeDeclarationFromSuperclassExtractor = typeDeclarationFromSuperclassExtractor;
        this.camelCaseConverter = camelCaseConverter;
    }

    @Override
    public List<? extends BuilderField> collect(TypeDeclaration typeDeclaration) {
        if (preferencesManager.getPreferenceValue(INCLUDE_SETTER_FIELDS_FROM_SUPERCLASS)) {
            List<SuperSetterBasedBuilderField> foundFields = collectFieldsRecursively(typeDeclaration);
            return deduplicateByName(foundFields);
        } else {
            return emptyList();
        }
    }

    private List<SuperSetterBasedBuilderField> collectFieldsRecursively(TypeDeclaration typeDeclaration) {
        List<SuperSetterBasedBuilderField> result = new ArrayList<>();
        Optional<TypeDeclaration> superClassType = typeDeclarationFromSuperclassExtractor.extractTypeDeclarationFromSuperClass(typeDeclaration);

        superClassType.ifPresent(type -> {
            result.addAll(findParametersWithSettersInType(type));
            result.addAll(collectFieldsRecursively(type));
        });

        return result;
    }

    private List<SuperSetterBasedBuilderField> findParametersWithSettersInType(TypeDeclaration parentTypeDeclaration) {
        return ((List<BodyDeclaration>) parentTypeDeclaration.bodyDeclarations())
                .stream()
                .filter(declaration -> isMethod(declaration))
                .map(declaration -> (MethodDeclaration) declaration)
                .filter(method -> isSetter(method))
                .map(method -> createBuilderField(method))
                .collect(Collectors.toList());
    }

    private boolean isMethod(BodyDeclaration declaration) {
        return declaration instanceof MethodDeclaration;
    }

    private boolean isSetter(MethodDeclaration method) {
        String methodName = method.getName().toString();
        return method.parameters().size() == 1 && methodName.startsWith(SETTER_METHOD_PREFIX);
    }

    private SuperSetterBasedBuilderField createBuilderField(MethodDeclaration method) {
        String methodName = method.getName().toString();
        String upperCamelCaseFieldName = methodName.replaceFirst(SETTER_METHOD_PREFIX, "");
        String fieldName = camelCaseConverter.toLowerCamelCase(upperCamelCaseFieldName);

        SingleVariableDeclaration parameter = (SingleVariableDeclaration) method.parameters().get(0);

        return SuperSetterBasedBuilderField.builder()
                .withBuilderFieldName(fieldName)
                .withOriginalFieldName(fieldName)
                .withFieldType(parameter.getType())
                .withSetterName(methodName)
                .build();
    }

    // deduplication is required for overridden setters
    private List<SuperSetterBasedBuilderField> deduplicateByName(List<SuperSetterBasedBuilderField> fields) {
        List<SuperSetterBasedBuilderField> result = new ArrayList<>();
        for (SuperSetterBasedBuilderField field : fields) {
            if (!alreadyContainsField(result, field)) {
                result.add(field);
            }
        }
        return result;
    }

    private boolean alreadyContainsField(List<SuperSetterBasedBuilderField> result, SuperSetterBasedBuilderField field) {
        return result.stream()
                .filter(element -> element.getBuilderFieldName().equals(field.getBuilderFieldName()))
                .findFirst()
                .isPresent();
    }
}