/*
 * Copyright (c) 2017-2021 Arm Limited.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#ifndef ARM_COMPUTE_MISC_UTILITY_H
#define ARM_COMPUTE_MISC_UTILITY_H

#include "arm_compute/core/Error.h"

#include <algorithm>
#include <array>
#include <limits>
#include <numeric>
#include <vector>

namespace arm_compute
{
namespace utility
{
/** @cond */
template <std::size_t...>
struct index_sequence
{
};

template <std::size_t N, std::size_t... S>
struct index_sequence_generator : index_sequence_generator < N - 1, N - 1, S... >
{
};

template <std::size_t... S>
struct index_sequence_generator<0u, S...> : index_sequence<S...>
{
    using type = index_sequence<S...>;
};

template <std::size_t N>
using index_sequence_t = typename index_sequence_generator<N>::type;

template <typename T, std::size_t N, T val, T... vals>
struct generate_array : generate_array < T, N - 1, val, val, vals... >
{
};

template <typename T, T val, T... vals>
struct generate_array<T, 0, val, vals...>
{
    static constexpr std::array<T, sizeof...(vals)> value{ vals... };
};

template <typename T, T                  val, T... vals>
constexpr std::array<T, sizeof...(vals)> generate_array<T, 0, val, vals...>::value;
/** @endcond */

namespace detail
{
template <std::size_t... S,
          typename Iterator,
          typename T = std::array<typename std::iterator_traits<Iterator>::value_type, sizeof...(S)>>
T make_array(Iterator first, index_sequence<S...>)
{
    return T{ { first[S]... } };
}
} // namespace detail

template <std::size_t N, typename Iterator>
std::array<typename std::iterator_traits<Iterator>::value_type, N> make_array(Iterator first, Iterator last)
{
    ARM_COMPUTE_UNUSED(last);
    return detail::make_array(first, index_sequence_t<N> {});
}

/** Performs clamping among a lower and upper value.
 *
 * @param[in] n     Value to clamp.
 * @param[in] lower Lower threshold.
 * @param[in] upper Upper threshold.
 *
 *  @return Clamped value.
 */
template <typename DataType, typename RangeType = DataType>
inline DataType clamp(const DataType &n,
                      const DataType &lower = std::numeric_limits<RangeType>::lowest(),
                      const DataType &upper = std::numeric_limits<RangeType>::max())
{
    return std::max(lower, std::min(n, upper));
}

/** Base case of for_each. Does nothing. */
template <typename F>
inline void for_each(F &&)
{
}

/** Call the function for each of the arguments
 *
 * @param[in] func Function to be called
 * @param[in] arg  Argument passed to the function
 * @param[in] args Remaining arguments
 */
template <typename F, typename T, typename... Ts>
inline void for_each(F &&func, T &&arg, Ts &&... args)
{
    func(std::forward<T>(arg));
    for_each(std::forward<F>(func), std::forward<Ts>(args)...);
}

/** Base case of foldl.
 *
 * @return value.
 */
template <typename F, typename T>
inline T &&foldl(F &&, T &&value)
{
    return std::forward<T>(value);
}

/** Fold left.
 *
 * @param[in] func    Function to be called
 * @param[in] initial Initial value
 * @param[in] value   Argument passed to the function
 * @param[in] values  Remaining arguments
 */
template <typename F, typename T, typename U, typename... Us>
inline auto foldl(F &&func, T &&initial, U &&value, Us &&... values) -> decltype(func(std::forward<T>(initial), std::forward<U>(value)))
{
    return foldl(std::forward<F>(func), func(std::forward<T>(initial), std::forward<U>(value)), std::forward<Us>(values)...);
}

/** Perform an index sort of a given vector.
 *
 * @param[in] v Vector to sort
 *
 * @return Sorted index vector.
 */
template <typename T>
std::vector<size_t> sort_indices(const std::vector<T> &v)
{
    std::vector<size_t> idx(v.size());
    std::iota(idx.begin(), idx.end(), 0);

    std::sort(idx.begin(), idx.end(),
              [&v](size_t i1, size_t i2)
    {
        return v[i1] < v[i2];
    });

    return idx;
}

/** Checks if a string contains a given suffix
 *
 * @param[in] str    Input string
 * @param[in] suffix Suffix to check for
 *
 * @return True if the string ends with the given suffix else false
 */
inline bool endswith(const std::string &str, const std::string &suffix)
{
    if(str.size() < suffix.size())
    {
        return false;
    }
    return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
}

/** Checks if a pointer complies with a given alignment
 *
 * @param[in] ptr       Pointer to check
 * @param[in] alignment Alignment value
 *
 * @return True if the pointer is aligned else false
 */
inline bool check_aligned(void *ptr, const size_t alignment)
{
    return (reinterpret_cast<std::uintptr_t>(ptr) % alignment) == 0;
}

/** Convert string to lower case.
 *
 * @param[in] string To be converted string.
 *
 * @return Lower case string.
 */
inline std::string tolower(std::string string)
{
    std::transform(string.begin(), string.end(), string.begin(), [](unsigned char c)
    {
        return std::tolower(c);
    });
    return string;
}

/** Get environment variable as a string
 *
 * @note Return empty string on bare-metal
 *
 * @param[in] env_name Name of the Environment variable to retrieve
 *
 * @return Environment variable content, or empty string if the variable is undefined or on bare-metal
 */
inline std::string getenv(const std::string &env_name)
{
#ifdef BARE_METAL
    ARM_COMPUTE_UNUSED(env_name);
    return std::string{};
#else  // BARE_METAL
    const auto env_chr = std::getenv(env_name.c_str());
    return env_chr == nullptr ? std::string{} : std::string{ env_chr };
#endif // BARE_METAL
}
} // namespace utility
} // namespace arm_compute
#endif /* ARM_COMPUTE_MISC_UTILITY_H */
