/*******************************************************************************
 *
 * File:     types.hpp
 *
 * Contents: Implementation of some basic convenience types.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * 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                                     *
 * ************************************************************************** */

// Unit 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 {
  constexpr UnitType() noexcept {
    // nop
  }

  constexpr UnitType(const UnitType &) noexcept {
    // nop
  }

  template <class T> explicit constexpr UnitType(T &&) noexcept {
    // nop
  }

  template <class... Ts>
  constexpr UnitType operator()(Ts &&...) const noexcept {
    return {};
  }
};

using unit_t = UnitType;

static constexpr unit_t unit = unit_t{}; // NOLINT

inline std::string to_string(const unit_t &) { return "unit"; }

template <typename T> struct LiftVoid { using Type = T; };

template <> struct LiftVoid<void> { using Type = unit_t; };

template <typename T> struct UnliftVoid { using Type = T; };

template <> struct UnliftVoid<unit_t> { using Type = void; };

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

// Represents "nothing", e.g., for clearing an Optional by assigning none.
struct NoneType {
  constexpr NoneType() {
    // nop
  }
  constexpr explicit operator bool() const { return false; }
};

using none_t = NoneType;

static constexpr none_t none = none_t{}; // NOLINT

inline std::string to_string(const none_t &) { return "none"; }

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

// A C++17 compatible optional implementation.
template <class T> class Optional {
public:
  using Type = T;

  // Creates an instance without value.
  Optional(const none_t & = none) : Valid(false) {
    // nop
  }

  // Creates an valid instance from value.
  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));
  }

  Optional(const Optional &Other) : Valid(false) {
    if (Other.Valid) {
      cr(Other.Value);
    }
  }

  Optional(Optional &&Other) noexcept(
      std::is_nothrow_move_constructible<T>::value)
      : Valid(false) {
    if (Other.Valid) {
      cr(std::move(Other.Value));
    }
  }

  ~Optional() { destroy(); }

  Optional &operator=(const Optional &Other) {
    if (Valid) {
      if (Other.Valid) {
        Value = Other.Value;
      } else {
        destroy();
      }
    } else if (Other.Valid) {
      cr(Other.Value);
    }
    return *this;
  }

  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.
  explicit operator bool() const { return Valid; }

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

  // Returns the value.
  // PRE: Valid
  T &operator*() {
    ASSERT(Valid);
    return Value;
  }

  // Returns the value.
  // PRE: Valid
  const T &operator*() const {
    ASSERT(Valid);
    return Value;
  }

  // Returns the value.
  // PRE: Valid
  const T *operator->() const {
    ASSERT(Valid);
    return &Value;
  }

  // Returns the value.
  // PRE: Valid
  T *operator->() {
    ASSERT(Valid);
    return &Value;
  }

  // Returns the value.
  // PRE: Valid
  T &value() {
    ASSERT(Valid);
    return Value;
  }

  // Returns the value.
  // PRE: Valid
  const T &value() const {
    ASSERT(Valid);
    return Value;
  }

  // Returns the value if Valid, otherwise returns DefaultValue.
  const T &valueOr(const T &DefaultValue) const {
    return Valid ? Value() : DefaultValue;
  }

private:
  void destroy() {
    if (Valid) {
      Value.~T();
      Valid = false;
    }
  }

  template <class V> void cr(V &&X) {
    ASSERT(!Valid);
    Valid = true;
    new (&Value) T(std::forward<V>(X));
  }

  bool Valid;
  union {
    T Value;
  };
};

// Template specialization to allow Optional to hold a reference
// rather than an actual value with minimal overhead.
template <typename T> class Optional<T &> {
public:
  using Type = T;

  Optional(const none_t & = none) : Value(nullptr) {
    // nop
  }

  Optional(T &X) : Value(&X) {
    // nop
  }

  Optional(T *X) : Value(X) {
    // nop
  }

  Optional(const Optional &Other) = default;

  Optional &operator=(const Optional &Other) = default;

  explicit operator bool() const { return Value != nullptr; }

  bool operator!() const { return !Value; }

  T &operator*() {
    ASSERT(Value);
    return *Value;
  }

  const T &operator*() const {
    ASSERT(Value);
    return *Value;
  }

  T *operator->() {
    ASSERT(Value);
    return Value;
  }

  const T *operator->() const {
    ASSERT(Value);
    return Value;
  }

  T &value() {
    ASSERT(Value);
    return *Value;
  }

  const T &value() const {
    ASSERT(Value);
    return *Value;
  }

  const T &valueOr(const T &DefaultValue) const {
    return Value ? Value() : DefaultValue;
  }

private:
  T *Value;
};

// Template specialization to allow Optional to implement a flag for void.
template <> class Optional<void> {
public:
  using Type = unit_t;

  Optional(none_t = none) : Value(false) {
    // nop
  }

  Optional(unit_t) : Value(true) {
    // nop
  }

  Optional(const Optional &Other) = default;

  Optional &operator=(const Optional &Other) = default;

  explicit operator bool() const { return Value; }

  bool operator!() const { return !Value; }

private:
  bool Value;
};

} // End namespace rosa

#endif // ROSA_SUPPORT_TYPES_HPP

