/***************************************************************************//**
 *
 * \file rosa/support/atom.hpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Facility for `atom`s, short strings statically encoded as integers.
 *
 * \note This implementation is based on the `atom` implementation of CAF.
 * \todo Check license.
 *
 * `Atom`s can be used to turn short string literals into statically generated
 * types. The literals may consist of at most `10` non-special characters, legal
 * characters are `_0-9A-Za-z` and the whitespace character. Special characters
 * are turned into whitespace, which may result in different string literals
 * being encoded into the same integer value, if any of those contain at least
 * one special character.
 *
 * \note The usage of special characters in the string literals used to create
 * `atoms` cannot be checked by the compiler.
 *
 * Example:
 *
 * \code
 * constexpr AtomValue NameValue = atom("name");
 * using NameAtom = AtomConstant<NameValue>;
 *
 * [](NameAtom){ std::cout << "Argument of type NameAtom"; }(NameAtom::Value)
 * \endcode
 *
 ******************************************************************************/

#ifndef ROSA_SUPPORT_ATOM_HPP
#define ROSA_SUPPORT_ATOM_HPP

#include "rosa/support/debug.hpp"

namespace rosa {

/// Maximal length of valid atom strings.
constexpr size_t MaxAtomLength = 10;

/// Underlying integer type of atom values.
using atom_t = uint64_t;

/// Turn `rosa::atom_t` into a strongly typed enumeration.
///
/// Values of `rosa::atom_t` casted to `rosa::AtomValue` may be used in a
/// type-safe way.
enum class AtomValue : atom_t {};

/// Anonymous namespace with implementational details, consider it private.
namespace {

/// Encodes ASCII characters to 6-bit encoding.
constexpr unsigned char AtomEncodingTable[] = {
/*     ..0 ..1 ..2 ..3 ..4 ..5 ..6 ..7 ..8 ..9 ..A ..B ..C ..D ..E ..F  */
/* 0.. */  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 1.. */  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 2.. */  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
/* 3.. */  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 0,  0,  0,  0,  0,  0,
/* 4.. */  0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
/* 5.. */ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,  0,  0,  0,  0, 37,
/* 6.. */  0, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
/* 7.. */ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,  0,  0,  0,  0,  0};

/// Decodes 6-bit characters to ASCII
constexpr char AtomDecodingTable[] = " 0123456789"
                                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"
                                     "abcdefghijklmnopqrstuvwxyz";

/// Encodes one character and updates the integer representation.
///
/// \param Current an encoded value
/// \param CharCode a character to add to `Current`
///
/// \return `Current` updated with `CharCode`
constexpr atom_t nextInterim(atom_t Current, size_t CharCode) {
  return (Current << 6) | AtomEncodingTable[(CharCode <= 0x7F) ? CharCode : 0];
}

/// Encodes a C-string into an integer value to be used as `rosa::AtomValue`.
///
/// \param CStr a string to encode
/// \param Interim encoded value to add `CStr` to it
///
/// \return `Interim` updated with `CStr`
constexpr atom_t atomValue(const char *CStr, atom_t Interim = 0xF) {
  return (*CStr == '\0')
             ? Interim
             : atomValue(CStr + 1,
                         nextInterim(Interim, static_cast<size_t>(*CStr)));
}

} // End namespace

/// Converts a `rosa::AtomValue` into `std::string`.
///
/// \param What value to convert
///
/// \return the `string` encoded in `What`
std::string to_string(const AtomValue &What);

/// Converts a `std::string` into a `rosa::AtomValue`.
///
/// \param S the `string` to convert
///
/// \return `AtomValue` representation of `S`
AtomValue atom_from_string(const std::string &S);

/// Converts a string-literal into a `rosa::AtomValue`.
///
/// \tparam Size the length of `Str`
///
/// \param Str the string-literal to convert
///
/// \return `AtomValue` representation of `Str`
///
/// \pre `Str` is not too long:\code
/// Size <= MaxAtomLength + 1
/// \endcode
template <size_t Size>
constexpr AtomValue atom(char const (&Str)[Size]) {
  // Last character is the NULL terminator.
  STATIC_ASSERT(Size <= MaxAtomLength + 1,
                "Too many characters in atom definition");
  return static_cast<AtomValue>(atomValue(Str));
}

/// Lifts a `rosa::AtomValue` to a compile-time constant.
///
/// \tparam V the `AtomValue` to lift
template <AtomValue V>
struct AtomConstant {

  /// Ctor, has to do nothing.
  constexpr AtomConstant(void) {}

  /// Returns the wrapped value.
  ///
  /// \return `V`
  constexpr operator AtomValue(void) const { return V; }

  /// Returns the wrapped value as of type `rosa::atom_t`.
  ///
  /// \return `atom_t` value from `V`
  static constexpr atom_t value() { return static_cast<atom_t>(V); }

  /// An instance *of this constant* (*not* an `rosa::AtomValue`).
  static const AtomConstant Value;
};

// Implementation of the static member field `Value` of `rosa::AtomConstant`.
template <AtomValue V>
const AtomConstant<V> AtomConstant<V>::Value = AtomConstant<V>{};

/// Converts a `rosa::AtomConstant` into `std::string`.
///
/// \tparam V `AtomValue` to convert
///
/// \note The actual argument of type `const AtomConstant<V>` is ignored
/// because the `AtomValue` to convert is encoded in the type itself.
///
/// \return the original string encoded in `V`
template <AtomValue V>
std::string to_string(const AtomConstant<V> &) {
  return to_string(V);
}

} // End namespace rosa

#endif // ROSA_SUPPORT_ATOM_HPP

