#pragma once

#include "utility"

namespace std
{

struct nullopt_t
{
  explicit constexpr nullopt_t(int) {}
};

inline constexpr nullopt_t nullopt{0};

template <class T>
class optional
{
  T value_;
  bool engaged_;

public:
  using value_type = T;

  constexpr optional() noexcept : value_(), engaged_(false) {}
  constexpr optional(nullopt_t) noexcept : value_(), engaged_(false) {}
  constexpr optional(const T &v) : value_(v), engaged_(true) {}
  constexpr optional(const optional &) = default;

  optional &operator=(nullopt_t) noexcept
  {
    engaged_ = false;
    return *this;
  }

  optional &operator=(const T &v)
  {
    value_ = v;
    engaged_ = true;
    return *this;
  }

  optional &operator=(const optional &) = default;

  constexpr bool has_value() const noexcept
  {
    return engaged_;
  }
  constexpr explicit operator bool() const noexcept
  {
    return engaged_;
  }

  T &operator*()
  {
    __ESBMC_assert(engaged_, "optional: dereference on disengaged value");
    return value_;
  }
  const T &operator*() const
  {
    __ESBMC_assert(engaged_, "optional: dereference on disengaged value");
    return value_;
  }

  T *operator->()
  {
    __ESBMC_assert(engaged_, "optional: arrow on disengaged value");
    return &value_;
  }
  const T *operator->() const
  {
    __ESBMC_assert(engaged_, "optional: arrow on disengaged value");
    return &value_;
  }

  T &value()
  {
    __ESBMC_assert(engaged_, "optional: value() on disengaged value");
    return value_;
  }
  const T &value() const
  {
    __ESBMC_assert(engaged_, "optional: value() on disengaged value");
    return value_;
  }

  template <class U>
  T value_or(U &&def) const
  {
    return engaged_ ? value_ : static_cast<T>(std::forward<U>(def));
  }

  void reset() noexcept
  {
    engaged_ = false;
  }
};

template <class T>
constexpr bool operator==(const optional<T> &o, nullopt_t) noexcept
{
  return !o.has_value();
}
template <class T>
constexpr bool operator==(nullopt_t, const optional<T> &o) noexcept
{
  return !o.has_value();
}
template <class T>
constexpr bool operator!=(const optional<T> &o, nullopt_t) noexcept
{
  return o.has_value();
}
template <class T>
constexpr bool operator!=(nullopt_t, const optional<T> &o) noexcept
{
  return o.has_value();
}

template <class T>
constexpr bool operator==(const optional<T> &a, const optional<T> &b)
{
  if (a.has_value() != b.has_value())
    return false;
  return !a.has_value() || (*a == *b);
}
template <class T>
constexpr bool operator!=(const optional<T> &a, const optional<T> &b)
{
  return !(a == b);
}

} // namespace std
