/* Copyright 2017 The MathWorks, Inc. */
 
#ifndef CPPSHAREDLIB_FUTURE_HPP
#define CPPSHAREDLIB_FUTURE_HPP

#include <vector>
#include <streambuf>
#include <memory>
#include <future>

namespace matlab {
    namespace cpplib {
        class MATLABLibrary;
    }
}

namespace matlab {

    namespace execution {
        
        using namespace matlab::cpplib;
        
        namespace runtime_future_detail {
            struct Impl {
                uint64_t handle;
                bool state;
                bool cancelled;
                bool interrupted;
                Impl();
                Impl(uint64_t aHandle);
                ~Impl();
            };
        }

        template <>
        class FutureResult<std::unique_ptr<MATLABLibrary>>: public std::future<std::unique_ptr<MATLABLibrary>>{
        public:
            FutureResult(std::shared_ptr<runtime_future_detail::Impl> other);

            FutureResult(std::future<std::unique_ptr<MATLABLibrary>>&& rhs);

            FutureResult(FutureResult<std::unique_ptr<MATLABLibrary>>&& rhs);

            FutureResult<std::unique_ptr<MATLABLibrary>>& operator=(FutureResult<std::unique_ptr<MATLABLibrary>>&& rhs);

            FutureResult();

            ~FutureResult();

            void swap(FutureResult<std::unique_ptr<MATLABLibrary>>& rhs);

            std::unique_ptr<MATLABLibrary> get();

            SharedFutureResult<std::unique_ptr<MATLABLibrary>> share();

            bool valid() const;

            void wait() const;

            template<class Clock, class Duration>
            std::future_status wait_until(const std::chrono::time_point<Clock, Duration>& abs_time) const;
            template<class Rep, class Period>

            std::future_status wait_for(const std::chrono::duration<Rep, Period>& rel_time) const;
           
            bool cancel(bool allowInterrupt = true);

        private:
            FutureResult(std::future<std::unique_ptr<MATLABLibrary>>&) = delete;
            FutureResult(FutureResult&) = delete;
            FutureResult& operator= (FutureResult&) = delete;
            std::future<std::unique_ptr<MATLABLibrary>> future;
            std::shared_ptr<runtime_future_detail::Impl> impl;
            friend SharedFutureResult<std::unique_ptr<MATLABLibrary>>;
        };

        template <>
        class SharedFutureResult<std::unique_ptr<MATLABLibrary>>: public std::shared_future<std::unique_ptr<MATLABLibrary>>{
        public:
            SharedFutureResult();

            ~SharedFutureResult();

            void swap(SharedFutureResult<std::unique_ptr<MATLABLibrary>>& rhs);

            SharedFutureResult(const SharedFutureResult& rhs);

            SharedFutureResult(SharedFutureResult&& rhs);

            SharedFutureResult(FutureResult<std::unique_ptr<MATLABLibrary>>&& rhs);

            SharedFutureResult<std::unique_ptr<MATLABLibrary>>& operator=(SharedFutureResult<std::unique_ptr<MATLABLibrary>>&& rhs);

            SharedFutureResult<std::unique_ptr<MATLABLibrary>>& operator=(const SharedFutureResult<std::unique_ptr<MATLABLibrary>>& rhs);

            const std::unique_ptr<MATLABLibrary>& get() const;

            bool valid() const;

            void wait() const;

            template<class Clock, class Duration>
            std::future_status wait_until(const std::chrono::time_point<Clock, Duration>& abs_time) const;
            
            template<class Rep, class Period>
            std::future_status wait_for(const std::chrono::duration<Rep, Period>& rel_time) const;
            
            bool cancel(bool allowInterrupt = true);

        private:
            std::shared_future<std::unique_ptr<MATLABLibrary>> sharedFuture;
            std::shared_ptr<runtime_future_detail::Impl> impl;
        };
    }
}

namespace matlab {
    namespace execution { 

        namespace runtime_future_detail {
            inline Impl::Impl(uint64_t aHandle) : handle(aHandle), state(true), cancelled(false), interrupted(false) {
            }
            inline Impl::Impl() : handle(0), state(true), cancelled(false), interrupted(false) {
            }
            inline Impl::~Impl(){
            }
        }

        inline FutureResult<std::unique_ptr<MATLABLibrary>>::FutureResult() : std::future<std::unique_ptr<MATLABLibrary>>(), future(), impl() {}

        inline FutureResult<std::unique_ptr<MATLABLibrary>>::FutureResult(std::shared_ptr<runtime_future_detail::Impl> rhs) : std::future<std::unique_ptr<MATLABLibrary>>(), future(), impl(rhs) {
        }

        inline void FutureResult<std::unique_ptr<MATLABLibrary>>::swap(FutureResult<std::unique_ptr<MATLABLibrary>>& rhs) {
            impl.swap(rhs.impl);
            std::swap(future, rhs.future);
            std::swap(*static_cast<std::future<std::unique_ptr<MATLABLibrary>>*>(this), static_cast<std::future<std::unique_ptr<MATLABLibrary>>&>(rhs));
        }

        inline FutureResult<std::unique_ptr<MATLABLibrary>>::FutureResult(std::future<std::unique_ptr<MATLABLibrary>>&& rhs) : std::future<std::unique_ptr<MATLABLibrary>>(), future(std::move(rhs)), impl() {

        }

        inline FutureResult<std::unique_ptr<MATLABLibrary>>::FutureResult(FutureResult<std::unique_ptr<MATLABLibrary>>&& rhs) : std::future<std::unique_ptr<MATLABLibrary>>(), future(), impl() {
            swap(rhs);
        }

