function [jobStatus, corruptionInfos] = getStatusFromJob(jobOrJobs, corruptionInfos, jobStatesCell)
; %#ok Undocumented
% Inputs:
% jobOrJobs - array of jobs
% jobStatesCell - cell array of states of those jobs
% Outputs:
% array of job status structs.

% Copyright 2011-2020 The MathWorks, Inc.

if isempty( jobOrJobs )
    jobStatus = struct('State'                     , {}, ...
                       'TotalTasks'                , {}, ...
                       'PendingTasks'              , {}, ...
                       'RunningTasks'              , {}, ...
                       'FinishedTasks'             , {}, ...
                       'FinishedTasksNoError'      , {}, ...
                       'FinishedTasksWithError'    , {}, ...
                       'FinishedTasksWithWarnings' , {}, ...
                       'ErroredTaskIDs'            , {}, ...
                       'TaskIDsWithWarnings'       , {});
    return
end

% Get all the tasks in one findTask call.
[pending, running, finished] = findTask( jobOrJobs );

% Task IDs of the finished tasks
finishedTaskIDs = [ finished.ID ].';

% Get the job IDs of the pending, running, and finished tasks. Note that
% task.Parent.ID is a relatively quick operation because the storage doesn't
% need to be consulted.
pendTaskJobID     = arrayfun( @(t) t.Parent.ID, pending );
runningTaskJobID  = arrayfun( @(t) t.Parent.ID, running );
finishedTaskJobID = arrayfun( @(t) t.Parent.ID, finished );

% Get the job IDs as a column:
jobIDs = [ jobOrJobs.ID ].';

% Get all Errors and Warnings from finished tasks only
try
    data = get( finished, {'Error', 'Warnings'} );
    errors = data(:,1);
    warnings = data(:,2);
catch
    % try to find which is the "offending" job
    [errors, warnings, corruptionInfos] = iGetFinishedJobInfo(finished, jobIDs, corruptionInfos);
end

goterror = ~cellfun( @isempty, errors );
gotwarnings = ~cellfun( @isempty, warnings );


% Work out how many tasks are in each state per job by counting up from each
% list of tasks how many match a particular job ID.
pendTasksPerJob       = arrayfun( @(id) sum( pendTaskJobID == id ), jobIDs );
runningTasksPerJob    = arrayfun( @(id) sum( runningTaskJobID == id ), jobIDs );
finishedTasksPerJob   = arrayfun( @(id) sum( finishedTaskJobID == id ), jobIDs );
erroredTasksPerJob    = arrayfun( @(id) sum( goterror & finishedTaskJobID == id ), jobIDs );
warningTasksPerJob    = arrayfun( @(id) sum( gotwarnings & finishedTaskJobID == id ), jobIDs );
successfulTasksPerJob = finishedTasksPerJob - erroredTasksPerJob;
totalTasksPerJob      = pendTasksPerJob + runningTasksPerJob + finishedTasksPerJob;

% For each job, extract the task errors and task IDs
erroredTaskIds = arrayfun( ...
    @(id) iTaskErrors(id, finishedTaskJobID, finishedTaskIDs, goterror ), jobIDs, ...
    'UniformOutput', false );

taskIdsWithWarnings = arrayfun( ...
    @(id) iTaskWarnings(id, finishedTaskJobID, finishedTaskIDs, gotwarnings ), jobIDs, ...
    'UniformOutput', false );

jobStatus = struct('State'                     , jobStatesCell, ...
                   'TotalTasks'                , num2cell( totalTasksPerJob ), ...
                   'PendingTasks'              , num2cell( pendTasksPerJob ), ...
                   'RunningTasks'              , num2cell( runningTasksPerJob ), ...
                   'FinishedTasks'             , num2cell( finishedTasksPerJob ), ...
                   'FinishedTasksNoError'      , num2cell( successfulTasksPerJob ), ...
                   'FinishedTasksWithError'    , num2cell( erroredTasksPerJob ), ...
                   'FinishedTasksWithWarnings' , num2cell( warningTasksPerJob ), ... 
                   'ErroredTaskIDs'            , erroredTaskIds, ...
                   'TaskIDsWithWarnings'       , taskIdsWithWarnings);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Inputs:
% jobs - array of the jobs (may be just one job)
% tasks - array of tasks to process
% jobIDs - array of job IDs
% corruptionInfos - job corruption information
% Outputs:
% errors - array of errors
% corruptionInfos - job corruption information
function [errors, warnings, corruptionInfos] = iGetFinishedJobInfo(tasks, jobIDs, corruptionInfos)
    errors = cell(numel(tasks), 1);
    warnings = cell(numel(tasks), 1);
    
    for index=1:numel(tasks)
        try
            data = get( tasks(index), { 'Error', 'Warnings' } );
            errors(index) = data(1);
            warnings(index) = data(2);
        catch E
            updateIDs = arrayfun(@(id) id == tasks(index).Parent.ID, jobIDs);
            corruptionInfos{updateIDs}{end+1} = parallel.internal.cluster.JobCorruptionInfo(...
                parallel.internal.cluster.types.JobCorruptionLevel.CannotProvideErroredTaskInformation, ...
                E.message);
        end
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function ids = iTaskErrors( jobID, jobIDofTasks, taskIDs, taskHasError )

idx = jobIDofTasks == jobID & taskHasError;
if any( idx )
    ids = taskIDs( idx );
else
    ids  = [];
end
end

function ids = iTaskWarnings( jobID, jobIDofTasks, taskIDs, taskHasWarnings )

idx = jobIDofTasks == jobID & taskHasWarnings;
if any( idx )
    ids = taskIDs( idx );
else
    ids = [];
end
end
