#ifndef SPROUT_ALGORITHM_STRING_JOIN_HPP
#define SPROUT_ALGORITHM_STRING_JOIN_HPP

#include <type_traits>
#include <sprout/config.hpp>
#include <sprout/container/traits.hpp>
#include <sprout/container/functions.hpp>
#include <sprout/container/indexes.hpp>
#include <sprout/index_tuple/metafunction.hpp>
#include <sprout/pit/pit.hpp>
#include <sprout/iterator/type_traits/category.hpp>
#include <sprout/range/adaptor/size_enumed.hpp>
#include <sprout/range/algorithm/lower_bound.hpp>
#include <sprout/range/numeric/partial_sum.hpp>
#include <sprout/type_traits/is_c_str.hpp>

namespace sprout {
	namespace algorithm {
		namespace detail {
			template<typename String, typename = void>
			struct string_size;
			template<typename String>
			struct string_size<
				String,
				typename std::enable_if<sprout::is_c_str<String>::value>::type
			>
				: public std::integral_constant<
					typename sprout::container_traits<String>::size_type,
					sprout::container_traits<String>::static_size - 1
				>
			{};
			template<typename String>
			struct string_size<
				String,
				typename std::enable_if<!sprout::is_c_str<String>::value>::type
			>
				: public std::integral_constant<
					typename sprout::container_traits<String>::size_type,
					sprout::container_traits<String>::static_size
				>
			{};

			template<typename String>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::is_c_str<String>::value,
				typename sprout::container_traits<String>::difference_type
			>::type
			str_size(String const& str) {
				return sprout::size(str) - 1;
			}
			template<typename String>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				!sprout::is_c_str<String>::value,
				typename sprout::container_traits<String>::difference_type
			>::type
			str_size(String const& str) {
				return sprout::size(str);
			}
		}	// namespace detail

		namespace result_of {
			//
			// join
			//
			template<typename ContainerContainer, typename Separator = void>
			struct join
				: public sprout::container_transform_traits<
					typename sprout::container_traits<ContainerContainer>::value_type
				>::template rebind_size<
					sprout::container_traits<
						typename sprout::container_traits<ContainerContainer>::value_type
					>::static_size
						? (sprout::container_traits<
								typename sprout::container_traits<ContainerContainer>::value_type
							>::static_size
							+ sprout::algorithm::detail::string_size<Separator>::value
							)
							* sprout::container_traits<ContainerContainer>::static_size
							- sprout::algorithm::detail::string_size<Separator>::value
						: 0
				>
			{};
			template<typename ContainerContainer>
			struct join<ContainerContainer, void>
				: public sprout::container_transform_traits<
					typename sprout::container_traits<ContainerContainer>::value_type
				>::template rebind_size<
					sprout::container_traits<
						typename sprout::container_traits<ContainerContainer>::value_type
					>::static_size
					* sprout::container_traits<ContainerContainer>::static_size
				>
			{};
		}	// namespace result_of

		namespace detail {
			template<typename Result, typename ContIterator, typename SizeIterator, typename Sizes>
			inline SPROUT_CONSTEXPR typename sprout::container_traits<Result>::value_type
			join_impl_ra_2(
				ContIterator first_cont,
				SizeIterator found,
				Sizes const& sizes,
				sprout::index_t idx
				)
			{
				typedef typename sprout::container_traits<Result>::value_type value_type;
				return found == sizes.end() ? value_type()
					: sprout::begin(first_cont[found - sizes.begin()])[idx - (found != sizes.begin() ? found[-1] : 0)]
					;
			}
			template<typename Result, typename ContIterator, sprout::index_t... Indexes, typename Sizes>
			inline SPROUT_CONSTEXPR Result
			join_impl_ra_1(
				ContIterator first_cont,
				sprout::index_tuple<Indexes...>,
				Sizes const& sizes
				)
			{
				return sprout::make<Result>(
					sprout::algorithm::detail::join_impl_ra_2<Result>(
						first_cont,
						sprout::range::lower_bound(sizes, Indexes + 1),
						sizes,
						Indexes
						)...
					);
			}
			template<typename Result, typename ContainerContainer>
			inline SPROUT_CONSTEXPR Result
			join_impl_ra(ContainerContainer const& cont_cont) {
				typedef typename sprout::container_traits<Result>::difference_type size_type;
				typedef sprout::array<
					size_type,
					sprout::container_traits<ContainerContainer>::static_size
				> sizes_type;
				return sprout::algorithm::detail::join_impl_ra_1<Result>(
					sprout::begin(cont_cont),
					sprout::container_indexes<Result>::make(),
					sprout::range::partial_sum(
						cont_cont | sprout::adaptors::size_enumed,
						sprout::pit<sizes_type>()
						)
					);
			}

