/* Copyright 2017 The MathWorks, Inc. */

#ifndef VALUE_FUTURE_HPP
#define VALUE_FUTURE_HPP

#include "util.hpp"

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

namespace matlab {

    namespace execution {
        
        
        template <class T>
        class FutureResult;

        template<class T>
        class SharedFutureResult;

        template <class T>
        class FutureResult : public std::future<T> {
        public:
            FutureResult();

            ~FutureResult();

            /**
            * Swap the contents of this FutureResult with another FutureResult
            *
            * @param a_futureresult -  A FutureResult
            * @return void
            *
            * @throw none
            */
            void swap(FutureResult<T>& a_futureresult);

            /**
            * A constructor of FutureResult that accepts a standard future
            *
            * @param a_future - A standard future
            *
            * @throw none
            */
            FutureResult(std::future<T>&& a_future);

            /**
            * A constructor of FutureResult that accepts a standard future and a task reference
            *
            * @param a_future - A standard future
            * @param a_taskreference - A shared pointer of task reference
            *
            * @throw none
            */
            FutureResult(std::future<T>&& a_future, std::shared_ptr<TaskReference> a_taskreference);

            /**
            * A constructor of FutureResult that accepts a standard future, a task reference, and buffers
            *
            * @param a_future - A standard future
            * @param a_taskreference - A shared pointer of task reference
            * @param outputBuffer - A shared pointer of StreamBuffer used to store standard output
            * @param errorBuffer - A shared pointer of StreamBuffer used to store standard error
            *
            * @throw none
            */
            FutureResult(std::future<T>&& a_future,
                const std::shared_ptr<TaskReference>& a_taskreference,
                const std::shared_ptr<StreamBuffer>& outputBuffer,
                const std::shared_ptr<StreamBuffer>& errorBuffer
            );

            /**
            * Move constructor of FutureResult
            *
            * @param a_futureresult - A future result
            *
            * @throw none
            */
            FutureResult(FutureResult<T>&& a_futureresult);

            /**
            * Assignment operator
            *
            * @param rhs - A future result
            *
            * @throw none
            */
            FutureResult<T>& operator=(FutureResult<T>&& rhs);

            /**
            * Create a shared copy of this future result
            *
            * @return a SharedFutureResult
            *
            * @throw none
            */
            SharedFutureResult<T> share();

            /**
            * Get the result of MATLAB function call
            *
            * @return the result
            *
            * @throw MatlabSyntaxException, MatlabExecutionException, TypeConversionException, MatlabNotAvailableException, CancelledException, InterruptedException
            */
            T get();


            /**
            * Check the future result is valid or not
            *
            * @return true if valid; false otherwise
            *
            * @throw none
            */
            bool valid() const;

            /**
            * Wait until the result is ready
            *
            * @throw none
            */
            void wait() const;

            /**
            * Wait until certain time point
            *
            * @param abs_time - A  time point
            * @return the status of the future result
            *
            * @throw none
            */
            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>

            /**
            * Wait for certain amount of time
            *
            * @param rel_time - Time during to wait
            * @return the status of the future result
            *
            * @throw none
            */
            std::future_status wait_for(const std::chrono::duration<Rep, Period>& rel_time) const;

            /**
            * Cancel the execution of a MATLAB command
            *
            * @param allowInterrupt - Interrupt the command or not if it is being processed
            * @return the request is cancel-able or not
            *
            * @throw none
            */
            bool cancel(bool allowInterrupt = true);

            /**
            * Get the task reference
            *
            * @return the shared pointer of the task reference
            *
            * @throw none
            */
            std::shared_ptr<TaskReference> getTaskReference();

        private:
            FutureResult(std::future<T>&) = delete;
            FutureResult(const FutureResult&) = delete;
            FutureResult& operator= (FutureResult&) = delete;

            std::future<T> future;
            std::shared_ptr<TaskReference> taskReference;
            std::weak_ptr<StreamBuffer> output;
            std::weak_ptr<StreamBuffer> error;
            friend SharedFutureResult<T>;
        };

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

            ~SharedFutureResult();

            /**
            * Swap the contents of this SharedFutureResult with another SharedFutureResult
            *
            * @param a_sharedfuture - A SharedFutureResult
            * @return void
            *
            * @throw none
            */
            void swap(SharedFutureResult<T>& a_sharedfuture);

            /**
            * Copy constructor
            */
            SharedFutureResult(const SharedFutureResult& a_sharedfuture);

            /**
            * Move constructor
            */
            SharedFutureResult(SharedFutureResult&& a_sharedfuture);

            /**
            * Move constructor that accepts a FutureResult
            */
            SharedFutureResult(FutureResult<T>&& a_futureresult);

            /**
            * Move assignment operator
            *
            * @param rhs - A shared future result
            *
            * @throw none
            */
            SharedFutureResult<T>& operator=(SharedFutureResult<T>&& rhs);

            /**
            * Assignment operator
            *
            * @param rhs - A shared future result
            *
            * @throw none
            */
            SharedFutureResult<T>& operator=(const SharedFutureResult<T>& rhs);

            /**
            * Get the result of MATLAB function call
            *
            * @return the result
            *
            * @throw MatlabSyntaxException, MatlabExecutionException, TypeConversionException, MatlabNotAvailableException, CancelledException, InterruptedException
            */
            decltype(std::declval<std::shared_future<T>>().get()) get() const;

            /**
            * Check the future result is valid or not
            *
            * @return true if valid; false otherwise
            *
            * @throw none
            */
            bool valid() const;

            /**
            * Wait until the result is ready
            *
            * @throw none
            */
            void wait() const;

            /**
            * Wait until certain time point
            *
            * @param abs_time - A  time point
            * @return the status of the future result
            *
            * @throw none
            */
            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>

            /**
            * Wait for certain amount of time
            *
            * @param rel_time - Time during to wait
            * @return the status of the future result
            *
            * @throw none
            */
            std::future_status wait_for(const std::chrono::duration<Rep, Period>& rel_time) const;

            /**
            * Cancel the execution of a MATLAB command
            *
            * @param allowInterrupt - Interrupt the command or not if it is being processed
            * @return the request is cancel-able or not
            *
            * @throw none
            */
            bool cancel(bool allowInterrupt = true);

        private:
            std::shared_future<T> sharedFuture;
            std::shared_ptr<TaskReference> taskReference;
        };
    }
}

#endif /* VALUE_FUTURE_HPP */