%WorkerPathHelper   Helper class to process the rmpath/addpath/cd events
%   WorkerPathHelper processes the rmpath/addpath/cd events caught by
%   dctPathAndClearNotification when a pool is open. Whatever is passed to
%   those commands is mapped (if possible) to a path on the worker, and
%   then the corresponding command is executed.

% Copyright 2015-2020 The MathWorks, Inc.

classdef WorkerPathHelper
    methods ( Static )
        function addpath( pathInfoArg )
            dctSchedulerMessage( 5, '> WorkerPathHelper.addpath' );
            pathInfo = iUnpackArgs( pathInfoArg );
            pathsToAdd = iGetWorkerPaths( pathInfo );
            
            % We need to respect the 'frozen'-ness specified by the client.
            % NB: If any path is frozen, add them all with '-frozen' and
            % correct afterwards, if necessary.
            if any( pathInfo.IsPathFrozen )
                stringArgs = {'-frozen'};
            else
                stringArgs = {};
            end
            
            % Prepend paths
            if any( pathInfo.IsPathBegin )
                dctSchedulerMessage( 6, '- WorkerPathHelper.addpath calling addpath( pathsToAdd, -begin, %s )', stringArgs{:} );
                addpath( pathsToAdd{pathInfo.IsPathBegin}, '-begin', stringArgs{:} );
            end
            
            % Append paths
            if any( ~pathInfo.IsPathBegin )
                dctSchedulerMessage( 6, '- WorkerPathHelper.addpath calling addpath( pathsToAdd, -end, %s )', stringArgs{:} );
                addpath( pathsToAdd{~pathInfo.IsPathBegin}, '-end', stringArgs{:} );
            end
            
            % If there was mixed frozenness ( not very likely ), correct the
            % paths that were added incorrectly with '-frozen' using
            % 'unfreezePath'. This causes an additional REHASH, so we don't 
            % use that by default.
            if numel( unique( pathInfo.IsPathFrozen ) ) > 1
                isNowFrozen = parallel.internal.general.isPathFrozen( pathsToAdd );
                needsToBeUnfrozen = isNowFrozen & ~pathInfo.IsPathFrozen;
                dctSchedulerMessage( 6, '- WorkerPathHelper.addpath unfreezing %d paths.', sum( needsToBeUnfrozen ) );
                parallel.internal.general.unfreezePath( pathsToAdd( needsToBeUnfrozen ) );
            end
            dctSchedulerMessage( 5, '< WorkerPathHelper.addpath' );
        end
        
        function oldFolder = cd( pathInfoArg )
            dctSchedulerMessage( 5, '> WorkerPathHelper.cd' );
            pathInfo = iUnpackArgs( pathInfoArg );
            assert( numel( pathInfo.RawPaths ) == 1, 'WorkerPathHelper.cd expects a single path' );
            pathToCd = parallel.internal.pool.WorkerPathHelper.getPathToCd( pathInfo.RawPaths{1}, pathInfo.UNCPaths{1}, pathInfo.MATLABDrivePathMap );
            dctSchedulerMessage( 6, '- WorkerPathHelper.cd calling cd %s', pathToCd );
            oldFolder = cd( pathToCd );
            dctSchedulerMessage( 5, '< WorkerPathHelper.cd' );
        end
        
        function rmpath( pathInfoArg )
            dctSchedulerMessage( 5, '> WorkerPathHelper.rmpath' );
            pathInfo = iUnpackArgs( pathInfoArg );
            pathsToRm = iGetWorkerPaths( pathInfo );
            dctSchedulerMessage( 6, '- WorkerPathHelper.rmpath calling rmpath %s', pathsToRm{:} );
            wasFrozen = parallel.internal.general.isPathFrozen( pathsToRm );
            rmpath( pathsToRm{:} );
            dctSchedulerMessage( 6, '- WorkerPathHelper.rmpath about to unfreeze %d paths.', sum( wasFrozen ) );
            parallel.internal.general.unfreezePath( pathsToRm( wasFrozen ) );
            dctSchedulerMessage( 5, '< WorkerPathHelper.rmpath' );
        end
        
        function pathToCd = getPathToCd( clientPath, UNCClientPath, MATLABDrivePathMapObj )
            dctSchedulerMessage( 5, '> WorkerPathHelper getPathToCd' );
            
            if ~parallel.internal.apishared.MATLABDriveUtils.forcePathTranslation()
                % Do not try to translate the path if the client and worker can both
                % access the same directory (for example if they are on the same
                % machine).
                if exist( clientPath, 'dir' ) == 7
                    pathToCd = clientPath;
                elseif exist( UNCClientPath, 'dir' ) == 7
                    pathToCd = UNCClientPath;
                else
                    pathToCd = iGetWorkerPath( clientPath, UNCClientPath, MATLABDrivePathMapObj );
                end
            else
                pathToCd = iGetWorkerPath( clientPath, UNCClientPath, MATLABDrivePathMapObj );
            end
            dctSchedulerMessage( 5, '< WorkerPathHelper getPathToCd %s', pathToCd );
        end
    end
