/* Copyright 2017 The MathWorks, Inc. */

#ifndef EXECUTION_INTERFACE_HPP
#define EXECUTION_INTERFACE_HPP

#include "util.hpp"
#include <MatlabDataArray/StructArray.hpp>
#include <MatlabDataArray/StructRef.hpp>
#include <MatlabDataArray/Struct.hpp>
#include <MatlabDataArray/CharArray.hpp>
#include <MatlabDataArray/TypedArray.hpp>
#include <MatlabDataArray/Reference.hpp>

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

namespace matlab {

    namespace execution {

        class ExecutionInterface {
        public:
            
            /**
            * Evaluate a MATLAB function synchronously
            *
            * @param function - The name of a MATLAB function
            * @param nlhs - The number of output to be expected
            * @param args - The arguments of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return std::vector<matlab::data::Array> - A vector of MATLAB Data Array as the result of the MATLAB function
            * 
            * @throw MATLABSyntaxException, MATLABExecutionException
            */
            std::vector<matlab::data::Array> feval(const std::u16string &function,
                                                   const size_t nlhs,
                                                   const std::vector<matlab::data::Array> &args,
                                                   const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                   const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );
            
            /**
            * @overload
            */
            std::vector<matlab::data::Array> feval(const std::string &function,
                                                   const size_t nlhs,
                                                   const std::vector<matlab::data::Array> &args,
                                                   const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                   const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );
            