        inline FutureResult<std::unique_ptr<MATLABLibrary>>& FutureResult<std::unique_ptr<MATLABLibrary>>::operator=(FutureResult<std::unique_ptr<MATLABLibrary>>&& rhs) {
            swap(rhs);
            return *this;
        }

        inline FutureResult<std::unique_ptr<MATLABLibrary>>::~FutureResult() {
        }
        
        inline std::unique_ptr<MATLABLibrary> FutureResult<std::unique_ptr<MATLABLibrary>>::get() {
            return future.get();
        }

        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>> FutureResult<std::unique_ptr<MATLABLibrary>>::share() {
            return SharedFutureResult<std::unique_ptr<MATLABLibrary>>(std::move(*this));
        }

        inline bool FutureResult<std::unique_ptr<MATLABLibrary>>::valid() const {
            return future.valid();
        }

        inline void FutureResult<std::unique_ptr<MATLABLibrary>>::wait() const {
            return future.wait();
        }

        template<class Clock, class Duration>
        std::future_status FutureResult<std::unique_ptr<MATLABLibrary> >::wait_until(const std::chrono::time_point<Clock, Duration>& abs_time) const {
            return future.wait_until(abs_time);
        }

        template<class Rep, class Period>
        std::future_status FutureResult<std::unique_ptr<MATLABLibrary> >::wait_for(const std::chrono::duration<Rep, Period>& rel_time) const {
            return future.wait_for(rel_time);
        }

        inline bool FutureResult<std::unique_ptr<MATLABLibrary> >::cancel(bool allowInterrupt) {
            /*if (allowInterrupt) {
                std::async(std::launch::async, [this](){ 
                    std::unique_ptr<MATLABLibrary> enginePtr = future.get();
                    enginePtr->disconnect();
                });
                impl->interrupted = true;
                return true;
            }
            impl->interrupted = false;
            impl->cancelled = false;*/
            return false;
        }


        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>>::SharedFutureResult() : std::shared_future<std::unique_ptr<MATLABLibrary>>(), sharedFuture(), impl() {
        }

        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>>::~SharedFutureResult() {
        }

        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>>::SharedFutureResult(const SharedFutureResult& rhs) : std::shared_future<std::unique_ptr<MATLABLibrary>>(), sharedFuture(rhs.sharedFuture), impl(rhs.impl) {
        }

        inline void SharedFutureResult<std::unique_ptr<MATLABLibrary>>::swap(SharedFutureResult<std::unique_ptr<MATLABLibrary>>& rhs) {
            impl.swap(rhs.impl);
            std::swap(sharedFuture, rhs.sharedFuture);
            std::swap(*static_cast<std::shared_future<std::unique_ptr<MATLABLibrary>>*>(this), static_cast<std::shared_future<std::unique_ptr<MATLABLibrary>>&>(rhs));
        }


        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>>::SharedFutureResult(SharedFutureResult&& rhs) : std::shared_future<std::unique_ptr<MATLABLibrary>>(), sharedFuture(), impl() {
            swap(rhs);
        }

        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>>::SharedFutureResult(FutureResult<std::unique_ptr<MATLABLibrary>>&& rhs) : std::shared_future<std::unique_ptr<MATLABLibrary>>(), sharedFuture(std::move(rhs.future)), impl() {
            impl.swap(rhs.impl);
        }

        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>>& SharedFutureResult<std::unique_ptr<MATLABLibrary>>::operator=(SharedFutureResult<std::unique_ptr<MATLABLibrary>>&& rhs) {
            swap(rhs);
            return *this;
        }

        inline SharedFutureResult<std::unique_ptr<MATLABLibrary>>& SharedFutureResult<std::unique_ptr<MATLABLibrary>>::operator=(const SharedFutureResult<std::unique_ptr<MATLABLibrary>>& rhs) {
            *(static_cast<std::shared_future<std::unique_ptr<MATLABLibrary>>*>(this)) = rhs;
            sharedFuture = rhs.sharedFuture;
            impl = rhs.impl;
            return *this;
        }

        inline const std::unique_ptr<MATLABLibrary>& SharedFutureResult<std::unique_ptr<MATLABLibrary>>::get() const {
            return sharedFuture.get();
        }

        inline bool SharedFutureResult<std::unique_ptr<MATLABLibrary>>::valid() const {
            return sharedFuture.valid();
        }

        inline void SharedFutureResult<std::unique_ptr<MATLABLibrary>>::wait() const {
            return sharedFuture.wait();
        }

        template<class Clock, class Duration>
        std::future_status SharedFutureResult<std::unique_ptr<MATLABLibrary>>::wait_until(const std::chrono::time_point<Clock, Duration>& abs_time) const {
            return sharedFuture.wait_until(abs_time);
        }

        template<class Rep, class Period>
        std::future_status SharedFutureResult<std::unique_ptr<MATLABLibrary>>::wait_for(const std::chrono::duration<Rep, Period>& rel_time) const {
            return sharedFuture.wait_for(rel_time);
        }

        inline bool SharedFutureResult<std::unique_ptr<MATLABLibrary>>::cancel(bool allowInterrupt) {
            if (allowInterrupt) {
                //TODO
                //engine_cancel_matlab_async(impl->handle);
                impl->interrupted = true;
                return true;
            }
            impl->interrupted = false;
            impl->cancelled = false;
            return false;
        }

    }
}
#endif // CPPSHAREDLIB_FUTURE_HPP