% MJSTask Task for MJS clusters
%
%   parallel.task.MJSTask methods:
%      cancel                - Cancel a pending or running task
%      delete                - Remove a task object from its job and memory
%      listAutoAttachedFiles - List the files that are automatically attached for this task
%      wait                  - Wait for task object to change state
%
%   parallel.task.MJSTask properties:
%      CaptureDiary       - Specify whether to return diary output
%      CreateDateTime     - Date and time at which this task was created
%      Diary              - Text produced by execution of task object's function
%      Error              - Task error information
%      ErrorIdentifier    - Task error identifier
%      ErrorMessage       - Message from task error
%      FailureInfo        - Information returned from failed task
%      FinishDateTime     - Date and time at which this task finished running
%      FinishedFcn        - Callback executed when the job or task finishes
%      Function           - Function called when evaluating task
%      ID                 - Task's numeric identifier
%      InputArguments     - Input arguments to task function
%      MaximumRetries     - Maximum number of times to rerun failed task
%      Name               - Name of this task
%      NumFailures        - Number of times a task was rerun because it failed
%      NumOutputArguments - Number of arguments returned by task function
%      OutputArguments    - Output arguments from running Function on a worker
%      Parent             - Job containing this task
%      RunningDuration    - The duration the job has been running for, if it has started
%      RunningFcn         - Callback executed when the job or task starts running
%      StartDateTime      - Date and time at which this task started running
%      State              - Current state of task
%      Timeout            - Time limit to complete task
%      UserData           - Data associated with a task object
%      Warnings           - Task warning information
%      Worker             - Object representing the worker for this task
%
%   See also parallel.Task, parallel.Job, parallel.Job/createTask, 
%            parallel.Job/findTask.

% Copyright 2011-2021 The MathWorks, Inc.

