/***************************************************************************//**
 *
 * \file rosa/support/types.hpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Implementation of some basic convenience types.
 *
 * \note This implementation is partially based on the implementation of
 * corresponding parts of CAF.
 * \todo Check license.
 *
 ******************************************************************************/

#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 `void`.
///
/// `rosa::UnitType` is analogous to `void`, but can be safely returned, stored,
/// etc. to enable higher-order abstraction without cluttering code with
/// exceptions for `void` (which can't be stored, for example).
struct UnitType {

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

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

/// Aliasing `rosa::UnitType` as `rosa::unit_t`.
using unit_t = UnitType;

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

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

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

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

/// Specialization for the type `void`.
template <> struct LiftVoid<void> { using Type = unit_t; };

///@}

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

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

/// Specialization for the type `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 `rosa::Optional` by assigning an instance of
/// `rosa::NoneType` to it.
struct NoneType {
  /// Ctor, needs to do nothing.
  constexpr NoneType(void) {}

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

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

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

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

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

/// \defgroup Optional
/// \brief Represents an optional value.
///
/// \note This implementation is compatible with `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 `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 `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 the instance.
  ~Optional(void) { destroy(); }

  /// Updates `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 `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 `this` object contains a value.
  ///
  /// \return if `this` object contains a value
  explicit operator bool(void) const { return Valid; }

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

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

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

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

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

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

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

  /// Returns the stored value or a default.
  ///
  /// If `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 `this` object does not contain
  ///                     a value
  ///
  /// \return reference to either the stored value or `DefaultValue` if `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 `this` object by moving a value into it.
  ///
  /// \tparam V type of `X`
  ///
  /// \param X value to move into
  ///
  /// \pre `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 `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 `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 `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 `this` object contains a reference.
  ///
  /// \return if `this` object contains a reference
  explicit operator bool(void) const { return Value != nullptr; }

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

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

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

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

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

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

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

  /// Returns the stored reference or a default.
  ///
  /// If `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 `this` object does not contain
  ///                     a reference
  ///
  /// \return either the stored reference or `DefaultValue` if `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 `void`.
///
/// The specialization allows `rosa::Optional` to implement a flag for `void`.
template <> class Optional<void> {
public:
  using Type = unit_t;

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

  /// Creates an instance with a `true` flag.
  ///
  /// \note The only argument is ignored because it can be *the `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 `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 `this` object contains a `true` flag.
  ///
  /// \return if `this` object contains a `true` flag.
  explicit operator bool(void) const { return Value; }

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

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

///@}

} // End namespace rosa

#endif // ROSA_SUPPORT_TYPES_HPP

