package org.vaadin.artur.spring.dataprovider;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.ToLongFunction;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;

import com.vaadin.flow.data.provider.ConfigurableFilterDataProvider;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.data.provider.QuerySortOrder;
import com.vaadin.flow.data.provider.SortDirection;

public class SpringDataProviderBuilder<T, F> {
    private final BiFunction<Pageable, F, Page<T>> queryFunction;
    private final ToLongFunction<F> lengthFunction;
    private final List<QuerySortOrder> defaultSortOrders = new ArrayList<>();

    private F defaultFilter = null;

    public SpringDataProviderBuilder(
            BiFunction<Pageable, F, Page<T>> queryFunction,
            ToLongFunction<F> lengthFunction) {
        this.queryFunction = queryFunction;
        this.lengthFunction = lengthFunction;
    }

    public SpringDataProviderBuilder<T, F> withDefaultSort(String column,
            SortDirection direction) {
        defaultSortOrders.add(new QuerySortOrder(column, direction));
        return this;
    }

    public SpringDataProviderBuilder<T, F> withDefaultFilter(F defaultFilter) {
        this.defaultFilter = defaultFilter;
        return this;
    }

    public DataProvider<T, F> build() {
        return new PageableDataProvider<T, F>() {
            @Override
            protected Page<T> fetchFromBackEnd(Query<T, F> query,
                    Pageable pageable) {
                return queryFunction.apply(pageable,
                        query.getFilter().orElse(defaultFilter));
            }

            @Override
            protected List<QuerySortOrder> getDefaultSortOrders() {
                return defaultSortOrders;
            }

            @Override
            protected int sizeInBackEnd(Query<T, F> query) {
                return (int) lengthFunction
                        .applyAsLong(query.getFilter().orElse(defaultFilter));
            }
        };
    }

    public ConfigurableFilterDataProvider<T, Void, F> buildFilterable() {
        return build().withConfigurableFilter();
    }

    public static <T> SpringDataProviderBuilder<T, Void> forRepository(
            PagingAndSortingRepository<T, ?> repository) {
        return new SpringDataProviderBuilder<>(
                (pageable, filter) -> repository.findAll(pageable),
                filter -> repository.count());
    }

    public static <T, F> SpringDataProviderBuilder<T, F> forFunctions(
            BiFunction<Pageable, F, Page<T>> queryFunction,
            ToLongFunction<F> lengthFunction) {
        return new SpringDataProviderBuilder<>(queryFunction, lengthFunction);
    }
}