/* Copyright 2017 The MathWorks, Inc. */

#ifndef MATLAB_ENGINE_HPP
#define MATLAB_ENGINE_HPP

#include <MatlabExecutionInterface/execution_interface.hpp>
#include "engine_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 engine {
        using namespace matlab::execution; 
        
        class MATLABEngine : public matlab::execution::ExecutionInterface {
        public:
            
            
            /**
            * 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 MATLABSyntaxException, MATLABExecutionException
            */
            void eval(const std::u16string &statement,
                      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 MATLABExecutionException
            */
            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 none
            */
            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 - A scalar object
            * @param propertyName - The name of the property to get for the object
            * @return matlab::data::Array - The property from the object
            *
            * @throw MATLABExecutionException
            */
            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);

            /**
            * Obtain the value of an object property
            *
            * @param objectArray - An object array
            * @param index - The linear zero-based index of the object in the array
            * @param propertyName - The name of the property to get for the object
            * @return matlab::data::Array - The property from the object
            *
            * @throw MATLABExecutionException
            */
            matlab::data::Array getProperty(const matlab::data::Array &objectArray, size_t index, const std::u16string &propertyName);
            
            /**
            * @overload
            */
            matlab::data::Array getProperty(const matlab::data::Array &objectArray, size_t index, const std::string &propertyName);

            /**
            * Set a property value of an object
            *
            * @param object - A scalar object
            * @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 MATLABExecutionException
            */
            void setProperty(matlab::data::Array &object, const std::u16string &propertyName, const matlab::data::Array &property);

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

            /**
            * Set a property value of an object
            *
            * @param objectArray - An object array
            * @param index - The linear zero-based index of the object in the 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 MATLABExecutionException
            */
            void setProperty(matlab::data::Array &objectArray, size_t index, const std::u16string &propertyName, const matlab::data::Array &property);

            /**
            * @overload
            */
            void setProperty(matlab::data::Array &objectArray, size_t index, const std::string &propertyName, const matlab::data::Array &property);
        
            /**
            * 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 to the evaluate of the MATLAB statement
            *
            * @throw none
            */
            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 to get the variable returned from MATLAB base or global workspace
            *
            * @throw none
            */
            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 to the operation
            *
            * @throw none
            */
            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 - A scalar object
            * @param propertyName - The name of the property to get for the object
            * @return FutureResult<matlab::data::Array> - A future to get the property from the object
            *
            * @throw none
            */
            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);

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

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

            /**
            * Set a property value of an object asynchronously
            *
            * @param object - A scalar object
            * @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 to the operation
            *
            * @throw none
            */
            FutureResult<void> setPropertyAsync(matlab::data::Array &object, const std::u16string &propertyName, const matlab::data::Array &property);

            /**
            * @overload
            */
            FutureResult<void> setPropertyAsync(matlab::data::Array &object, const std::string &propertyName, const matlab::data::Array &property);
            
            /**
            * Set a property value of an object asynchronously
            *
            * @param objectArray - An object array
            * @param index - The linear zero-based index of the object in the 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 to the operation
            *
            * @throw none
            */
            FutureResult<void> setPropertyAsync(matlab::data::Array &objectArray, size_t index, const std::u16string &propertyName, const matlab::data::Array &property);

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

            /**
            * Constructor
            *
            * @param handle - The internal implementation
            * 
            * @throw none
            */
            MATLABEngine(uint64_t handle);

            /**
            * Destructor
            *
            * @throw none
            */
            ~MATLABEngine();

        };
    }
}

#endif /* MATLAB_ENGINE_HPP */