package org.springframework.data.jpa.repository.query;

import org.springframework.data.repository.query.EvaluationContextProvider;
import org.springframework.data.util.StreamUtils;
import org.springframework.expression.ExpressionParser;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * .
 *
 * @author stormning 2018/5/21
 * @since 1.3.0
 */
public class TemplateQueryParameterBinderFactory {

    public static ParameterBinder createQueryAwareBinder(JpaParameters parameters, DeclaredQuery query,
                                                         ExpressionParser parser, EvaluationContextProvider evaluationContextProvider) {

        Assert.notNull(parameters, "JpaParameters must not be null!");
        Assert.notNull(query, "StringQuery must not be null!");
        Assert.notNull(parser, "ExpressionParser must not be null!");
        Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");

        List<StringQuery.ParameterBinding> bindings = query.getParameterBindings();
        QueryParameterSetterFactory expressionSetterFactory = TemplateBasedQueryParameterSetterFactory.parsingTemplate(parser,
                evaluationContextProvider, parameters);
        QueryParameterSetterFactory basicSetterFactory = TemplateBasedQueryParameterSetterFactory.basic(parameters);

        return new ParameterBinder(parameters, createSetters(bindings, query, expressionSetterFactory, basicSetterFactory),
                !query.usesPaging());
    }


    private static Iterable<QueryParameterSetter> createSetters(List<StringQuery.ParameterBinding> parameterBindings,
                                                                QueryParameterSetterFactory... factories) {
        return createSetters(parameterBindings, EmptyDeclaredQuery.EMPTY_QUERY, factories);
    }

    private static Iterable<QueryParameterSetter> createSetters(List<StringQuery.ParameterBinding> parameterBindings,
                                                                DeclaredQuery declaredQuery, QueryParameterSetterFactory... strategies) {

        return parameterBindings.stream() //
                .map(it -> createQueryParameterSetter(it, strategies, declaredQuery)) //
                .collect(StreamUtils.toUnmodifiableList());
    }

    private static QueryParameterSetter createQueryParameterSetter(StringQuery.ParameterBinding binding,
                                                                   QueryParameterSetterFactory[] strategies, DeclaredQuery declaredQuery) {

        return Arrays.stream(strategies)//
                .map(it -> it.create(binding, declaredQuery))//
                .filter(Objects::nonNull)//
                .findFirst()//
                .orElse(QueryParameterSetter.NOOP);
    }
}