#pragma once

#include <cstddef>
#include <utility>

namespace maps::wiki::common {

template <typename T, typename = void>
struct HasReserve : public std::false_type
{
};

template <typename T>
struct HasReserve<T, decltype(std::declval<T>().reserve(0))> : public std::true_type
{
};

/**
 * Split the container into chunks of given size and apply the function to check the chunks.
 *
 * Example usage:
 * \code
 * std::vector<int> data = ...
 * const size_t BATCH_SIZE = ...
 * common::applyBatchFindOp<std::vector<int>>(
 *     data,
 *     BATCH_SIZE,
 *     [](const std::vector<int>& batch) {
 *         // do something
 *         return true;
 *     });
 * \endcode
 */
template<typename Container, typename Func>
bool applyBatchFindOp(
    const Container& data,
    size_t bulkCount,
    Func findOp)
{
    if (data.empty()) {
        return false;
    }

    if (data.size() <= bulkCount) {
        return findOp(data);
    }

    Container batch;

    if constexpr (HasReserve<Container>::value) {
        batch.reserve(bulkCount);
    }

    for (const auto& element : data) {
        batch.insert(batch.end(), element);

        if (batch.size() >= bulkCount) {
            if (findOp(batch)) {
                return true;
            }
            batch.clear();
        }
    }

    if (!batch.empty() && findOp(batch)) {
        return true;
    }

    return false;
}

/**
 * Split the container into chunks of given size and apply the function to every chunk.
 *
 * Example usage:
 * \code
 * std::vector<int> data = ...
 * const size_t BATCH_SIZE = ...
 * common::applyBatchOp<std::vector<int>>(
 *     data,
 *     BATCH_SIZE,
 *     [](const std::vector<int>& batch) {
 *         // do something
 *     });
 * \endcode
 */
template<typename Container, typename Func>
void applyBatchOp(
    const Container& data,
    size_t bulkCount,
    Func batchOp)
{
    applyBatchFindOp<Container>(
        data,
        bulkCount,
        [&batchOp](const Container& batch) -> bool {
            batchOp(batch);
            return false;
        });
}

} // namespace maps::wiki::common
