//===-- rosa/support/types.hpp ----------------------------------*- C++ -*-===//
//
//                                 The RoSA Framework
//
// Distributed under the terms and conditions of the Boost Software License 1.0.
// See accompanying file LICENSE.
//
// If you did not receive a copy of the license file, see
// http://www.boost.org/LICENSE_1_0.txt.
//
//===----------------------------------------------------------------------===//
///
/// \file rosa/support/types.hpp
///
/// \author David Juhasz (david.juhasz@tuwien.ac.at)
///
/// \date 2017-2019
///
/// \brief Implementation of some basic convenience types.
///
/// \note This implementation is partially based on the implementation of
/// corresponding parts of CAF.
///
//===----------------------------------------------------------------------===//

#ifndef ROSA_SUPPORT_TYPES_HPP
#define ROSA_SUPPORT_TYPES_HPP

#include "rosa/support/debug.hpp"

#include <string>

namespace rosa {

/* ************************************************************************** *
 *                                   Unit                                     *
 * ************************************************************************** */

/// A safe type to replace \c void.
///
/// \c rosa::UnitType is analogous to \c void, but can be safely returned,
/// stored, etc. to enable higher-order abstraction without cluttering code with
/// exceptions for \c void (which can't be stored, for example).
struct UnitType {

  /// Constructor, needs to do nothing.
  constexpr UnitType() noexcept {}

  /// Copy-constructor, needs to do nothing.
  constexpr UnitType(const UnitType &) noexcept {}
};

/// Aliasing \c rosa::UnitType as \c rosa::unit_t.
using unit_t = UnitType;

/// The value of \c rosa::unit_t.
///
/// \note Since a value of \c rosa::UnitType has no state, all instances of
/// \c rosa::UnitType is equal and considered *the \c rosa::unit_t value*.
static constexpr unit_t unit = unit_t{}; // NOLINT

/// \name LiftVoid
/// \brief Lifts a type to avoid \c void.
///
/// A type \c T can be lifted as \code
/// typename LiftVoid<T>::Type
/// \endcode
/// The resulted type is \c rosa::unit_t if \c T is \c void, and \c T itself
/// otherwise.
///@{

/// Definition for the general case.
///
/// \tparam T type to lift
template <typename T> struct LiftVoid { using Type = T; };

/// Specialization for \c void.
template <> struct LiftVoid<void> { using Type = unit_t; };

///@}

/// \name UnliftVoid
/// \brief Unlifts a type already lifted by \c rosa::LiftVoid.
///
/// A type \c T can be unlifted as \code
/// typename UnliftVoid<T>::Type
/// \endcode
/// The resulted type is \c void if \c T is \c rosa::unit_t -- that is \c void
/// lifted by \c rosa::LiftVoid --, and \c T itself otherwise.
///
///@{

/// Definition for the general case.
///
/// \tparam T type to unlift
template <typename T> struct UnliftVoid { using Type = T; };

/// Specialization for \c rosa::unit_t.
template <> struct UnliftVoid<unit_t> { using Type = void; };

///@}

/* ************************************************************************** *
 *                                   None                                     *
 * ************************************************************************** */

/// Represents *nothing*.
///
/// An instance of the type represents *nothing*, that can be used, e.g., for
/// clearing an instance of \c rosa::Optional by assigning an instance of
/// \c rosa::NoneType to it.
struct NoneType {
  /// Constructor, needs to do nothing.
  constexpr NoneType(void) {}