classdef ( ConstructOnLoad = true ) MJSTask < parallel.Task & ...
        parallel.internal.cluster.MJSRunningCallbackMixin & ...
        parallel.internal.cluster.MJSFinishedCallbackMixin

    properties (Constant, Access = private)
        StateEnumToEventName = iBuildStateEnumToEventMap();
    end
        
    properties ( PCTGetSet, Transient, PCTConstraint = 'positivescalar|presubmission' )
        %MaximumRetries Maximum number of times to rerun failed task
        %   If a task cannot complete because of certain system failures,
        %   the cluster can attempt to rerun the task. MaximumRetries
        %   specifies how many times to try to run the task after such
        %   failures. The task reruns until it succeeds or until it reaches
        %   the specified maximum number of attempts.
        %
        %   See also parallel.task.MJSFailureInfo,
        %            parallel.task.MJSTask.FailureInfo,
        %            parallel.task.MJSTask.NumFailures.
        MaximumRetries = 1;
        
        %Timeout Time limit to complete task
        %   Timeout is a double value specifying the time limit in seconds
        %   before giving up on the task. The time for timeout begins
        %   counting when the task state changes from pending to running.
        %   When a task times out, the behavior of the task is the same as
        %   if the task were stopped with the cancel function, except a
        %   different message is placed in the task object's Error
        %   property.
        %
        %   The default value for Timeout is large enough so that in
        %   practice, tasks will never time out. You should set the value
        %   of Timeout to the number of seconds you want to allow for
        %   completion of tasks.
        %
        %   See also parallel.task.MJSTask.ErrorMessage,
        %            parallel.task.MJSTask.State.
        Timeout       = Inf;
    end
    properties ( PCTGetSet, Transient, SetAccess = private )
        %FailureInfo Information returned from failed task
        %   If a task reruns because of certain system failures, the task
        %   property FailureInfo stores information related to the failure
        %   and rerun attempts.
        %
        %   FailureInfo is an array of parallel.task.MJSFailureInfo
        %   objects, one for each rerun of the task.
        %   (read-only)
        %
        %   See also parallel.task.MJSFailureInfo,
        %            parallel.task.MJSTask.NumFailures,
        %            parallel.task.MJSTask.MaximumRetries.
        FailureInfo
        
        %NumFailures Number of times a task was rerun because it failed
        %   If a task reruns because of certain system failures, the task
        %   property NumFailures stores a count of the number of attempted
        %   reruns. 
        %   (read-only)
        %
        %   See also parallel.task.MJSFailureInfo,
        %            parallel.task.MJSTask.FailureInfo,
        %            parallel.task.MJSTask.MaximumRetries.
        NumFailures
    end

    properties (Transient, SetAccess = immutable, GetAccess = private)
        Support
        SupportID
        JobSupportID
    end
    
    properties ( Hidden, Transient, SetAccess = private )
        Attempt
        PreviousTask
    end
    
    properties ( Hidden, Dependent )
        MaxNumRetries
    end
    methods
        function set.MaxNumRetries( obj, val )
            obj.MaximumRetries = val;
        end
        function v = get.MaxNumRetries( obj )
            v = obj.MaximumRetries;
        end
    end
    
    methods ( Hidden )
        function obj = MJSTask( parent, tid, sid, support, jobSId )
            parallel.Task.loadCheck( nargin );

            obj@parallel.Task( parent, tid );

            obj.Support      = support;
            obj.SupportID    = sid;
            obj.JobSupportID = jobSId;
        end        
    end
    
    methods ( Access = protected )
        function destroyTasks( taskOrTasks )
            for ii = 1:numel( taskOrTasks )
                task = taskOrTasks(ii);
                
                try
                    task.Support.destroyOneTask( task.JobSupportID, task.SupportID );
                    task.Support.notifyDeleteTask( task.Parent );
                    task.unregisterForEventsAfterDestroyed();
                catch E
                    throw( distcomp.handleJavaException( task, E ) );
                end
            end
        end
        
        function cancelOneTask( task, cancelException )
            try
                task.Support.cancelOneTask( task.SupportID, cancelException );
                task.Support.notifyCancelTask( task.Parent );
            catch E
                throw( distcomp.handleJavaException( task, E ) );
            end
        end
        function w = getWorker( task )
            try
                mjs = task.Parent.Parent;
                w = task.Support.getWorkerForTask( mjs, task.SupportID );
            catch E
                throw( distcomp.handleJavaException( task, E ) );
            end
        end
        function ok = doWait( task, stateEnum, timeout )
            waitRequiredFcn = iCreateStateComparisonFcn( stateEnum );
            ok = task.waitForEvent( task.StateEnumToEventName(stateEnum.Name), timeout, waitRequiredFcn );
        end
        
        function supportVec = checkVectorizedSetSupport( ~ )
            % Override checkVectorizedSetSupport method inherited from
            % CustomGetSet to indicate that vectorized set calls are
            % supported. This is done to minimise remote calls.
            supportVec = true;
        end
    end
    methods ( Hidden )
        function state = hIsClusterAvailable( obj )
            state = obj.Parent.hIsClusterAvailable();
        end
        function hSetPropertyNoCheck( obj, propName, val )
            % hSetPropertyNoCheck supports vectorized calls for arrays of obj and 
            % cell arrays of propName, so that remote calls can be minimized with MJS.
            try
                hErrorIfReleaseMismatch( [obj.Parent], 'parallel:task:PropertySetVersionMismatch' );
                
                if parallel.internal.cluster.hasSameSupport( obj )
                    obj(1).Support.setTaskProperties( obj.hGetSupportID(), propName, val );
                else
                    for idx = 1:numel( obj )
                        if iscell( propName )
                            values = cellfun( @(x) x{idx}, val, 'UniformOutput', false );
                        else
                            values = val{idx};
                        end
                        obj(idx).Support.setTaskProperties( obj(idx).hGetSupportID(), propName, values );
                    end
                end
            catch E
                throw( distcomp.handleJavaException( obj, E ) );
            end
        end
        function v = hGetProperty( obj, propName )
            % hGetProperty supports vectorized calls for arrays of obj and 
            % cell arrays of propName, so that remote calls can be minimized with MJS.
            try
                if any( strcmpi( propName, 'Diary' ) )
                    obj.warnIfDiaryIncomplete();
                end
                if parallel.internal.cluster.hasSameSupport( obj )
                    v = obj(1).Support.getTaskProperties( obj.hGetSupportID(), propName );
                else
                    if ~iscell( propName )
                        propName = {propName};
                    end
                    
                    v = arrayfun( @(x) x.Support.getTaskProperties( ...
                            x.hGetSupportID(), propName ), obj(:) );
                end
            catch E
                throw( distcomp.handleJavaException( obj, E ) );
            end
        end
        function sid = hGetSupportID( obj )
            sid = vertcat( obj.SupportID );
        end
        function support = hGetSupport( obj )
        % Only used by parallel.internal.cluster.hasSameSupport
            support = obj.Support;
        end
        function s = hGetStateEnum( task )
            s = parallel.internal.cluster.getWorkUnitStateEnum( task );
        end
        function workerProxy = hGetWorkerProxy( obj )
            try
                workerProxy = obj.Support.getTaskProperties( obj.SupportID, 'WorkerProxies' );
            catch E
                throw( distcomp.handleJavaException( obj, E ) );
            end
        end
        function [taskPropertyMap, propNames]  = hGetDisplayItems(obj, diFactory)
            taskPropertyMap = hGetDisplayItems@parallel.Task(obj, diFactory);
            
            % The order the properties appear in the propNames array is the
            % order in which they will be displayed.
            propNames = {...
                'ID', ...
                'State', ...
                'Function', ...
                'Parent', ...
                'StartDateTime', ...
                'RunningDuration', ...
                'Separator', ...
                'Error', ...
                'Warnings', ...
                };
           
            % MJSTask specific displayable items to map. Function is 
            % present in all task displays, but must be gotten via the
            % special cached property FunctionName for MJS tasks. 
            functionName = parallel.internal.cluster.generateTaskFunctionString(obj.hGetDisplayPropertiesNoError('FunctionName'));
            taskPropertyMap('Function') = diFactory.createDefaultItem(functionName);
        end
        function hSetAttempt( obj, attempt )
            obj.Attempt = attempt;
        end
        function hSetPreviousTask( obj, previousTask )
            obj.PreviousTask = previousTask;
        end
        function hSubmitResult( obj, result, error, warnings, diary )
            obj.Support.submitTaskResult( obj.SupportID, obj.Attempt, result, error, warnings, diary );
        end
    end

    % MJSCallbackMixin methods
    methods ( Access = protected )
        function doRegisterForEvents( obj )            
            obj.Support.registerForTaskEvents( obj.Parent.Parent, obj.SupportID );
            obj.enableAutoEventUnregistration();
        end        
        function doUnregisterForEvents( obj )
            obj.Support.unregisterForTaskEvents( obj.Parent.Parent, obj.SupportID );
            obj.disableAutoEventUnregistration();
        end
        function doUnregisterForEventsAfterDestroyed( obj )
            obj.Support.unstashCluster( obj.Parent.Parent );
        end        
    end
    
    methods( Hidden )
        function hFireStateEvent( obj, stateEnum )
            notify( obj, obj.StateEnumToEventName(stateEnum.Name) );
        end        
    end
    
    % API1
    methods ( Hidden )
        function pPreTaskEvaluate( ~ )
        % Do nothing
        end
        
        function info = pGetDataStoreInfo( task )
            info = task.Support.getTaskDataStoreInfo;
        end
    end
end
%#ok<*ATUNK>

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% iBuildStateEnumToEventMap
function m = iBuildStateEnumToEventMap()
    import parallel.internal.types.States;
    m = containers.Map;
    m(States.Running.Name)  = parallel.internal.cluster.MJSRunningCallbackMixin.RunningEventName;
    m(States.Finished.Name) = parallel.internal.cluster.MJSFinishedCallbackMixin.FinishedEventName;
end

function fcn = iCreateStateComparisonFcn( stateEnum )
% Returns a function that will check the state of the job when called.
% Created here to prevent the job object being sent as part of the
% workspace.
    fcn = @( obj ) obj.StateEnum < stateEnum;
end