end

function pathInfo = iUnpackArgs( pathInfoArg )
%iUnpackArgs   Accepts a MATLAB PathInfo or a Java PathNotificationCommand.
% Returns a PathInfo object
dctSchedulerMessage( 5, '> WorkerPathHelper iUnpackArgs' );

% If the argument is not a PathInfo object, we expect a Java
% PathNotificationCommand that needs to be unpacked
% PCT_Java_Transition_TODO (g2233336): Remove Java branch
if isa( pathInfoArg, 'parallel.internal.apishared.PathInfo' )
    pathInfo = pathInfoArg;
else
    pathNotificationCommand = pathInfoArg;
    rawInfo = pathNotificationCommand.getPathData();
    pathInfo = parallel.internal.apishared.PathInfo.getDeserializedPathInfo( rawInfo );
end

dctSchedulerMessage( 6, '- WorkerPathHelper iUnpackArgs pathInfo %s', pathInfo.getDebugString() );
dctSchedulerMessage( 5, '< WorkerPathHelper iUnpackArgs' );
end


function workerPaths = iGetWorkerPaths( pathInfo )
dctSchedulerMessage( 5, '> WorkerPathHelper iGetPaths' );

workerPaths = cellfun( @(rawPath, UNCPath) iGetWorkerPath( rawPath, UNCPath, pathInfo.MATLABDrivePathMap ), ...
    pathInfo.RawPaths, pathInfo.UNCPaths, 'UniformOutput', false );

dctSchedulerMessage( 5, '< WorkerPathHelper iGetWorkerPaths %s', strjoin( workerPaths, ',' ) );
end

function workerPath = iGetWorkerPath( clientPath, UNCClientPath, MATLABDrivePathMapObj )
dctSchedulerMessage( 5, '> WorkerPathHelper iGetWorkerPaths %s %s', clientPath, UNCClientPath );

[ isAttachedFilePath, workerPath ] = iGetAttachedFilePath( clientPath );

if ~isAttachedFilePath
    [ pathTranslated, workerPath ] = MATLABDrivePathMapObj.translatePath( clientPath );
    dctSchedulerMessage( 6, '- WorkerPathHelper: pathTranslated %d', pathTranslated );
    
    if ~pathTranslated && exist( clientPath, 'dir' ) ~=7 && exist( UNCClientPath, 'dir' ) == 7
        workerPath = UNCClientPath;
    end
end

dctSchedulerMessage( 5, '< WorkerPathHelper iGetWorkerPath' );
end


function [ isAttachedFilePath, attachedFilePath ] = iGetAttachedFilePath( clientPathIn )
% Checks if clientPath is a path that should be translated to a path in the
% worker's attached files folder.
% For instance, if there is a client/worker mapping like /a/b/c->/wa/wb/wc,
% iGetAttachedFilePath('/a/b/c/d/e/f') returns '/wa/wb/wc/d/e/f'
% which will be fed to any addpath/rmpath/cd command. If a valid mapping
% cannot be found, we return the original argument unchanged.
dctSchedulerMessage( 5, '> WorkerPathHelper iGetAttachedFilePath %s', clientPathIn );
root = distcomp.getdistcompobjectroot;
runprop = root.CurrentRunprop;

% runprop.AttachedFilesPathMap contains all the clientPath->workerPath mappings
attachedFilesPathMap = runprop.AttachedFilesPathMap;
dependencyFolder = runprop.getAttachedFilesFolder();

% Fix the slashes. We need forward slashes to regexp into the pathMap.keys
clientPath = strrep( clientPathIn, '\', '/' );

[ ~, matches ] = regexp( clientPath, strcat( '^', attachedFilesPathMap.keys ) );
matches = cell2mat( matches );

dctSchedulerMessage( 6, '- WorkerPathHelper iGetAttachedFilePath matches in attachedFilePathMap: %d', matches );

if isempty( matches )
    % if matches is empty just return the original value (this holds
    % also for flags like -begin, -end, -frozen)
    attachedFilePath = clientPathIn;
    isAttachedFilePath = false;
else
    % longest prefix match
    index = max( matches );
    
    % Base path on the client
    clientBase = clientPath( 1:index );
    
    % Relative path to add (this is the same for clients and workers)
    clientPathRelativeToBase = clientPath( index+1:end );
    
    % Base path on the worker
    workerBase = attachedFilesPathMap.get( clientBase );
    
    % Absolute path to add (on the worker)
    attachedFilePath = fullfile( dependencyFolder, workerBase, clientPathRelativeToBase );
    isAttachedFilePath = true;
end

dctSchedulerMessage( 5, '< WorkerPathHelper iGetAttachedFilePath isAttachedFilePath: %d attachedFilePath: %s', isAttachedFilePath, attachedFilePath );
end

