#pragma once
#include <gmock/gmock.h>
#include <memory>

struct UnderlyingUniquePtrAlreadyMoved : public std::runtime_error {
    UnderlyingUniquePtrAlreadyMoved() : std::runtime_error("unique_ptr already moved") {
    }
};

template <typename Mock, typename Ptr = std::unique_ptr<Mock>>
class UniquePtrMockWrapper {
  public:
    using MockType = Mock;
    using PtrType  = Ptr;

    explicit UniquePtrMockWrapper(Mock* p_ptr = new Mock()) : m_uniquePtr(p_ptr), m_rawPtr(p_ptr) {
    }

    explicit UniquePtrMockWrapper(std::nullptr_t)
        : UniquePtrMockWrapper(static_cast<Mock*>(nullptr)) {
    }

    template <typename MockDerived, typename EnableIf = typename std::enable_if<std::is_convertible<MockDerived*, Mock*>::value>::type>
    explicit UniquePtrMockWrapper(MockDerived* p_ptr)
        : UniquePtrMockWrapper(static_cast<Mock*>(p_ptr)) {
    }

    template <typename NotMockPtr, typename EnableIf = typename std::enable_if<not std::is_convertible<NotMockPtr, Mock*>::value>::type, typename... Args>
    explicit UniquePtrMockWrapper(NotMockPtr&& p_firstArg, Args&&... p_args)
        : UniquePtrMockWrapper(new Mock(std::forward<NotMockPtr>(p_firstArg), std::forward<Args>(p_args)...)) {
    }

    template <typename MockDerived, typename EnableIf = typename std::enable_if<std::is_convertible<MockDerived*, Mock*>::value>::type>
    explicit UniquePtrMockWrapper(std::unique_ptr<MockDerived> p_ptr)
        : UniquePtrMockWrapper(static_cast<Mock*>(p_ptr.release())) {
    }

    Ptr getPtr() {
        if (!m_uniquePtr) {
            throw UnderlyingUniquePtrAlreadyMoved();
        }
        return std::move(m_uniquePtr);
    }

    Mock* get() const {
        return m_rawPtr;
    }

    Mock& operator*() const {
        return *m_rawPtr;
    }

    Mock* operator->() const {
        return m_rawPtr;
    }

    template <typename MockBase,
              typename EnableIf = typename std::enable_if<std::is_convertible<Mock*, MockBase*>::value &&
                                                          std::is_same<Ptr, std::unique_ptr<Mock>>::value>::type>
    /*implicit*/ operator std::unique_ptr<MockBase>() {
        return getPtr();
    }

    void reset(Mock* p_ptr) {
        m_uniquePtr.reset(p_ptr);
        m_rawPtr = p_ptr;
    }

  private:
    Ptr m_uniquePtr;
    Mock* m_rawPtr;
};
//namespace testing {

//struct UniquePtrReturnedMoreThanOnce : public std::logic_error {
//    UniquePtrReturnedMoreThanOnce() : std::logic_error("std::unique_ptr returned more than once") {
//    }
//};

//namespace internal {

//template <typename T>
//class ActionResultHolder<std::unique_ptr<T>> : public UntypedActionResultHolderBase {
//    using UP = std::unique_ptr<T>;

//  public:
//    explicit ActionResultHolder(UP p_value) : m_value(std::move(p_value)) {
//    }

//    UP GetValueAndDelete() const {
//        UP l_retval(std::move(m_value));
//        delete this;
//        return std::move(l_retval);
//    }

//    virtual void PrintAsActionResult(::std::ostream* p_os) const override {
//        *p_os << "\n          Returns: ";
//        UniversalPrinter<UP>::Print(m_value, p_os);
//    }

//    template <typename F>
//    static ActionResultHolder* PerformDefaultAction(const FunctionMockerBase<F>* p_func_mocker,
//                                                    const typename Function<F>::ArgumentTuple& p_args,
//                                                    const string& p_call_description) {
//        return new ActionResultHolder(p_func_mocker->PerformDefaultAction(p_args, p_call_description));
//    }

//    template <typename F>
//    static ActionResultHolder* PerformAction(const Action<F>& p_action,
//                                             const typename Function<F>::ArgumentTuple& p_args) {
//        return new ActionResultHolder(p_action.Perform(p_args));
//    }

//  private:
//    mutable UP m_value;

//    GTEST_DISALLOW_ASSIGN_(ActionResultHolder);
//};

//template <typename T>
//class ReturnUniquePtrAction {
//  public:
//    using UniquePtr       = std::unique_ptr<T>;
//    using SharedUniquePtr = std::shared_ptr<UniquePtr>;
//    explicit ReturnUniquePtrAction(UniquePtr p_ref) : m_ref(new UniquePtr(std::move(p_ref))) {
//    }

//    template <typename F>
//    operator Action<F>() const {
//        return Action<F>(new Impl<F>(m_ref));
//    }

//  private:
//    template <typename F>
//    class Impl : public ActionInterface<F> {
//      public:
//        typedef typename Function<F>::Result Result;
//        typedef typename Function<F>::ArgumentTuple ArgumentTuple;

//        explicit Impl(SharedUniquePtr p_ref) : m_ref(p_ref) {
//        }

//        virtual Result Perform(const ArgumentTuple&) {
//            if (m_performed) {
//                throw UniquePtrReturnedMoreThanOnce{};
//            }
//            m_performed = true;
//            return std::move(*m_ref);
//        }

//      private:
//        bool m_performed = false;
//        SharedUniquePtr mutable m_ref;

//        GTEST_DISALLOW_ASSIGN_(Impl);
//    };
//    SharedUniquePtr mutable m_ref;
//    GTEST_DISALLOW_ASSIGN_(ReturnUniquePtrAction);
//};

//}  // namespace internal

//template <typename T>
//internal::ReturnUniquePtrAction<T> ReturnUniquePtr(std::unique_ptr<T> p_ptr) {
//    return internal::ReturnUniquePtrAction<T>(std::move(p_ptr));
//}

//template <typename T>
//class DefaultValue<std::unique_ptr<T>> {
//  public:
//    static void Clear() {
//    }
//    static bool IsSet() {
//        return false;
//    }

//    static bool Exists() {
//        return true;
//    }

//    static std::unique_ptr<T> Get() {
//        return std::unique_ptr<T>();
//    }
//};

//}  // namespace testing
