// Unique resource wrapper imported (and slightly modified) from
// https://github.com/tandasat/PgResarch (MIT licensed)

//
// N3949 - Scoped Resource - Generic RAII Wrapper for the Standard Library
// Peter Sommerlad and Andrew L. Sandoval
//
#ifndef UNIQUE_RESOURCE_H_
#define UNIQUE_RESOURCE_H_

#include <type_traits>
#include <utility>

#ifndef _MSC_VER
#define UNIQUE_RESOURCE_NOEXCEPT noexcept
#else
#define UNIQUE_RESOURCE_NOEXCEPT
#endif

#define UNIQUE_RESOURCE_TRY_BEGIN   try {
#define UNIQUE_RESOURCE_CATCH_ALL   } catch (...) {
#define UNIQUE_RESOURCE_CATCH_END   }


namespace std {
    namespace experimental {

        enum class invoke_it { once, again };

        template<typename R, typename D>
        class unique_resource_t
        {
            R resource;
            D deleter;
            bool execute_on_destruction; // exposition only
            unique_resource_t& operator=(unique_resource_t const &) = delete;
            unique_resource_t(unique_resource_t const &) = delete; // no copies!

        public:
            // construction
            explicit unique_resource_t(R && resource, D && deleter, bool shouldrun = true) UNIQUE_RESOURCE_NOEXCEPT
                : resource(std::move(resource))
                , deleter(std::move(deleter))
                , execute_on_destruction{ shouldrun }
            {}

            // move
            unique_resource_t(unique_resource_t &&other) UNIQUE_RESOURCE_NOEXCEPT
                : resource(std::move(other.resource))
                , deleter(std::move(other.deleter))
                , execute_on_destruction{ other.execute_on_destruction }
            {
                other.release();
            }

            unique_resource_t& operator=(unique_resource_t &&other) UNIQUE_RESOURCE_NOEXCEPT
            {
                this->invoke(invoke_it::once);
                deleter = std::move(other.deleter);
                resource = std::move(other.resource);
                execute_on_destruction = other.execute_on_destruction;
                other.release();
                return *this;
            }

            // resource release
            ~unique_resource_t()
            {
                this->invoke(invoke_it::once);
            }

            void invoke(invoke_it const strategy = invoke_it::once) UNIQUE_RESOURCE_NOEXCEPT
            {
                if (execute_on_destruction)
                {
                    UNIQUE_RESOURCE_TRY_BEGIN
                    this->get_deleter()(resource);
                    UNIQUE_RESOURCE_CATCH_ALL
                    UNIQUE_RESOURCE_CATCH_END
                }
                execute_on_destruction = strategy == invoke_it::again;
            }

            R const & release() UNIQUE_RESOURCE_NOEXCEPT
            {
                execute_on_destruction = false;
                return this->get();
            }

            void reset(R && newresource) UNIQUE_RESOURCE_NOEXCEPT
            {
                invoke(invoke_it::again);
                resource = std::move(newresource);
            }

            // resource access
            R const & get() const UNIQUE_RESOURCE_NOEXCEPT
            {
                return resource;
            }

            operator R const &() const UNIQUE_RESOURCE_NOEXCEPT
            {
                return resource;
            }

            R operator->() const UNIQUE_RESOURCE_NOEXCEPT
            {
                return resource;
            }

            std::add_lvalue_reference_t<std::remove_pointer_t<R>> operator*() const
            {
                return *resource;
            }

            // deleter access
            const D & get_deleter() const UNIQUE_RESOURCE_NOEXCEPT
            {
                return deleter;
            }
        };

        //factories
        template<typename R, typename D>
        unique_resource_t<R, D> unique_resource(R && r, D t) UNIQUE_RESOURCE_NOEXCEPT
        {
            return unique_resource_t<R, D>(std::move(r), std::move(t), true);
        }

        template<typename R, typename D>
        unique_resource_t<R, D> unique_resource_checked(R r, R invalid, D t) UNIQUE_RESOURCE_NOEXCEPT
        {
            bool shouldrun = (r != invalid);
            return unique_resource_t<R, D>(std::move(r), std::move(t), shouldrun);
        }
    }
}


#undef UNIQUE_RESOURCE_TRY_BEGIN
#undef UNIQUE_RESOURCE_CATCH_ALL
#undef UNIQUE_RESOURCE_CATCH_END
#undef UNIQUE_RESOURCE_NOEXCEPT
#endif /* UNIQUE RESOURCE H */