/*!
@file
Defines `boost::hana::string`.

@copyright Louis Dionne 2013-2017
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_STRING_HPP
#define BOOST_HANA_STRING_HPP

#include <boost/hana/fwd/string.hpp>

#include <boost/hana/bool.hpp>
#include <boost/hana/concept/constant.hpp>
#include <boost/hana/config.hpp>
#include <boost/hana/core/make.hpp>
#include <boost/hana/detail/algorithm.hpp>
#include <boost/hana/detail/operators/adl.hpp>
#include <boost/hana/detail/operators/comparable.hpp>
#include <boost/hana/detail/operators/iterable.hpp>
#include <boost/hana/detail/operators/orderable.hpp>
#include <boost/hana/fwd/at.hpp>
#include <boost/hana/fwd/contains.hpp>
#include <boost/hana/fwd/core/tag_of.hpp>
#include <boost/hana/fwd/core/to.hpp>
#include <boost/hana/fwd/drop_front.hpp>
#include <boost/hana/fwd/equal.hpp>
#include <boost/hana/fwd/find.hpp>
#include <boost/hana/fwd/front.hpp>
#include <boost/hana/fwd/hash.hpp>
#include <boost/hana/fwd/is_empty.hpp>
#include <boost/hana/fwd/length.hpp>
#include <boost/hana/fwd/less.hpp>
#include <boost/hana/fwd/plus.hpp>
#include <boost/hana/fwd/unpack.hpp>
#include <boost/hana/fwd/zero.hpp>
#include <boost/hana/if.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/optional.hpp>
#include <boost/hana/type.hpp>

#include <utility>
#include <cstddef>
#include <type_traits>


BOOST_HANA_NAMESPACE_BEGIN
    //////////////////////////////////////////////////////////////////////////
    // string<>
    //////////////////////////////////////////////////////////////////////////
    //! @cond
    namespace detail {
        template <char ...s>
        constexpr char const string_storage[sizeof...(s) + 1] = {s..., '\0'};
    }

    template <char ...s>
    struct string
        : detail::operators::adl<string<s...>>
        , detail::iterable_operators<string<s...>>
    {
        static constexpr char const* c_str() {
            return &detail::string_storage<s...>[0];
        }
    };
    //! @endcond

    template <char ...s>
    struct tag_of<string<s...>> {
        using type = string_tag;
    };

    //////////////////////////////////////////////////////////////////////////
    // make<string_tag>
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct make_impl<string_tag> {
        template <typename ...Chars>
        static constexpr auto apply(Chars const& ...) {
            return hana::string<hana::value<Chars>()...>{};
        }
    };

    //////////////////////////////////////////////////////////////////////////
    // BOOST_HANA_STRING
    //////////////////////////////////////////////////////////////////////////
    namespace string_detail {
        template <typename S, std::size_t ...N>
        constexpr string<S::get()[N]...>
        prepare_impl(S, std::index_sequence<N...>)
        { return {}; }

        template <typename S>
        constexpr decltype(auto) prepare(S s) {
            return prepare_impl(s,
                std::make_index_sequence<sizeof(S::get()) - 1>{});
        }
    }

#define BOOST_HANA_STRING(s)                                                \
    (::boost::hana::string_detail::prepare([]{                              \
        struct tmp {                                                        \
            static constexpr decltype(auto) get() { return s; }             \
        };                                                                  \
        return tmp{};                                                       \
    }()))                                                                   \
/**/

#ifdef BOOST_HANA_CONFIG_ENABLE_STRING_UDL
    //////////////////////////////////////////////////////////////////////////
    // _s user-defined literal
    //////////////////////////////////////////////////////////////////////////
    namespace literals {
        template <typename CharT, CharT ...s>
        constexpr auto operator"" _s() {
            static_assert(std::is_same<CharT, char>::value,
            "hana::string: Only narrow string literals are supported with "
            "the _s string literal right now. See https://goo.gl/fBbKD7 "
            "if you need support for fancier types of compile-time strings.");
            return hana::string_c<s...>;
        }
    }
