% Implementation of AbstractRemoteFuture backed by MCOS classes
% This requires that the remote MATLAB function has the return arguments
% [ok, result, warnings]
% Where ok == false indicates that result is a MATLAB exception

% Copyright 2020 The MathWorks, Inc.

classdef RemoteFutureImpl < parallel.internal.pool.AbstractRemoteFuture

    properties (SetAccess = immutable, GetAccess = private)
        % MCOS remote MATLAB method evaluation future
        MCOSFuture
    end
    
    properties (Access = private)
        % Exception thrown during remote evaluation not captured in MATLAB
        % result
        Exception
        % Flag indicating if Result is a MATLAB error
        IsError
        % Result which is set once the future completes
        Result
        % Warnings which is set once the future completes
        Warnings
        % Flag to indicate that get() has been called on the MCOSFuture and
        % results are now stored on this class
        Completed
    end
    
    % Implement AbstractFuture methods
    methods
        function obj = RemoteFutureImpl(future)
            obj.MCOSFuture = future;
            obj.Exception = MException.empty();
            obj.IsError = false;
            obj.Result = [];
            obj.Warnings = matlab.internal.diagnostic.Warning.empty();
            obj.Completed = false;
        end
        
        function completed = await(obj, checkFcn)
            completed = true;
            if obj.Completed
                return;
            end
            if nargin < 2
                checkFcn = @iAlwaysWaitFcn;
            end
            
            % Poll checkFcn every 100ms
            while ~obj.MCOSFuture.waitForMillis(100)
                if ~checkFcn()
                    completed = false;
                    break;
                end
                parallel.internal.pool.yield();
            end
        end

        function result = getResult(obj)
            obj.ensureComplete();
            if ~isempty(obj.Exception) 
                throwAsCaller(obj.Exception);
            end
            result = obj.Result;
            if obj.IsError
                throwAsCaller(result);
            end
        end

        function warnings = getWarnings(obj)
            obj.ensureComplete();
            warnings = obj.Warnings;
        end
    end

    methods (Access = private)
        function ensureComplete(obj)
            if ~obj.Completed
                obj.Completed = true;
                try
                    result = obj.MCOSFuture.get();
                    obj.Exception = parallel.internal.pool.handleRemoteException(result.Exception);
                    obj.IsError = ~result.MatlabOKFlag;
                    obj.Result = result.Result;
                    obj.Warnings = result.Warnings;
                catch E
                    obj.Exception = E;
                end
            end
        end
    end
end

function continueWait = iAlwaysWaitFcn()
    continueWait = true;
end
