/*
 * Copyright 2000-2023 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.flow.data.provider;

import java.io.Serializable;
import java.util.Objects;
import java.util.stream.Stream;

import com.vaadin.flow.function.ValueProvider;

/**
 * Data provider that uses one callback for fetching items from a back end and
 * another callback for counting the number of available items.
 *
 * @author Vaadin Ltd
 * @since 1.0
 *
 * @param <T>
 *            data provider data type
 * @param <F>
 *            data provider filter type
 */
public class CallbackDataProvider<T, F>
        extends AbstractBackEndDataProvider<T, F> {
    /**
     * Callback interface for fetching a stream of items from a backend based on
     * a query.
     *
     * @param <T>
     *            the type of the items to fetch
     * @param <F>
     *            the type of the optional filter in the query,
     *            <code>Void</code> if filtering is not supported
     */
    @FunctionalInterface
    public interface FetchCallback<T, F> extends Serializable {
        /**
         * Fetches a stream of items based on a query. The query defines the
         * paging of the items to fetch through {@link Query#getOffset()} and
         * {@link Query#getLimit()}, the sorting through
         * {@link Query#getSortOrders()} and optionally also any filtering to
         * use through {@link Query#getFilter()}.
         *
         * @param query
         *            the query that defines which items to fetch
         * @return a stream of items
         */
        Stream<T> fetch(Query<T, F> query);
    }

    /**
     * Callback interface for counting the number of items in a backend based on
     * a query.
     *
     * @param <T>
     *            the type of the items to count
     * @param <F>
     *            the type of the optional filter in the query,
     *            <code>Void</code> if filtering is not supported
     */
    @FunctionalInterface
    public interface CountCallback<T, F> extends Serializable {
        /**
         * Counts the number of available items based on a query. The query
         * optionally defines any filtering to use through
         * {@link Query#getFilter()}. The query also contains information about
         * paging and sorting although that information is generally not
         * applicable for determining the number of items.
         *
         * @param query
         *            the query that defines which items to count
         * @return the number of available items
         */
        int count(Query<T, F> query);
    }

    private final FetchCallback<T, F> fetchCallback;
    private final CountCallback<T, F> countCallback;
    private final ValueProvider<T, Object> idGetter;

    /**
     * Constructs a new DataProvider to request data using callbacks for
     * fetching and counting items in the back end.
     *
     * @param fetchCallback
     *            function that returns a stream of items from the back end for
     *            a query
     * @param countCallback
     *            function that return the number of items in the back end for a
     *            query
     *
     * @see #CallbackDataProvider(FetchCallback, CountCallback, ValueProvider)
     */
    public CallbackDataProvider(FetchCallback<T, F> fetchCallback,
            CountCallback<T, F> countCallback) {
        this(fetchCallback, countCallback, t -> t);
    }

    /**
     * Constructs a new DataProvider to request data using callbacks for
     * fetching and counting items in the back end.
     *
     * @param fetchCallBack
     *            function that requests data from back end based on query
     * @param countCallback
     *            function that returns the amount of data in back end for query
     * @param identifierGetter
     *            function that returns the identifier for a given item
     */
    public CallbackDataProvider(FetchCallback<T, F> fetchCallBack,
            CountCallback<T, F> countCallback,
            ValueProvider<T, Object> identifierGetter) {
        Objects.requireNonNull(fetchCallBack, "Request function can't be null");
        Objects.requireNonNull(countCallback, "Count callback can't be null");
        Objects.requireNonNull(identifierGetter,
                "Identifier getter function can't be null");
        this.fetchCallback = fetchCallBack;
        this.countCallback = countCallback;
        this.idGetter = identifierGetter;
    }

    @Override
    public Stream<T> fetchFromBackEnd(Query<T, F> query) {
        return fetchCallback.fetch(query);
    }

    @Override
    protected int sizeInBackEnd(Query<T, F> query) {
        return countCallback.count(query);
    }

    @Override
    public Object getId(T item) {
        Object itemId = idGetter.apply(item);
        assert itemId != null
                : "CallbackDataProvider got null as an id for item: " + item;
        return itemId;
    }
}
