/**
 * Published header for C++ MEX MatlabEngine
 *
 * Copyright 2017-2018 The MathWorks, Inc.
 */

#ifndef __MEX_MATLAB_ENGINE_HPP__
#define __MEX_MATLAB_ENGINE_HPP__

#include <vector>
#include <memory>
#include <string>
#include "mexFuture.hpp"

namespace matlab {

    namespace data {
        class Array;
    }

    namespace engine {

        typedef std::basic_streambuf<char16_t> StreamBuffer;

        enum class WorkspaceType {
            BASE = 0,
            GLOBAL = 1
        };

        std::basic_string<char16_t> convertUTF8StringToUTF16String(const std::string &utf8string);
        std::string convertUTF16StringToUTF8String(const std::basic_string<char16_t> &utf16string);


        class MATLABEngine {
          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 MATLABException
             */
            std::vector<matlab::data::Array> feval(const std::u16string &function,
                                                   const int 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 int 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 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 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 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 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 statement synchronously
             *
             * @param statement- The MATLAB statement to be evaluated
             * @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 none
             *
             * @throw MATLABExecutionException
             */
            void eval(const std::u16string &str,
                      const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                      const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>());

            /**
             * Obtain a variable from the MATLAB base or global workspace
             *
             * @param varName - The name of a MATLAB variable in the base or global workspace
             * @return matlab::data::Array - The variable returned from MATLAB base or global workspace
             *
             * @throw MATLABException
             */
            matlab::data::Array getVariable(const std::u16string &varName,
                                            WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
             * @overload
             */
            matlab::data::Array getVariable(const std::string &varName,
                                            WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
             * Send a variable to the MATLAB base or global workspace
             *
             * @param varName - The name of a MATLAB variable in the base or global workspace
             * @param var - The variable to be sent to the MATLAB base or global workspace
             * @return none
             *
             * @throw MATLABException
             */
            void setVariable(const std::u16string &varName,
                             const matlab::data::Array &var,
                             WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
             * @overload
             */
            void setVariable(const std::string &varName,
                             const matlab::data::Array &var,
                             WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
            * Obtain the value of an object property
            *
            * @param object - An object array
            * @param index - The index of the object of the object array
            * @param propertyName - The name of the property to set for the object
            * @return matlab::data::Array - The property from the object
            *
            * @throw MATLABException
            */
            matlab::data::Array getProperty(const matlab::data::Array &object,
                                            size_t index,
                                            const std::u16string &propertyName);

            /**
             * @overload
             */
            matlab::data::Array getProperty(const matlab::data::Array &object,
                                            size_t index,
                                            const std::string &propertyName);

            /**
            * Obtain the value of an object property
            *
            * @param object - An object array
            * @param propertyName - The name of the property to set for the object
            * @return matlab::data::Array - The property from the object
            *
            * @throw MATLABException
            */
            matlab::data::Array getProperty(const matlab::data::Array &object,
                                            const std::u16string &propertyName);

            /**
             * @overload
             */
            matlab::data::Array getProperty(const matlab::data::Array &object,
                                            const std::string &propertyName);

            /**
            * Set a property value of an object
            *
            * @param object - An object array
            * @param index - The index of the object of the object array
            * @param propertyName - The name of the property to set for the object
            * @param property - The value of the property to set for the object
            * @return none
            *
            * @throw MATLABException
            */
            void setProperty(matlab::data::Array &object,
                             size_t index,
                             const std::u16string &propertyName,
                             const matlab::data::Array &value);

            /**
             * @overload
             */
            void setProperty(matlab::data::Array &object,
                             size_t index,
                             const std::string &propertyName,
                             const matlab::data::Array &value);

            /**
            * Set a property value of an object
            *
            * @param object - An object array
            * @param propertyName - The name of the property to set for the object
            * @param property - The value of the property to set for the object
            * @return none
            *
            * @throw MATLABException
            */
            void setProperty(matlab::data::Array &object,
                             const std::u16string &propertyName,
                             const matlab::data::Array &value);

            /**
             * @overload
             */
            void setProperty(matlab::data::Array &object,
                             const std::string &propertyName,
                             const matlab::data::Array &value);

            /**
            * 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
            */
            FutureResult<std::vector<matlab::data::Array>> fevalAsync(const std::u16string &function,
                const int 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 int 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
            */
            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
            */
            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 of the result of the MATLAB function
            */
            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 of the result of the MATLAB function
            */
            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);

            /**
            * Evaluate a MATLAB statement asynchronously
            *
            * @param statement- The MATLAB statement to be evaluated
            * @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<void> - A future of the MATLAB command
            */
            FutureResult<void> evalAsync(const std::u16string &str,
                const std::shared_ptr<StreamBuffer> &output = std::shared_ptr<StreamBuffer>(),
                const std::shared_ptr<StreamBuffer> &error = std::shared_ptr<StreamBuffer>());

            /**
            * Obtain a variable from the MATLAB base or global workspace asynchronously
            *
            * @param varName - The name of a MATLAB variable in the base or global workspace
            * @return FutureResult<matlab::data::Array> - A future of the variable returned from MATLAB base or global workspace
            */
            FutureResult<matlab::data::Array> getVariableAsync(const std::u16string &varName,
                WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
            * @overload
            */
            FutureResult<matlab::data::Array> getVariableAsync(const std::string &varName,
                WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
            * Send a variable to the MATLAB base or global workspace asynchronously
            *
            * @param varName - The name of a MATLAB variable in the base or global workspace
            * @param var - The variable to be sent to the MATLAB base or global workspace
            * @return FutureResult<void> - A future of the MATLAB command
            */
            FutureResult<void> setVariableAsync(const std::u16string &varName,
                const matlab::data::Array &var,
                WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
            * @overload
            */
            FutureResult<void> setVariableAsync(const std::string &varName,
                const matlab::data::Array &var,
                WorkspaceType workspaceType = WorkspaceType::BASE);

            /**
            * Obtain the value of an object property asynchronously
            *
            * @param object - An object array
            * @param index - The index of the object of the object array
            * @param propertyName - The name of the property to set for the object
            * @return FutureResult<matlab::data::Array> - A future of the property from the object
            */
            FutureResult<matlab::data::Array> getPropertyAsync(const matlab::data::Array &object,
                size_t index,
                const std::u16string &propertyName);

            /**
            * @overload
            */
            FutureResult<matlab::data::Array> getPropertyAsync(const matlab::data::Array &object,
                size_t index,
                const std::string &propertyName);

            /**
            * Obtain the value of an object property asynchronously
            *
            * @param object - An object array
            * @param propertyName - The name of the property to set for the object
            * @return FutureResult<matlab::data::Array> - A future of the property from the object
            */
            FutureResult<matlab::data::Array> getPropertyAsync(const matlab::data::Array &object,
                const std::u16string &propertyName);

            /**
            * @overload
            */
            FutureResult<matlab::data::Array> getPropertyAsync(const matlab::data::Array &object,
                const std::string &propertyName);

            /**
            * Set a property value of an object asynchronously
            *
            * @param object - An object array
            * @param index - The index of the object of the object array
            * @param propertyName - The name of the property to set for the object
            * @param property - The value of the property to set for the object
            * @return FutureResult<void> - A future of the MATLAB command
            */
            FutureResult<void> setPropertyAsync(matlab::data::Array &object,
                size_t index,
                const std::u16string &propertyName,
                const matlab::data::Array &value);

            /**
            * @overload
            */
            FutureResult<void> setPropertyAsync(matlab::data::Array &object,
                size_t index,
                const std::string &propertyName,
                const matlab::data::Array &value);

            /**
            * Set a property value of an object asynchronously
            *
            * @param object - An object array
            * @param propertyName - The name of the property to set for the object
            * @param property - The value of the property to set for the object
            * @return FutureResult<void> - A future of the MATLAB command
            */
            FutureResult<void> setPropertyAsync(matlab::data::Array &object,
                const std::u16string &propertyName,
                const matlab::data::Array &value);

            /**
            * @overload
            */
            FutureResult<void> setPropertyAsync(matlab::data::Array &object,
                const std::string &propertyName,
                const matlab::data::Array &value);

            MATLABEngine(void* a_pImpl) :pImpl(a_pImpl) {}
            virtual ~MATLABEngine(){ }

          private:
                void* pImpl;
        };
    }
}

#endif //__MEX_MATLAB_ENGINE_HPP__