% TaskDisplayer - Displayer for parallel.Task

% Copyright 2011-2019 The MathWorks, Inc.

classdef TaskDisplayer < parallel.internal.display.Displayer
    properties (Constant, GetAccess = private)
        BaseDisplayClass = 'parallel.Task';
        % We use 'StateEnum' here for performance reasons. For mjs this
        % will make 1 remote call to get all the states. If we were to use
        % 'State' it would loop over all the tasks getting the state for
        % each in turn.
        VectorDisplayProps = {'ID', 'StateEnum', 'FinishDateTime', 'FunctionName', 'Error', 'Warnings'};
        VectorTableColumns = { ...
            % 'Title',        'Resizeable', 'MinimumWidth'
            'ID',             true,         length('ID'); ...
            'State',          false,        length('unavailable'); ...
            % Users can change the default display of datetimes at any time.
            % 26 characters is the longest possible display in the MATLAB preferences
            % (corresponding to millisecond display in Japan), but if users
            % override this to a longer format then use that length.
            'FinishDateTime', true,         max(length(char(datetime('now', 'TimeZone', 'local'))), 26); ...
            'Function',       true,         length('Function'); ...
            'Errors',         false,        length('Errors'); ...
            'Warnings',       false,        length('Warnings'); ...
            }
    end

    properties (SetAccess = immutable, GetAccess = protected) % implementation for abstract Displayer properties
        DisplayHelper 
    end
 
    methods (Static)   
        function obj = buildForTask( hetTask )
        %buildForTask - build for all task classes
        %   simply uses the default.
            obj = parallel.internal.cluster.TaskDisplayer( hetTask );
        end
        function obj = buildForCJS( cjsTask )
            obj = parallel.internal.cluster.TaskDisplayer( cjsTask );
        end
        function obj = buildForMJS( mjsTask )
            obj = parallel.internal.cluster.TaskDisplayer( mjsTask );
        end
    end
    
    methods 
        function obj = TaskDisplayer(someObj)
            obj@parallel.internal.display.Displayer(someObj, parallel.internal.cluster.TaskDisplayer.BaseDisplayClass);
            obj.DisplayHelper = parallel.internal.display.DisplayHelper( length('Running Duration') );
        end
    end
    
    methods (Access = private)    
        function doInvalidSingleDisplay( obj, invalidToDisp )
            obj.DisplayHelper.displayMainHeading(getString(message('parallel:cluster:InvalidTaskID', invalidToDisp.ID)));
            
            obj.DisplayHelper.displayProperty('State', invalidToDisp.State);
        end
    end

    methods (Access = protected)        
        function doSingleDisplay(obj, toDisp)
            import parallel.internal.types.States

            if ~States.isValidState( toDisp.State )
                % Do not attempt to access any further info.
                obj.doInvalidSingleDisplay( toDisp );
                return
            end
            
            % For the moment, for tasks, we only want to display help for the common base class
            obj.DisplayHelper.displayMainHeading('%s \n', ...
                getString( message('MATLAB:ObjectText:DISPLAY_AND_DETAILS_SCALAR_WITH_PROPS',obj.formatDocLink(parallel.internal.cluster.TaskDisplayer.BaseDisplayClass)) ));
            
            obj.displaySpecificItems(toDisp);
        end

        function doVectorDisplay(obj, toDisp)
            import parallel.internal.cluster.TaskDisplayer
            import parallel.internal.types.States
            
            % We need to filter invalid objects from the vector of objects
            % to display, because getDisplayPropsAsStruct errors when an
            % object is invalid.
            validToDisp = toDisp(isvalid(toDisp));
            
            % Will display the SchedulerID column only if all the tasks can
            % have one
            shouldDisplaySchedulerID = all( ...
                arrayfun(@(x) isa(x,'parallel.task.CJSTask') && ...
                ~isa(x.Parent.Parent, 'parallel.cluster.Local'), ...
                validToDisp) );
            
            % Work out which columns to display
            VectorDisplayPropsToUse = TaskDisplayer.VectorDisplayProps;
            VectorTableColumnsToUse = TaskDisplayer.VectorTableColumns;
            if shouldDisplaySchedulerID
                VectorDisplayPropsToUse = [VectorDisplayPropsToUse, 'SchedulerID'];
                VectorTableColumnsToUse = [VectorTableColumnsToUse; ...
                    {'SchedulerID', true, length('SchedulerID')}];
            end
            
            tableData = cell(numel(toDisp), size(VectorTableColumnsToUse, 1));
            dispPropsFcn = @(x) obj.getDisplayPropsAsStruct( x, VectorDisplayPropsToUse );
            propsToDisp = parallel.internal.cluster.hetfun( dispPropsFcn, validToDisp, 'stable' );
            
            % Some objects may be invalid, so increment a validIdx counter
            % separately.
            validIdx = 1;
            prevParent = parallel.Job.empty;
            jobMemento = parallel.internal.display.JobMemento.empty;
            for ii = 1:numel(toDisp)
                currToDisp = toDisp(ii);
                if isvalid(currToDisp)
                    % Subsequent tasks will have the same parent more often
                    % than not. For performance we will create the job
                    % memento here and reuse it if possible.
                    if isempty(prevParent) || currToDisp.Parent ~= prevParent
                        jobMemento = parallel.internal.display.JobMemento(currToDisp.Parent);
                        prevParent = currToDisp.Parent;
                    end
                    
                    currState = propsToDisp{validIdx}.StateEnum.Name;
                    if States.isValidState(currState)
                        dataRow = {obj.hGetLink(currToDisp, propsToDisp{validIdx}.ID, jobMemento), ...
                                   currState, ...
                                   obj.DisplayHelper.formatDateTime(propsToDisp{validIdx}.FinishDateTime), ...
                                   propsToDisp{validIdx}.FunctionName, ...
                                   numel(propsToDisp{validIdx}.Error), ...
                                   numel(propsToDisp{validIdx}.Warnings)};
                        if shouldDisplaySchedulerID
                            dataRow = [dataRow, {propsToDisp{validIdx}.SchedulerID}]; %#ok<AGROW> only grows once
                        end
                        validIdx = validIdx + 1;
                    else
                        dataRow = {currToDisp.ID, currToDisp.State, ...
                                   '', '', '', ''};
                        if shouldDisplaySchedulerID
                            dataRow = [dataRow, {''}]; %#ok<AGROW> only grows once
                        end
                    end
                else
                    dataRow = {'', parallel.internal.display.AbstractDisplayer.DeletedString, '', '', '', ''};
                    if shouldDisplaySchedulerID
                        dataRow = [dataRow, {''}]; %#ok<AGROW> only grows once
                    end
                end
                tableData(ii, :) = dataRow;
            end
            
            % In the vector display we only want to display help for the common base class
            obj.DisplayHelper.displayDimensionHeading(size(toDisp), obj.formatDocLink(TaskDisplayer.BaseDisplayClass));
            obj.DisplayHelper.displayTable(VectorTableColumnsToUse, tableData, obj.DisplayHelper.DisplayIndexColumn);
        end
        
    end
    
end
