%ClientPathHelper Static methods called from C++ by parallel.internal.pool.PathAssistant
%
%   Computes PathInfo objects to send to workers on path change events
%   (i.e. addpath, rmpath, cd)

% Copyright 2020 The MathWorks, Inc.

classdef ClientPathHelper
    methods (Static)
        % Given sets of path entries before and after a change, returns
        % PathInfo objects corresponding to entries that have been added to
        % the path and entries that have been removed from the path. If no
        % entries have been added/removed, returns an empty object instead.
        function [addPathInfo, rmPathInfo] = pathChanged(entriesBefore, entriesAfter)
            import parallel.internal.apishared.PathInfo
            
            [beforeAbove, beforeBelow] = iSplitAroundMatlabRoot(entriesBefore);
            [afterAbove, afterBelow] = iSplitAroundMatlabRoot(entriesAfter);
            
            % Determine the entries that have been added to the path
            % NB: Use 'stable' to preserve order in addpath() call
            addedToAbove = iGetPathsToAdd(beforeAbove, afterAbove);
            addedToBelow = iGetPathsToAdd(beforeBelow, afterBelow);
            if isempty(addedToAbove) && isempty(addedToBelow)
                addPathInfo = [];
            else
                addPathInfo = PathInfo.createAddPathInfo(addedToAbove, addedToBelow);
            end
            
            % Determine the entries that have been removed from the path
            removedFromAbove = setdiff(beforeAbove, afterAbove);
            removedFromBelow = setdiff(beforeBelow, afterBelow);
            allRemovedEntries = [removedFromAbove, removedFromBelow];
            if isempty(allRemovedEntries)
                rmPathInfo = [];
            else
                rmPathInfo = PathInfo.createRmPathInfo(allRemovedEntries);
            end
        end
        
        % Creates and returns a PathInfo object for a cd event
        function pathInfo = currentDirectoryChanged(cwd)
            pathInfo = parallel.internal.apishared.PathInfo.createCdPathInfo({cwd});
        end
    end
end

% For the given path entries, returns the subsets of path entries that are
% above and below the first path entry under matlabroot. All path entries
% under matlabroot are filtered out from the returned sets.
%
% NB: If no path entry is under matlabroot, all path entries are considered
% 'above'.
function [above, below] = iSplitAroundMatlabRoot(entries)
    % Find the index of the first entry that is under matlabroot
    underMatlabRoot = startsWith(entries, matlabroot);
    splitIndex = find(underMatlabRoot, 1, 'first');
    
    if isempty(splitIndex)
        % No entry under matlabroot; return all entries as 'above'
        above = entries;
        below = {};
    else
        entries(underMatlabRoot) = []; % Filter out entries under matlabroot
        above = entries(1:splitIndex-1);
        below = entries(splitIndex:end);
    end
end

% Returns the paths that need to be added to pathsBefore to get pathsAfter,
% including any existing paths that are needed to give the correct order
function pathsToAdd = iGetPathsToAdd(pathsBefore, pathsAfter)
    [~, idx] = ismember(pathsAfter, pathsBefore);
    
    % Find the last index beyond which all indices are non-zero (i.e. the
    % paths have already been added) and sorted (i.e. appear in the same
    % order as before). All paths up to and including this index must be
    % added to ensure they are on the path and in the correct order!
    n = numel(idx);
    for ii = n:-1:0 % Go down to 0 in case no paths need to be added
        if ii == 0 || idx(ii) == 0 || (ii ~= n && idx(ii) > idx(ii+1)) 
            break
        end
    end
    pathsToAdd = pathsAfter(1:ii);
end