/*
    Copyright (c) 2013-2014 Contributors as noted in the AUTHORS file

    This file is part of azmq

    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)

    This is an implementation of Andrei Alexandrescu's Expected<T> type
    from the "Systematic Error Handling in C++" talk given at C++ And
    Beyond 2012
*/
#ifndef AZMQ_EXPECTED_HPP_
#define AZMQ_EXPECTED_HPP_

#include <boost/assert.hpp>
#include <exception>
#include <utility>
#include <typeinfo>
#include <stdexcept>
#include <cassert>

namespace azmq { namespace util {
// define AZMQ_LOG_UNCHECKED *BEFORE* including expected.hpp to forward declare the following
// function to be called any time an exception is present and unchecked in an expected<T>
// when it's destructor is called
#ifdef AZMQ_LOG_UNCHECKED
    void log_expected_unchecked(std::exception_ptr err);
#endif

template<class T>
class expected {
    union {
        T val_;
        std::exception_ptr err_;
    };
    bool valid_;
    mutable bool unchecked_;

    expected() { }
public:
    expected(const T & rhs) : val_(rhs), valid_(true), unchecked_(false) { }
    expected(T && rhs) : val_(std::move(rhs)), valid_(true), unchecked_(false) { }
    expected(const expected & rhs) :
        valid_(rhs.valid_),
        unchecked_(rhs.unchecked_) {
            if (rhs.valid_)
                new(&val_) T(rhs.val_);
            else
                new(&err_) std::exception_ptr(rhs.err_);
        }

    expected(expected && rhs) :
        valid_(rhs.valid_),
        unchecked_(rhs.unchecked_) {
            if (rhs.valid_)
                new(&val_) T(std::move(rhs.val_));
            else
                new(&err_) std::exception_ptr(std::move(rhs.err_));
            rhs.unchecked_ = false;
        }

    ~expected() {
#ifdef AZMQ_LOG_UNCHECKED
        if (unchecked_)
            log_expected_unchecked(err_);
#else
        BOOST_ASSERT_MSG(!unchecked_, "error result not checked");
#endif
    }

    void swap(expected& rhs) {
        if (valid_) {
            if (rhs.valid_) {
                std::swap(val_, rhs.val_);
            } else {
                auto t = std::move(rhs.err_);
                new(&rhs.val_) T(std::move(val_));
                new(&err_) std::exception_ptr(t);
                std::swap(valid_, rhs.valid_);
                std::swap(unchecked_, rhs.unchecked_);
            }
        } else {
            if (rhs.valid_) {
                rhs.swap(*this);
            } else {
                std::swap(err_, rhs.err_);
                std::swap(valid_, rhs.valid_);
                std::swap(unchecked_, rhs.unchecked_);
            }
        }
    }

    template<class E>
    static expected<T> from_exception(const E & exception) {
        if (typeid(exception) != typeid(E))
            throw std::invalid_argument("slicing detected");
        return from_exception(std::make_exception_ptr(exception));
    }

    static expected<T> from_exception(std::exception_ptr p) {
        expected<T> res;
        res.valid_ = false;
        res.unchecked_ = true;
        new(&res.err_) std::exception_ptr(std::move(p));
        return res;
    }

    static expected<T> from_exception() {
        return from_exception(std::current_exception());
    }

    bool valid() const {
        unchecked_ = false;
        return valid_;
    }

    const T & get() const {
        unchecked_ = false;
        if (!valid_) std::rethrow_exception(err_);
        return val_;
    }

    T & get() {
        unchecked_ = false;
        if (!valid_) std::rethrow_exception(err_);
        return val_;
    }

    template<class E>
    bool has_exception() const {
        unchecked_ = false;
        try {
            if (!valid_) std::rethrow_exception(err_);
        } catch (const E &) {
            return true;
        } catch (...) { }
        return false;
    }
};
} // namespace util
} // namespace azmq
#endif // AZMQ_EXPECTED_HPP_
