/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */
#pragma once

/** \file static string for constexpr. Based on:
 * https://akrzemi1.wordpress.com/2017/06/28/compile-time-string-concatenation/
 * (Boost License)
 */

#include <mrpt/typemeta/xassert.h>

#include <string>

namespace mrpt
{
namespace typemeta
{
template <int N>
class string_literal
{
  const char (&_lit)[N + 1];

 public:
  /** Ctor from C string literal, with trailing zero. */
  constexpr string_literal(const char (&lit)[N + 1]) : _lit((MRPT_X_ASSERT(lit[N] == '\0'), lit)) {}
  constexpr std::size_t size() const { return N; }
  constexpr char operator[](int i) const { return MRPT_X_ASSERT(i >= 0 && i < N), _lit[i]; }
  constexpr const char* c_str() const { return _lit; }
  constexpr operator const char*() const { return _lit; }
  operator std::string() const { return _lit; }
};

template <int N_PLUS_1>
constexpr auto literal(const char (&lit)[N_PLUS_1]) -> string_literal<N_PLUS_1 - 1>
{
  return string_literal<N_PLUS_1 - 1>(lit);
}

#define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), bool>::type = true

namespace internal
{
// the type used to receive the pack
template <int... I>
struct sequence
{
};

// auxiliary meta-function for making (N+1)-sized sequence
// from an N-sized sequence
template <typename T>
struct append;

template <int... I>
struct append<sequence<I...>>
{
  using type = sequence<I..., sizeof...(I)>;
};

// recursive implementation of make_sequence

template <int I>
struct make_sequence_;

template <int I>
using make_sequence = typename make_sequence_<I>::type;

template <>
struct make_sequence_<0>  // recursion end
{
  using type = sequence<>;
};

template <int I>
struct make_sequence_ : append<make_sequence<I - 1>>
{
  static_assert(I >= 0, "negative size");
};
}  // namespace internal

template <int N>
class array_string
{
  char _array[N + 1];

  template <typename S1, typename S2, int... PACK1, int... PACK2>
  constexpr array_string(
      const S1& s1, const S2& s2, internal::sequence<PACK1...>, internal::sequence<PACK2...>) :
      _array{s1[PACK1]..., s2[PACK2]..., '\0'}
  {
  }

 public:
  /** ctor: literal + literal */
  template <int N1, REQUIRES(N1 <= N)>
  constexpr array_string(const string_literal<N1>& s1, const string_literal<N - N1>& s2) :
      array_string{s1, s2, internal::make_sequence<N1>{}, internal::make_sequence<N - N1>{}}
  {
  }

  /** ctor: string + literal */
  template <int N1, REQUIRES(N1 <= N)>
  constexpr array_string(const array_string<N1>& s1, const string_literal<N - N1>& s2) :
      array_string{s1, s2, internal::make_sequence<N1>{}, internal::make_sequence<N - N1>{}}
  {
  }

  /** ctor: string + string */
  template <int N1, REQUIRES(N1 <= N)>
  constexpr array_string(const array_string<N1>& s1, const array_string<N - N1>& s2) :
      array_string{s1, s2, internal::make_sequence<N1>{}, internal::make_sequence<N - N1>{}}
  {
  }

  constexpr std::size_t size() const { return N; }
  constexpr char operator[](int i) const { return MRPT_X_ASSERT(i >= 0 && i < N), _array[i]; }
  constexpr const char* c_str() const { return _array; }
  constexpr operator const char*() const { return c_str(); }
  operator std::string() const { return c_str(); }
};

template <int N1, int N2>
constexpr auto operator+(const string_literal<N1>& s1, const string_literal<N2>& s2)
    -> array_string<N1 + N2>
{
  return array_string<N1 + N2>(s1, s2);
}

template <int N1, int N2>
constexpr auto operator+(const array_string<N1>& s1, const string_literal<N2>& s2)
    -> array_string<N1 + N2>
{
  return array_string<N1 + N2>(s1, s2);
}

template <int N1, int N2>
constexpr auto operator+(const array_string<N1>& s1, const array_string<N2>& s2)
    -> array_string<N1 + N2>
{
  return array_string<N1 + N2>(s1, s2);
}

}  // namespace typemeta
}  // namespace mrpt
