// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
// (C) Copyright 2014 Vicente J. Botet Escriba

#ifndef BOOST_EXPECTED_UNEXPECTED_HPP
#define BOOST_EXPECTED_UNEXPECTED_HPP

#include <boost/expected/config.hpp>
#include <boost/expected/detail/constexpr_utility.hpp>
#include <boost/expected/detail/requires.hpp>
#include <boost/functional/type_traits_t.hpp>

#ifdef BOOST_EXPECTED_USE_BOOST_HPP
#include <boost/exception_ptr.hpp>
#include <boost/type_traits.hpp>
#endif

#include <exception>
#include <utility>
#include <type_traits>

namespace boost
{

  template <typename ErrorType = std::exception_ptr>
  class unexpected_type
  {
    ErrorType error_;
  public:
    unexpected_type() = delete;

    BOOST_EXPECTED_0_REQUIRES(std::is_copy_constructible<ErrorType>::value)
    BOOST_FORCEINLINE BOOST_CONSTEXPR explicit unexpected_type(ErrorType const& e) :
      error_(e)
    {
    }
    BOOST_EXPECTED_0_REQUIRES(std::is_move_constructible<ErrorType>::value)
    BOOST_FORCEINLINE BOOST_CONSTEXPR explicit unexpected_type(ErrorType&& e) :
      error_(std::move(e))
    {
    }
#if ! defined BOOST_EXPECTED_NO_CXX11_RVALUE_REFERENCE_FOR_THIS

    BOOST_CONSTEXPR
    BOOST_FORCEINLINE ErrorType const& value() const&
    {
      return error_;
    }
    BOOST_EXPECTED_CONSTEXPR_IF_MOVE_ACCESSORS
    BOOST_FORCEINLINE ErrorType& value() &
    {
      return error_;
    }
    BOOST_EXPECTED_CONSTEXPR_IF_MOVE_ACCESSORS
    BOOST_FORCEINLINE ErrorType&& value() &&
    {
      return constexpr_move(error_);
    }
#else
    BOOST_CONSTEXPR
    BOOST_FORCEINLINE ErrorType const& value() const
    {
      return error_;
    }
    BOOST_FORCEINLINE ErrorType& value()
    {
      return error_;
    }
#endif
  };

  template <class E>
  BOOST_FORCEINLINE BOOST_CONSTEXPR unexpected_type<decay_t<E>> make_unexpected(E&& ex)
  {
    return unexpected_type<decay_t<E>> (std::forward<E>(ex));
  }

#ifdef BOOST_EXPECTED_USE_BOOST_HPP
  template <>
  struct unexpected_type<boost::exception_ptr>
  {
    boost::exception_ptr error_;
  public:
    unexpected_type() = delete;

    BOOST_FORCEINLINE explicit unexpected_type(boost::exception_ptr const& e) :
      error_(e)
    {
    }
    BOOST_FORCEINLINE explicit unexpected_type(boost::exception_ptr&& e) :
      error_(std::move(e))
    {
    }

    template <class E>
    BOOST_FORCEINLINE explicit unexpected_type(E e) :
      error_(boost::copy_exception(e))
    {
    }

    BOOST_FORCEINLINE boost::exception_ptr const& value() const
    {
      return error_;
    }
  };
#endif

  template <>
  struct unexpected_type<std::exception_ptr>
  {
    std::exception_ptr error_;
  public:
    unexpected_type() = delete;

    BOOST_FORCEINLINE explicit unexpected_type(std::exception_ptr const& e) :
      error_(e)
    {
    }

    BOOST_FORCEINLINE explicit unexpected_type(std::exception_ptr &&e) :
      error_(std::move(e))
    {
    }

    template <class E>
    BOOST_FORCEINLINE explicit unexpected_type(E e) :
      error_(std::make_exception_ptr(e))
    {
    }
    BOOST_FORCEINLINE std::exception_ptr const& value() const
    {
      return error_;
    }
  };

  template <class E>
  BOOST_CONSTEXPR bool operator==(const unexpected_type<E>& x, const unexpected_type<E>& y)
  {
    return x.value() == y.value();
  }
  template <class E>
  BOOST_CONSTEXPR bool operator!=(const unexpected_type<E>& x, const unexpected_type<E>& y)
  {
    return !(x == y);
  }

  template <class E>
  BOOST_CONSTEXPR bool operator<(const unexpected_type<E>& x, const unexpected_type<E>& y)
  {
    return x.value() < y.value();
  }

  template <class E>
  BOOST_CONSTEXPR bool operator>(const unexpected_type<E>& x, const unexpected_type<E>& y)
  {
    return (y < x);
  }

  template <class E>
  BOOST_CONSTEXPR bool operator<=(const unexpected_type<E>& x, const unexpected_type<E>& y)
  {
    return !(y < x);
  }

  template <class E>
  BOOST_CONSTEXPR bool operator>=(const unexpected_type<E>& x, const unexpected_type<E>& y)
  {
    return !(x < y);
  }

  inline BOOST_CONSTEXPR bool operator<(const unexpected_type<std::exception_ptr>&, const unexpected_type<std::exception_ptr>&)
  {
    return false;
  }
  inline BOOST_CONSTEXPR bool operator>(const unexpected_type<std::exception_ptr>&, const unexpected_type<std::exception_ptr>&)
  {
    return false;
  }
  inline BOOST_CONSTEXPR bool operator<=(const unexpected_type<std::exception_ptr>& x, const unexpected_type<std::exception_ptr>& y)
  {
    return x==y;
  }
  inline BOOST_CONSTEXPR bool operator>=(const unexpected_type<std::exception_ptr>& x, const unexpected_type<std::exception_ptr>& y)
  {
    return x==y;
  }

#ifdef BOOST_EXPECTED_USE_BOOST_HPP
  inline BOOST_CONSTEXPR bool operator<(const unexpected_type<boost::exception_ptr>& x, const unexpected_type<boost::exception_ptr>& y)
  {
    return false;
  }
  inline BOOST_CONSTEXPR bool operator>(const unexpected_type<boost::exception_ptr>& x, const unexpected_type<boost::exception_ptr>& y)
  {
    return false;
  }
  inline BOOST_CONSTEXPR bool operator<=(const unexpected_type<boost::exception_ptr>& x, const unexpected_type<boost::exception_ptr>& y)
  {
    return x==y;
  }
  inline BOOST_CONSTEXPR bool operator>=(const unexpected_type<boost::exception_ptr>& x, const unexpected_type<boost::exception_ptr>& y)
  {
    return x==y;
  }
#endif

  template <typename E>
  struct is_unexpected : std::false_type {};
  template <typename E>
  struct is_unexpected<unexpected_type<E>> : std::true_type {};

  BOOST_FORCEINLINE unexpected_type<std::exception_ptr> make_unexpected_from_current_exception()
  {
    return unexpected_type<std::exception_ptr> (std::current_exception());
  }

} // namespace boost

#endif // BOOST_EXPECTED_UNEXPECTED_HPP
