//===-- rosa/support/atom.hpp -----------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/support/atom.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017
///
/// \brief Facility for *atoms*, short strings statically encoded as integers.
///
/// \note This implementation is based on the \c atom implementation of CAF.
/// \todo Check license.
///
/// *Atoms* can be used to turn short string literals into statically generated
/// types. The literals may consist of at most \c 10 non-special characters,
/// legal characters are \c _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 \c rosa::atom_t into a strongly typed enumeration.
///
/// Values of \c rosa::atom_t casted to \c rosa::AtomValue may be used in a
/// type-safe way.
enum class AtomValue : atom_t {};

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

// clang-format off

/// 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};

// clang-format on

/// 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 \p Current
///
/// \return \p Current updated with \p 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 \c rosa::AtomValue.
///
/// \param CStr a string to encode
/// \param Interim encoded value to add \p CStr to it
///
/// \return \p Interim updated with \p 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 \c rosa::AtomValue into \c std::string.
///
/// \param What value to convert
///
/// \return \c std::string encoded in \p What
std::string to_string(const AtomValue &What);

/// Converts a \c std::string into a \c rosa::AtomValue.
///
/// \param S \c std::string to convert
///
/// \return \c rosa::AtomValue representing \p S
AtomValue atom_from_string(const std::string &S);

/// Converts a string-literal into a \c rosa::AtomValue.
///
/// \tparam Size the length of \p Str
///
/// \param Str the string-literal to convert
///
/// \return \c rosa::AtomValue representating \p Str
///
/// \pre \p 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 \c rosa::AtomValue to a compile-time constant.
///
/// \tparam V \c rosa::AtomValue to lift
template <AtomValue V> struct AtomConstant {

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

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

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

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

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

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

} // End namespace rosa

#endif // ROSA_SUPPORT_ATOM_HPP