			template<typename Result, typename ContIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size == sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				Args const&... args
				);
			template<typename Result, typename ContIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size != sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				Args const&... args
				);
			template<typename Result, typename ContIterator, typename InputIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size == sizeof...(Args),
				Result
			>::type join_impl_1(
				ContIterator first_cont,
				ContIterator last_cont,
				InputIterator first,
				InputIterator last,
				Args const&... args
				)
			{
				return sprout::make<Result>(args...);
			}
			template<typename Result, typename ContIterator, typename InputIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size != sizeof...(Args),
				Result
			>::type join_impl_1(
				ContIterator first_cont,
				ContIterator last_cont,
				InputIterator first,
				InputIterator last,
				Args const&... args
				)
			{
				return first != last
					? sprout::algorithm::detail::join_impl_1<Result>(
						first_cont, last_cont,
						sprout::next(first), last,
						args..., *first
						)
					: sprout::algorithm::detail::join_impl<Result>(sprout::next(first_cont), last_cont, args...)
					;
			}
			template<typename Result, typename ContIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size == sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				Args const&... args
				)
			{
				return sprout::make<Result>(args...);
			}
			template<typename Result, typename ContIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size != sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				Args const&... args
				)
			{
				return first_cont != last_cont
					? sprout::algorithm::detail::join_impl_1<Result>(
						first_cont, last_cont,
						sprout::begin(*first_cont), sprout::end(*first_cont),
						args...
						)
					: sprout::make<Result>(args...)
					;
			}

			template<typename ContainerContainer>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::is_random_access_iterator<
					typename sprout::container_traits<ContainerContainer const>::iterator
				>::value
					&& sprout::is_random_access_iterator<
						typename sprout::container_traits<
							typename sprout::container_traits<ContainerContainer const>::value_type
						>::iterator
					>::value
					,
				typename sprout::algorithm::result_of::join<ContainerContainer>::type
			>::type
			join(ContainerContainer const& cont_cont) {
				typedef typename sprout::algorithm::result_of::join<ContainerContainer>::type result_type;
				return sprout::algorithm::detail::join_impl_ra<result_type>(cont_cont);
			}
			template<typename ContainerContainer>
			inline SPROUT_CONSTEXPR typename std::enable_if<!(
				sprout::is_random_access_iterator<
					typename sprout::container_traits<ContainerContainer const>::iterator
				>::value
					&& sprout::is_random_access_iterator<
						typename sprout::container_traits<
							typename sprout::container_traits<ContainerContainer const>::value_type
						>::iterator
					>::value
					),
				typename sprout::algorithm::result_of::join<ContainerContainer>::type
			>::type
			join(ContainerContainer const& cont_cont) {
				typedef typename sprout::algorithm::result_of::join<ContainerContainer>::type result_type;
				return sprout::algorithm::detail::join_impl<result_type>(
					sprout::begin(cont_cont),
					sprout::end(cont_cont)
					);
			}
		}	// namespace detail
		//
		// join
		//
		template<typename ContainerContainer>
		inline SPROUT_CONSTEXPR typename sprout::algorithm::result_of::join<ContainerContainer>::type
		join(ContainerContainer const& cont_cont) {
			return sprout::algorithm::detail::join(cont_cont);
		}

		namespace detail {
			template<typename Result, typename ContIterator, typename SepIterator, typename SizeIterator, typename Sizes>
			inline SPROUT_CONSTEXPR typename sprout::container_traits<Result>::value_type
			join_impl_ra_2(
				ContIterator first_cont,
				SepIterator first,
				SizeIterator found,
				Sizes const& sizes,
				sprout::index_t idx
				)
			{
				typedef typename sprout::container_traits<Result>::value_type value_type;
				return found == sizes.end() ? value_type()
					: (found - sizes.begin()) % 2 ? first[idx - found[-1]]
					: sprout::begin(first_cont[(found - sizes.begin()) / 2])[idx - (found != sizes.begin() ? found[-1] : 0)]
					;
			}
			template<typename Result, typename ContIterator, typename SepIterator, sprout::index_t... Indexes, typename Sizes>
			inline SPROUT_CONSTEXPR Result
			join_impl_ra_1(
				ContIterator first_cont,
				SepIterator first,
				sprout::index_tuple<Indexes...>,
				Sizes const& sizes
				)
			{
				return sprout::make<Result>(
					sprout::algorithm::detail::join_impl_ra_2<Result>(
						first_cont,
						first,
						sprout::range::lower_bound(sizes, Indexes + 1),
						sizes,
						Indexes
						)...
					);
			}
			template<typename Result, typename ContainerContainer, typename Separator>
			inline SPROUT_CONSTEXPR Result
			join_impl_ra(ContainerContainer const& cont_cont, Separator const& separator) {
				typedef typename sprout::container_traits<Result>::difference_type size_type;
				typedef sprout::array<
					size_type,
					sprout::container_traits<ContainerContainer>::static_size
						? sprout::container_traits<ContainerContainer>::static_size * 2 - 1
						: 0
				> sizes_type;
				return sprout::algorithm::detail::join_impl_ra_1<Result>(
					sprout::begin(cont_cont),
					sprout::begin(separator),
					sprout::container_indexes<Result>::make(),
					sprout::range::partial_sum(
						cont_cont | sprout::adaptors::size_enumed(sprout::algorithm::detail::str_size(separator), true),
						sprout::pit<sizes_type>()
						)
					);
			}

			template<typename Result, typename ContIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size == sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				Args const&... args
				);
			template<typename Result, typename ContIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size != sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				Args const&... args
				);
			template<typename Result, typename ContIterator, typename SepIterator, typename InputIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size == sizeof...(Args),
				Result
			>::type join_impl_1(
				ContIterator first_cont,
				ContIterator last_cont,
				SepIterator sep_first,
				SepIterator sep_last,
				bool sep,
				InputIterator first,
				InputIterator last,
				Args const&... args
				)
			{
				return sprout::make<Result>(args...);
			}
			template<typename Result, typename ContIterator, typename SepIterator, typename InputIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size != sizeof...(Args),
				Result
			>::type join_impl_1(
				ContIterator first_cont,
				ContIterator last_cont,
				SepIterator sep_first,
				SepIterator sep_last,
				bool sep,
				InputIterator first,
				InputIterator last,
				Args const&... args
				)
			{
				return first != last
					? sprout::algorithm::detail::join_impl_1<Result>(
						first_cont, last_cont,
						sep_first, sep_last,
						sep,
						sprout::next(first), last,
						args..., *first
						)
					: sep
						? sprout::algorithm::detail::join_impl<Result>(
							sprout::next(first_cont), last_cont,
							sep_first, sep_last,
							false,
							args...
							)
						: sprout::algorithm::detail::join_impl<Result>(
							first_cont, last_cont,
							sep_first, sep_last,
							true,
							args...
							)
					;
			}
			template<typename Result, typename ContIterator, typename SepIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size == sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				SepIterator sep_first,
				SepIterator sep_last,
				bool sep,
				Args const&... args
				)
			{
				return sprout::make<Result>(args...);
			}
			template<typename Result, typename ContIterator, typename SepIterator, typename... Args>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::container_traits<Result>::static_size != sizeof...(Args),
				Result
			>::type join_impl(
				ContIterator first_cont,
				ContIterator last_cont,
				SepIterator sep_first,
				SepIterator sep_last,
				bool sep,
				Args const&... args
				)
			{
				return first_cont != last_cont
					? sep
						? sprout::algorithm::detail::join_impl_1<Result>(
							first_cont, last_cont,
							sep_first, sep_last,
							sep,
							sep_first, sep_last,
							args...
							)
						: sprout::algorithm::detail::join_impl_1<Result>(
							first_cont, last_cont,
							sep_first, sep_last,
							sep,
							sprout::begin(*first_cont), sprout::end(*first_cont),
							args...
							)
					: sprout::make<Result>(args...)
					;
			}

			template<typename ContainerContainer, typename Separator>
			inline SPROUT_CONSTEXPR typename std::enable_if<
				sprout::is_random_access_iterator<
					typename sprout::container_traits<ContainerContainer const>::iterator
				>::value
					&& sprout::is_random_access_iterator<
						typename sprout::container_traits<
							typename sprout::container_traits<ContainerContainer const>::value_type
						>::iterator
					>::value
					&& sprout::is_random_access_iterator<
						typename sprout::container_traits<Separator const>::iterator
					>::value
					,
				typename sprout::algorithm::result_of::join<ContainerContainer, Separator>::type
			>::type
			join(ContainerContainer const& cont_cont, Separator const& separator) {
				typedef typename sprout::algorithm::result_of::join<ContainerContainer, Separator>::type result_type;
				return sprout::algorithm::detail::join_impl_ra<result_type>(cont_cont, separator);
			}
			template<typename ContainerContainer, typename Separator>
			inline SPROUT_CONSTEXPR typename std::enable_if<!(
				sprout::is_random_access_iterator<
					typename sprout::container_traits<ContainerContainer const>::iterator
				>::value
					&& sprout::is_random_access_iterator<
						typename sprout::container_traits<
							typename sprout::container_traits<ContainerContainer const>::value_type
						>::iterator
					>::value
					&& sprout::is_random_access_iterator<
						typename sprout::container_traits<Separator const>::iterator
					>::value
					),
				typename sprout::algorithm::result_of::join<ContainerContainer, Separator>::type
			>::type
			join(ContainerContainer const& cont_cont, Separator const& separator) {
				typedef typename sprout::algorithm::result_of::join<ContainerContainer, Separator>::type result_type;
				return sprout::algorithm::detail::join_impl<result_type>(
					sprout::begin(cont_cont),
					sprout::end(cont_cont),
					sprout::begin(separator),
					sprout::end(separator),
					false
					);
			}
		}	// namespace detail
		//
		// join
		//
		template<typename ContainerContainer, typename Separator>
		inline SPROUT_CONSTEXPR typename sprout::algorithm::result_of::join<ContainerContainer, Separator>::type
		join(ContainerContainer const& cont_cont, Separator const& separator) {
			return sprout::algorithm::detail::join(cont_cont, separator);
		}
	}	// namespace algorithm
}	// namespace sprout

#endif	// #ifndef SPROUT_ALGORITHM_STRING_JOIN_HPP