            /**
            * Evaluate a MATLAB function synchronously
            *
            * @param function - The name of a MATLAB function
            * @param args - The arguments of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return matlab::data::Array - A MATLAB Data Array as the result of the MATLAB function
            *
            * @throw MATLABSyntaxException, MATLABExecutionException
            */
            matlab::data::Array feval(const std::u16string &function,
                                      const std::vector<matlab::data::Array> &args,
                                      const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                      const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * @overload
            */
            matlab::data::Array feval(const std::string &function,
                                      const std::vector<matlab::data::Array> &args,
                                      const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                      const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * Evaluate a MATLAB function synchronously
            *
            * @param function - The name of a MATLAB function
            * @param arg - The argument of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return matlab::data::Array - A MATLAB Data Array as the result of the MATLAB function
            *
            * @throw MATLABSyntaxException, MATLABExecutionException
            */
            matlab::data::Array feval(const std::u16string &function,
                                      const matlab::data::Array &arg,
                                      const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                      const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * @overload
            */
            matlab::data::Array feval(const std::string &function,
                                      const matlab::data::Array &arg,
                                      const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                      const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * Evaluate a MATLAB function synchronously
            *
            * @param function - The name of a MATLAB function
            * @param rhsArgs - The arguments of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return ReturnType - The result of the MATLAB function
            *
            * @throw MATLABSyntaxException, MATLABExecutionException
            */
            template<class ReturnType, typename...RhsArgs>
            ReturnType feval(const std::u16string &function,
                    const std::shared_ptr<StreamBuffer> &output,
                    const std::shared_ptr<StreamBuffer> &error, 
                    RhsArgs&&... rhsArgs
            );
            
            /**
            * @overload
            */
            template<class ReturnType, typename...RhsArgs>
            ReturnType feval(const std::string &function,
                    const std::shared_ptr<StreamBuffer> &output,
                    const std::shared_ptr<StreamBuffer> &error, 
                    RhsArgs&&... rhsArgs
            );
            
            /**
            * Evaluate a MATLAB function synchronously
            *
            * @param function - The name of a MATLAB function
            * @param rhsArgs - The arguments of the MATLAB function
            * @return ReturnType - The result of the MATLAB function
            *
            * @throw MATLABSyntaxException, MATLABExecutionException
            */
            template<class ReturnType, typename...RhsArgs>
            ReturnType feval(const std::u16string &function,
                    RhsArgs&&... rhsArgs
            );
            
            /**
            * @overload
            */
            template<class ReturnType, typename...RhsArgs>
            ReturnType feval(const std::string &function,
                    RhsArgs&&... rhsArgs
            );

            /**
            * Evaluate a MATLAB function asynchronously
            *
            * @param function - The name of a MATLAB function
            * @param nlhs - The number of output to be expected
            * @param args - The arguments of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return FutureResult<std::vector<matlab::data::Array>> - A future of a vector of MATLAB Data Array as the result of the MATLAB function
            *
            * @throw none
            */
            FutureResult<std::vector<matlab::data::Array> > fevalAsync(const std::u16string &function,
                                                                       const size_t nlhs, 
                                                                       const std::vector<matlab::data::Array> &args,
                                                                       const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                                       const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * @overload
            */
            FutureResult<std::vector<matlab::data::Array> > fevalAsync(const std::string &function,
                                                                       const size_t nlhs, 
                                                                       const std::vector<matlab::data::Array> &args,
                                                                       const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                                       const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * Evaluate a MATLAB function asynchronously
            *
            * @param function - The name of a MATLAB function
            * @param args - The arguments of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return FutureResult<matlab::data::Array> - A future of a MATLAB Data Array as the result of the MATLAB function
            *
            * @throw none
            */
            FutureResult<matlab::data::Array> fevalAsync(const std::u16string &function,
                                                         const std::vector<matlab::data::Array> &args,
                                                         const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                         const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * @overload
            */
            FutureResult<matlab::data::Array> fevalAsync(const std::string &function,
                                                         const std::vector<matlab::data::Array> &args,
                                                         const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                         const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * Evaluate a MATLAB function asynchronously
            *
            * @param function - The name of a MATLAB function
            * @param arg - The argument of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return FutureResult<matlab::data::Array> - A future of a MATLAB Data Array as the result of the MATLAB function
            *
            * @throw none
            */
            FutureResult<matlab::data::Array> fevalAsync(const std::u16string &function,
                                                         const matlab::data::Array &arg,
                                                         const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                         const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * @overload
            */
            FutureResult<matlab::data::Array> fevalAsync(const std::string &function,
                                                         const matlab::data::Array &arg,
                                                         const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                                                         const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>()
            );

            /**
            * Evaluate a MATLAB function asynchronously
            *
            * @param function - The name of a MATLAB function
            * @param rhsArgs - The arguments of the MATLAB function
            * @param output - The stream used to redirect standard output generated by MATLAB
            * @param error - The stream used to redirect standard error generated by MATLAB
            * @return FutureResult<ReturnType> - A future to the result of the MATLAB function
            *
            * @throw none
            */
            template<class ReturnType, typename...RhsArgs>
            FutureResult<ReturnType> fevalAsync(const std::u16string &function,
                                       const std::shared_ptr<StreamBuffer> &output,
                                       const std::shared_ptr<StreamBuffer> &error,
                                       RhsArgs&&... rhsArgs
            );
            
            /**
            * @overload
            */
            template<class ReturnType, typename...RhsArgs>
            FutureResult<ReturnType> fevalAsync(const std::string &function,
                                       const std::shared_ptr<StreamBuffer> &output,
                                       const std::shared_ptr<StreamBuffer> &error,
                                       RhsArgs&&... rhsArgs
            );
            
            /**
            * Evaluate a MATLAB function asynchronously
            *
            * @param function - The name of a MATLAB function
            * @param rhsArgs - The arguments of the MATLAB function
            * @return FutureResult<ReturnType> - A future to the result of the MATLAB function
            *
            * @throw none
            */
            template<class ReturnType, typename...RhsArgs>
            FutureResult<ReturnType> fevalAsync(const std::u16string &function,
                                       RhsArgs&&... rhsArgs
            );
            
            /**
            * @overload
            */
            template<class ReturnType, typename...RhsArgs>
            FutureResult<ReturnType> fevalAsync(const std::string &function,
                                       RhsArgs&&... rhsArgs
            );
            
            /**
            * Constructor
            *
            * @param handle - The internal implementation
            * 
            * @throw none
            */
            ExecutionInterface(uint64_t handle);

            /**
            * Destructor
            *
            * @throw none
            */
            ~ExecutionInterface();
        protected:
            uint64_t matlabHandle;
        private:
            std::string convertUTF16StringToASCIIString(const std::u16string &str);
        };

        void writeToStreamBuffer(void* buffer, const char16_t* stream, size_t n);
        void deleteStreamBufferImpl(void* impl);
    }
}

#endif /* EXECUTION_INTERFACE_HPP */