  /// Evaluates the instance to \c bool.
  ///
  /// A "nothing" is always evaluates to \c false.
  constexpr explicit operator bool(void) const { return false; }
};

/// Aliasing type \c rosa::NoneType as \c rosa::none_t.
using none_t = NoneType;

/// The value of \c rosa::none_t.
///
/// \note Since a value of \c rosa::NoneType has no state, all instances of
/// \c rosa::NoneType is equal and considered *the \c rosa::none_t value*.
static constexpr none_t none = none_t{}; // NOLINT

/* ************************************************************************** *
 *                                 Optional                                   *
 * ************************************************************************** */

/// \defgroup Optional Specializations of rosa::Optional
///
/// \brief Represents an optional value.
///
/// \note This implementation is compatible with \c std::optional of C++17.
///@{

/// Definition for the general case, optionally storing a value.
///
/// \tparam T type of the optional value
template <class T> class Optional {
public:
  using Type = T;

  /// Creates an instance without value.
  ///
  /// \note Use it with its default parameter.
  Optional(const none_t & = none) : Valid(false) {}

  /// Creates a valid instance with value.
  ///
  /// \tparam U type of the \p X
  /// \tparam E always use it with default value!
  ///
  /// \param X value to store in the object
  ///
  /// \note The constructor is available for types that are convertible to \p T.
  template <class U, class E = typename std::enable_if<
                         std::is_convertible<U, T>::value>::type>
  Optional(U X) : Valid(false) {
    cr(std::move(X));
  }

  /// Creates an instance as a copy of another one.
  ///
  /// \param Other the instance whose state to copy
  Optional(const Optional &Other) : Valid(false) {
    if (Other.Valid) {
      cr(Other.Value);
    }
  }

  /// Creates an instance by moving the state of another one.
  ///
  /// \param Other the instance whose state to obtain
  Optional(Optional &&Other) noexcept(
      std::is_nothrow_move_constructible<T>::value)
      : Valid(false) {
    if (Other.Valid) {
      cr(std::move(Other.Value));
    }
  }

  /// Destroys \p this object.
  ~Optional(void) { destroy(); }

  /// Updates \p this object by copying the state of another one.
  ///
  /// \param Other the instance whose state to copy
  ///
  /// \return reference of the updated instance
  Optional &operator=(const Optional &Other) {
    if (Valid) {
      if (Other.Valid) {
        Value = Other.Value;
      } else {
        destroy();
      }
    } else if (Other.Valid) {
      cr(Other.Value);
    }
    return *this;
  }

  /// Updates \p this object by moving the state of another one.
  ///
  /// \param Other the instance whose state to obtain
  ///
  /// \return reference of the updated instance
  Optional &operator=(Optional &&Other) noexcept(
      std::is_nothrow_destructible<T>::value
          &&std::is_nothrow_move_assignable<T>::value) {
    if (Valid) {
      if (Other.Valid) {
        Value = std::move(Other.Value);
      } else {
        destroy();
      }
    } else if (Other.Valid) {
      cr(std::move(Other.Value));
    }
    return *this;
  }

  /// Checks whether \p this object contains a value.
  ///
  /// \return if \p this object contains a value
  explicit operator bool(void) const { return Valid; }

  /// Checks whether \p this object does not contain a value.
  ///
  /// \return if \p this object does not contain a value
  bool operator!(void)const { return !Valid; }

  /// Returns the value stored in \p this object.
  ///
  /// \return reference of the stored value
  ///
  /// \pre \p this object contains a value
  T &operator*(void) {
    ASSERT(Valid);
    return Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return reference of the stored value
  ///
  /// \pre \p this object contains a value
  const T &operator*(void)const {
    ASSERT(Valid);
    return Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return pointer to the stored value
  ///
  /// \pre \p this object contains a value
  const T *operator->(void)const {
    ASSERT(Valid);
    return &Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return pointer of the stored value
  ///
  /// \pre \p this object contains a value
  T *operator->(void) {
    ASSERT(Valid);
    return &Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return reference of the stored value
  ///
  /// \pre \p this object contains a value
  T &value(void) {
    ASSERT(Valid);
    return Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return reference of the stored value
  ///
  /// \pre \p this object contains a value
  const T &value(void) const {
    ASSERT(Valid);
    return Value;
  }

  /// Returns the stored value or a default.
  ///
  /// If \p this object contains a value, then the stored value is returned. A
  /// given default value is returned otherwise.
  ///
  /// \param DefaultValue the value to return if \p this object does not contain
  /// a value
  ///
  /// \return reference to either the stored value or \p DefaultValue if \p this
  /// object does not contain a value
  const T &valueOr(const T &DefaultValue) const {
    return Valid ? Value : DefaultValue;
  }

private:
  /// Deallocates the stored value if any.
  void destroy(void) {
    if (Valid) {
      Value.~T();
      Valid = false;
    }
  }

  /// Updates the state of \p this object by moving a value into it.
  ///
  /// \tparam V type of \p X
  ///
  /// \param X value to move
  ///
  /// \pre \p this object does not contain a value
  template <class V> void cr(V &&X) {
    ASSERT(!Valid);
    Valid = true;
    new (&Value) T(std::forward<V>(X));
  }

  /// Denotes if \p this object contains a value.
  bool Valid;

  /// Holds the stored value if any.
  union {
    T Value; ///< The stored value.
  };
};

/// Specialization storing a reference.
///
/// The specialization allows \p rosa::Optional to hold a reference
/// rather than an actual value with minimal overhead.
///
/// \tparam T the base type whose reference is to be stored
template <typename T> class Optional<T &> {
public:
  using Type = T;

  /// Creates an instance without reference
  ///
  /// \note Use it with its default parameter.
  Optional(const none_t & = none) : Value(nullptr) {}

  /// Creates a valid instance with reference.
  ///
  /// \param X reference to store in the object
  Optional(T &X) : Value(&X) {}

  /// Creates a valid instance with reference.
  ///
  /// \param X pointer to store in the object as reference
  Optional(T *X) : Value(X) {}

  /// Creates an instance as a copy of another one.
  ///
  /// \param Other the instance whose state to copy
  Optional(const Optional &Other) = default;

  /// Updates \p this object by copying the state of another one.
  ///
  /// \param Other the instance whose state to copy
  ///
  /// \return reference of the updated instance
  Optional &operator=(const Optional &Other) = default;

  /// Checks whether \p this object contains a reference.
  ///
  /// \return if \p this object contains a reference
  explicit operator bool(void) const { return Value != nullptr; }

  /// Checks whether \p this object does not contain a reference.
  ///
  /// \return if \p this object does not contain a reference
  bool operator!(void)const { return !Value; }

  /// Returns the reference stored in \p this object.
  ///
  /// \return the stored reference
  ///
  /// \pre \p this object contains a reference
  T &operator*(void) {
    ASSERT(Value);
    return *Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return the stored reference
  ///
  /// \pre \p this object contains a reference
  const T &operator*(void)const {
    ASSERT(Value);
    return *Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return the stored reference
  ///
  /// \pre \p this object contains a reference
  T *operator->(void) {
    ASSERT(Value);
    return Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return the stored reference
  ///
  /// \pre \p this object contains a reference
  const T *operator->(void)const {
    ASSERT(Value);
    return Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return the stored reference
  ///
  /// \pre \p this object contains a reference
  T &value(void) {
    ASSERT(Value);
    return *Value;
  }

  /// Returns the value stored in \p this object.
  ///
  /// \return the stored reference
  ///
  /// \pre \p this object contains a reference
  const T &value(void) const {
    ASSERT(Value);
    return *Value;
  }

  /// Returns the stored reference or a default.
  ///
  /// If \p this object contains a reference, then the stored reference is
  /// returned. A given default value is returned otherwise.
  ///
  /// \param DefaultValue the value to return if \p this object does not contain
  /// a reference
  ///
  /// \return either the stored reference or \p DefaultValue if \p this object
  /// does not contain a reference
  const T &valueOr(const T &DefaultValue) const {
    return Value ? Value : DefaultValue;
  }

private:
  /// The stored reference as a pointer.
  T *Value;
};

/// Specialization storing \c void.
///
/// The specialization allows \c rosa::Optional to implement a flag for \c void.
template <> class Optional<void> {
public:
  using Type = unit_t;

  /// Creates an instance with a \c false flag.
  ///
  /// \note Use it with its default parameter.
  Optional(none_t = none) : Value(false) {}

  /// Creates an instance with a \c true flag.
  ///
  /// \note The only argument is ignored because it can be *the \c rosa::unit_t
  /// value* only.
  Optional(unit_t) : Value(true) {}

  /// Creates an instance as a copy of another one.
  ///
  /// \param Other the instance whose state to copy
  Optional(const Optional &Other) = default;

  /// Updates \p this object by copying the state of another one.
  ///
  /// \param Other the instance whose state to copy
  ///
  /// \return reference of the updated instance
  Optional &operator=(const Optional &Other) = default;

  /// Checks whether \p this object contains a \p true flag.
  ///
  /// \return if \p this object contains a \p true flag.
  explicit operator bool(void) const { return Value; }

  /// Checks whether \p this object contains a \p false flag.
  ///
  /// \return if \p this object contains a \p false flag.
  bool operator!(void)const { return !Value; }

private:
  /// The stored flag.
  bool Value;
};

///@}

} // End namespace rosa

namespace std {

/// Returns the textual representation of any value of \c rosa::unit_t.
///
/// \return textual representation of \c rosa::UnitType.
inline std::string to_string(const rosa::unit_t &) { return "unit"; }

/// Dumps a \c rosa::Unit to a given \c std::ostream.
///
/// \param [in,out] OS output stream to dump to
/// \param U \c rosa::Unit to dump
///
/// \return \p OS after dumping \p U to it
inline ostream &operator<<(ostream &OS, const rosa::unit_t &U) {
  OS << to_string(U);
  return OS;
}

/// Returns the textual representation of any value of \c rosa::none_t.
///
/// \return textual representation of \c rosa::NoneType.
inline std::string to_string(const rosa::none_t &) { return "none"; }

/// Dumps a \c rosa::none_t to a given \c std::ostream.
///
/// \param [in,out] OS output stream to dump to
/// \param N \c rosa::none_t to dump
///
/// \return \p OS after dumping \p N to it
inline ostream &operator<<(ostream &OS, const rosa::none_t &N) {
  OS << to_string(N);
  return OS;
}

} // End namespace std

#endif // ROSA_SUPPORT_TYPES_HPP
