/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef DEF_FORMAT
#define DEF_FORMAT

#include <sstream>
#include <vector>
#include <string>
#include <cstddef>

namespace Format
{
    /// An optional placeholder identifier associated with
    /// the value that should replace it
    using NamedValue = std::pair<std::string, std::string>;

    namespace
    {
        /// Convert arbitrarily typed arguments to std::string if possible
        /// and adds it to a resulting std::vector<NamedValue>:

        /// Tail of the variadic template
        template <typename...>
        void addToVect(std::vector<NamedValue> &/*target*/) {}

        /// General case for unnamed placeholder identifiers
        template <typename T, typename... Ts>
        void addToVect(std::vector<NamedValue> &target, T&& head, Ts&&... tail)
        {
            std::stringstream sstream;
            sstream << head;
            target.emplace_back(std::string(), sstream.str());

            addToVect(target, std::forward<Ts>(tail)...);
        }

        /// std::pair case for named placeholder identifiers
        template <typename T, typename... Ts>
        void addToVect(std::vector<NamedValue> &target,
                       std::pair<const char*, T>&& head, Ts&&... tail)
        {
            std::stringstream sstream;
            sstream << head.second;
            target.emplace_back(head.first, sstream.str());

            addToVect(target, std::forward<Ts>(tail)...);
        }
    }

    /// Parse the variadic arguments into a set of named values for formatting
    template <typename... Ts>
    inline std::vector<NamedValue> named_values(Ts&&... args)
    {
        std::vector<NamedValue> strArgs;
        strArgs.reserve(sizeof...(Ts));
        addToVect(strArgs, std::forward<Ts>(args)...);
        return strArgs;
    }

    /// Formats a \p sentence based on a set of named values \p strArgs
    inline std::string apply_format(const std::string &sentence,
                                    const std::vector<NamedValue> &strArgs)
    {
        std::string formatted(sentence);

        std::size_t argsIx = 0;
        while (argsIx < strArgs.size())
        {
            const std::string placeholder = '{' + strArgs[argsIx].first + '}';
            const std::size_t findIndex = formatted.find(placeholder);

            if (findIndex != std::string::npos)
                formatted.replace(findIndex,
                                  placeholder.length(),
                                  strArgs[argsIx].second);
            argsIx++;
        }

        return formatted;
    }

    /// Utility to directly format a string \p sentence with variadic arguments
    template <typename... Ts>
    inline std::string format(const std::string &sentence, Ts&&... args)
    {
        return apply_format(sentence, named_values(std::forward<Ts>(args)...));
    }

    /// Pads \p str by appending space characters at its end until it reaches
    /// the requested size \p length, does nothing is length <= str.size()
    inline std::string pad(const std::string &str, std::size_t length)
    {
        return (str.length() >= length) ? str :
            str + std::string(length - str.length(), ' ');
    }

    /// Formats the description of a command line option in a --help text
    /// Returns \p label, padded to reach \p labelLength,
    /// followed by the description
    inline std::string column(const std::string &label,
                              const std::string &description,
                              std::size_t labelLength)
    {
        return pad(label, labelLength) + description;
    }
}

#endif