#endif

    //////////////////////////////////////////////////////////////////////////
    // Operators
    //////////////////////////////////////////////////////////////////////////
    namespace detail {
        template <>
        struct comparable_operators<string_tag> {
            static constexpr bool value = true;
        };
        template <>
        struct orderable_operators<string_tag> {
            static constexpr bool value = true;
        };
    }

    //////////////////////////////////////////////////////////////////////////
    // to<char const*>
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct to_impl<char const*, string_tag> {
        template <char ...c>
        static constexpr char const* apply(string<c...> const&)
        { return string<c...>::c_str(); }
    };

    //////////////////////////////////////////////////////////////////////////
    // to<string_tag>
    //////////////////////////////////////////////////////////////////////////
    namespace detail {
        constexpr std::size_t cx_strlen(char const* s) {
          std::size_t n = 0u;
          while (*s != '\0')
            ++s, ++n;
          return n;
        }

        template <typename S, std::size_t ...I>
        constexpr hana::string<hana::value<S>()[I]...> expand(std::index_sequence<I...>)
        { return {}; }
    }

    template <typename IC>
    struct to_impl<hana::string_tag, IC, hana::when<
        hana::Constant<IC>::value &&
        std::is_convertible<typename IC::value_type, char const*>::value
    >> {
        template <typename S>
        static constexpr auto apply(S const&) {
            constexpr char const* s = hana::value<S>();
            constexpr std::size_t len = detail::cx_strlen(s);
            return detail::expand<S>(std::make_index_sequence<len>{});
        }
    };

    //////////////////////////////////////////////////////////////////////////
    // Comparable
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct equal_impl<string_tag, string_tag> {
        template <typename S>
        static constexpr auto apply(S const&, S const&)
        { return hana::true_c; }

        template <typename S1, typename S2>
        static constexpr auto apply(S1 const&, S2 const&)
        { return hana::false_c; }
    };

    //////////////////////////////////////////////////////////////////////////
    // Orderable
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct less_impl<string_tag, string_tag> {
        template <char ...s1, char ...s2>
        static constexpr auto
        apply(string<s1...> const&, string<s2...> const&) {
            // We put a '\0' at the end only to avoid empty arrays.
            constexpr char const c_str1[] = {s1..., '\0'};
            constexpr char const c_str2[] = {s2..., '\0'};
            return hana::bool_c<detail::lexicographical_compare(
                c_str1, c_str1 + sizeof...(s1),
                c_str2, c_str2 + sizeof...(s2)
            )>;
        }
    };

    //////////////////////////////////////////////////////////////////////////
    // Monoid
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct plus_impl<string_tag, string_tag> {
        template <char ...s1, char ...s2>
        static constexpr auto
        apply(string<s1...> const&, string<s2...> const&) {
            return string<s1..., s2...>{};
        }
    };

    template <>
    struct zero_impl<string_tag> {
        static constexpr auto apply() {
            return string<>{};
        }
    };

    template <char ...s1, char ...s2>
    constexpr auto operator+(string<s1...> const&, string<s2...> const&) {
        return hana::string<s1..., s2...>{};
    }

    //////////////////////////////////////////////////////////////////////////
    // Foldable
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct unpack_impl<string_tag> {
        template <char ...s, typename F>
        static constexpr decltype(auto) apply(string<s...> const&, F&& f)
        { return static_cast<F&&>(f)(char_<s>{}...); }
    };

    template <>
    struct length_impl<string_tag> {
        template <char ...s>
        static constexpr auto apply(string<s...> const&)
        { return hana::size_c<sizeof...(s)>; }
    };

    //////////////////////////////////////////////////////////////////////////
    // Iterable
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct front_impl<string_tag> {
        template <char x, char ...xs>
        static constexpr auto apply(string<x, xs...> const&)
        { return hana::char_c<x>; }
    };

    template <>
    struct drop_front_impl<string_tag> {
        template <std::size_t N, char ...xs, std::size_t ...i>
        static constexpr auto helper(string<xs...> const&, std::index_sequence<i...>) {
            constexpr char s[] = {xs...};
            return hana::string_c<s[i + N]...>;
        }

        template <char ...xs, typename N>
        static constexpr auto apply(string<xs...> const& s, N const&) {
            return helper<N::value>(s, std::make_index_sequence<
                (N::value < sizeof...(xs)) ? sizeof...(xs) - N::value : 0
            >{});
        }

        template <typename N>
        static constexpr auto apply(string<> const& s, N const&)
        { return s; }
    };

    template <>
    struct is_empty_impl<string_tag> {
        template <char ...s>
        static constexpr auto apply(string<s...> const&)
        { return hana::bool_c<sizeof...(s) == 0>; }
    };

    template <>
    struct at_impl<string_tag> {
        template <char ...s, typename N>
        static constexpr auto apply(string<s...> const&, N const&) {
            // We put a '\0' at the end to avoid an empty array.
            constexpr char characters[] = {s..., '\0'};
            constexpr auto n = N::value;
            return hana::char_c<characters[n]>;
        }
    };

    //////////////////////////////////////////////////////////////////////////
    // Searchable
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct contains_impl<string_tag> {
        template <char ...s, typename C>
        static constexpr auto
        helper(string<s...> const&, C const&, hana::true_) {
            constexpr char const characters[] = {s..., '\0'};
            constexpr char c = hana::value<C>();
            return hana::bool_c<
                detail::find(characters, characters + sizeof...(s), c)
                    != characters + sizeof...(s)
            >;
        }

        template <typename S, typename C>
        static constexpr auto helper(S const&, C const&, hana::false_)
        { return hana::false_c; }

        template <typename S, typename C>
        static constexpr auto apply(S const& s, C const& c)
        { return helper(s, c, hana::bool_c<hana::Constant<C>::value>); }
    };

    template <>
    struct find_impl<string_tag> {
        template <char ...s, typename Char>
        static constexpr auto apply(string<s...> const& str, Char const& c) {
            return hana::if_(contains_impl<string_tag>::apply(str, c),
                hana::just(c),
                hana::nothing
            );
        }
    };

    //////////////////////////////////////////////////////////////////////////
    // Hashable
    //////////////////////////////////////////////////////////////////////////
    template <>
    struct hash_impl<string_tag> {
        template <typename String>
        static constexpr auto apply(String const&) {
            return hana::type_c<String>;
        }
    };
BOOST_HANA_NAMESPACE_END

#endif // !BOOST_HANA_STRING_HPP
