/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone
 * (see https://gitlab.linphone.org/BC/public/liblinphone).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _L_STATIC_STRING_H_
#define _L_STATIC_STRING_H_

#include "linphone/utils/general.h"

// =============================================================================
// Compile time strings. Useful to build const char * at compilation.
// =============================================================================

LINPHONE_BEGIN_NAMESPACE

template <std::size_t...>
struct IndexSequence {
	using Type = IndexSequence;
};

namespace Private {
template <typename S1, typename S2>
struct ConcatIndexSequenceImpl;

// Expand S1 and offset all elements of S2 by the number of elements of S1 so that we get a contiguous sequence of
// integer numbers Example:
// - S1 = 0 1 2
// - S2 = 0 1 2 3
// - concatenation is:    0 1 2                    3 4 5 6
//                     <-- S1 --> <-- S2 offset by number of elements of S1 -->
// https://en.cppreference.com/w/cpp/language/parameter_pack
// https://en.cppreference.com/w/cpp/language/sizeof...
template <std::size_t... S1, std::size_t... S2>
struct ConcatIndexSequenceImpl<IndexSequence<S1...>, IndexSequence<S2...>>
    : IndexSequence<S1..., (sizeof...(S1) + S2)...> {};

template <std::size_t N>
struct MakeIndexSequenceImpl : ConcatIndexSequenceImpl<typename MakeIndexSequenceImpl<N / 2>::Type,
                                                       typename MakeIndexSequenceImpl<N - N / 2>::Type> {};

template <>
struct MakeIndexSequenceImpl<0> : IndexSequence<> {};

template <>
struct MakeIndexSequenceImpl<1> : IndexSequence<0> {};
} // namespace Private

template <std::size_t N>
using MakeIndexSequence = typename Private::MakeIndexSequenceImpl<N>::Type;

// =============================================================================

// See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4121.pdf
template <std::size_t N>
using RawStaticString = const char[N];

namespace Private {
template <std::size_t N, typename = MakeIndexSequence<N>>
struct StaticString;

template <std::size_t N, std::size_t... Index>
struct StaticString<N, IndexSequence<Index...>> {
	constexpr StaticString(const RawStaticString<N> &inRaw) : raw{(inRaw[Index])...} {
	}

	constexpr char operator[](std::size_t i) const {
		return raw[i];
	}

	constexpr operator const char *() const {
		return raw;
	}

	RawStaticString<N> raw;
};

template <std::size_t N>
class StaticStringConcatHelper {
public:
	template <std::size_t N1>
	constexpr StaticStringConcatHelper(const Private::StaticString<N1> &s1, const Private::StaticString<N - N1 + 1> &s2)
	    : StaticStringConcatHelper(s1, s2, MakeIndexSequence<N1 - 1>{}, MakeIndexSequence<N - N1 + 1>{}) {
	}

	RawStaticString<N> raw;

private:
	template <std::size_t N1, std::size_t... Index1, std::size_t... Index2>
	constexpr StaticStringConcatHelper(const Private::StaticString<N1> &s1,
	                                   const Private::StaticString<N - N1 + 1> &s2,
	                                   IndexSequence<Index1...>,
	                                   IndexSequence<Index2...>)
	    : raw{s1[Index1]..., s2[Index2]...} {
	}
};

// Add 1 to the number of digits of Value because of terminating \0
template <int Value, int N = getIntLength(Value) + 1>
class StaticIntStringHelper {
public:
	constexpr StaticIntStringHelper() : StaticIntStringHelper(MakeIndexSequence < Value >= 0 ? N - 1 : N - 2 > {}) {
	}

	RawStaticString<N> raw;

private:
	// Workaround for MSVC 2015.
	// See:
	// https://stackoverflow.com/questions/41593649/why-wont-visual-studio-let-me-use-a-templatized-constexpr-function-in-enable-i/41597153
	// struct IsNeg { static const bool value = Value < 0; };

	// Subtract 2 from N because powers of 10 must start at 0 and \0 are accounted in the value of N
	template <std::size_t... Index, typename Int = int, typename std::enable_if<Value >= 0, Int>::type * = nullptr>
	constexpr StaticIntStringHelper(const IndexSequence<Index...> &)
	    : raw{char('0' + Value / pow10(N - Index - 2) % 10)..., '\0'} {
	}

	// Subtract 3 from N because powers of 10 must start at 0 and - sign as well as \0 are accounted in the value of N
	template <std::size_t... Index,
	          typename Int = int,
	          typename std::enable_if<Value<0, Int>::type * = nullptr> constexpr StaticIntStringHelper(
	              const IndexSequence<Index...> &) : raw{'-', char('0' + abs(Value) / pow10(N - Index - 3) % 10)...,
	                                                     '\0'} {
	}
};
}; // namespace Private

// -----------------------------------------------------------------------------

template <std::size_t N>
constexpr Private::StaticString<N> StaticString(const RawStaticString<N> &raw) {
	return Private::StaticString<N>(raw);
}

template <std::size_t N1, std::size_t N2>
constexpr Private::StaticString<N1 + N2 - 1> operator+(const Private::StaticString<N1> &s1,
                                                       const Private::StaticString<N2> &s2) {
	return StaticString(Private::StaticStringConcatHelper<N1 + N2 - 1>(s1, s2).raw);
}

template <std::size_t N1, std::size_t N2>
constexpr Private::StaticString<N1 + N2 - 1> operator+(const Private::StaticString<N1> &s1,
                                                       const RawStaticString<N2> &s2) {
	return StaticString(Private::StaticStringConcatHelper<N1 + N2 - 1>(s1, StaticString(s2)).raw);
}

template <std::size_t N1, std::size_t N2>
constexpr Private::StaticString<N1 + N2 - 1> operator+(const RawStaticString<N2> &s1,
                                                       const Private::StaticString<N1> &s2) {
	return StaticString(Private::StaticStringConcatHelper<N1 + N2 - 1>(StaticString(s1), s2).raw);
}

// -----------------------------------------------------------------------------

template <int Value = 0>
constexpr Private::StaticString<getIntLength(Value) + 1> StaticIntString() {
	return StaticString(Private::StaticIntStringHelper<Value>().raw);
}

// -----------------------------------------------------------------------------

LINPHONE_END_NAMESPACE

#endif // ifndef _L_STATIC_STRING_H_
