/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright:
 *      2018, Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "../types/traits.hpp"

#include <iterator>
#include <functional>


namespace cruft::search {
    /// Performs a linear search to discover the iterator corresponding to
    /// the value which minimises a function.
    ///
    /// Useful as a mechanism to avoid possibly expensive comparison
    /// calculations; eg recomputing path distances when testing a series of
    /// possible routes.
    ///
    /// The provided function will be invoked with the supplied argument pack
    /// as the first arguments and the dereferenced iterator as the final
    /// argument.
    ///
    /// \tparam ForwardT   A forward iterator
    /// \tparam FunctionT  An invokable
    /// \param first  The first iterator in the range to be searched
    /// \param last   The last iterator in the range to be searched
    /// \param func   A functional which returns a comparable value
    /// \param args   The first arguments passed to `func`
    /// \return  A pair of the best score, and the best iterator
    template <
        typename ForwardT,
        typename FunctionT,
        typename ...Args
    >
    auto
    minimises (
        ForwardT first,
        ForwardT last,
        FunctionT &&func,
        Args &&...args
    ) {
        using value_type = typename std::iterator_traits<ForwardT>::value_type;
        using score_t = std::invoke_result_t<FunctionT, Args..., value_type>;
        static_assert (::cruft::is_orderable_v<score_t>);

        auto best_score = std::invoke (func, args..., *first);
        auto best_item = first;

        for (++first; first != last; ++first) {
            auto this_score = std::invoke (func, args..., *first);
            if (this_score < best_score) {
                best_score = this_score;
                best_item = first;
            }
        }

        return std::pair (std::move (best_score), std::move (best_item));
    }
}