function dependencyMap = pAddDependenciesFromZipfile(zipfileName, attachedFilePaths, dependencyDir)
% pAddDependenciesFromZipFile - Unzip the zip archive into the attached files folder.
%
%   zipFileName - the name of the zip file containing the attached files.
%
%   attachedFilePaths - maps original path roots to the truncated path roots in the zip archive
%       In API2: EITHER 
%       - an array of Strings of the client paths and worker paths
%         interleaved.
%       - an AttachedFileMap
%
%   dependencyDir - path to the attached files folder on the worker.

%   Copyright 2009-2018 The MathWorks, Inc.

% Get the runtime properties of the cluster
root = distcomp.getdistcompobjectroot;
runprop = root.CurrentRunprop;

unzip(zipfileName, dependencyDir);

if isa( attachedFilePaths, 'java.lang.String[]' )
    attachedFilePaths = cell( attachedFilePaths );
end

% When called from MATLAB dependencyPaths will be an AttachedFileMap  which
% we'll use to create the dependency map. Instead, when this method is
% called from Java, dependencyPath is a cell array that represents the
% interleaved paths on the client and worker.
if isa( attachedFilePaths, 'parallel.internal.apishared.AttachedFileMap' )
    % Since dependencyPaths *is* an AttachedFileMap, just assign it to
    % runprop.AttachedFilesPathMap. 
    runprop.AttachedFilesPathMap = attachedFilePaths;

    dependencyMap = iCreateDependencyMap( attachedFilePaths, dependencyDir );
    pathsToAdd = dependencyMap(:,2);
else
    % Work out the folders to add to runprop.AttachedFilesPathMap
    clientList = attachedFilePaths(1:2:end);
    workerList = attachedFilePaths(2:2:end);
    % convert '\' to '/' in case the path is coming from a PC - NOTE PC
    % MATLAB can deal with '/' whereas unix cannot deal with '\' so ALWAYS
    % convert to unix.
    clientList = cellfun( @(x) strrep( x, '\', '/' ), clientList, ...
        'UniformOutput', false );
    tmpMap = runprop.AttachedFilesPathMap;
    cellfun( @tmpMap.put, clientList, workerList );
    
    dctSchedulerMessage( 6, 'Calculating dependency paths' );
    % Need to replace slashes in the dependency paths if we don't have an AttachedFileMap.
    workerList = cellfun( @(x) strrep( x, '\', '/' ), workerList, ...
        'UniformOutput', false );
    [dependencyMap, pathsToAdd] = ...
        iCalculatePathsToAdd(dependencyDir, clientList, workerList);
end

dctSchedulerMessage( 6, ...
    sprintf( 'Created dependency map:\n%s', iDependencyMapToString( dependencyMap ) ) );
dctSchedulerMessage( 6, ...
    sprintf( 'Created paths to add :\n%s', sprintf( '%s\n', pathsToAdd{:} ) ) );  

% Add the paths only if we're not deployed. This is because addpath throws
% an error in deployed applications if the path-entry to be added is not
% already on the path. Omitting this addpath will not affect a worker's
% ability to execute code files, because deployed applications can only
% execute compiled MATLAB code, which is necessarily already on their path.
% It will however affect the worker's ability to access data files, as they
% will have to specify the full path to the file, rather than just their
% filename. For example, load('data.mat') will not work but
% load('/full/path/to/data.mat') will.
if ~isdeployed
    iAddPaths( pathsToAdd );
end

%--------------------------------------------------------------------------
% Helpers
%--------------------------------------------------------------------------

function dependencyMap = iCreateDependencyMap( pathMap, dependencyDir )
% iCreateDependencyMap - convert paths from their original client path to the
%   equivalent local path on the worker, and return a map of the conversion.

originalPaths = pathMap.keys();
dependencyMap = cell( length( originalPaths ), 2 );
for ii = 1:numel( originalPaths )
    newPath = pathMap.get( originalPaths{ii} );
    % The dependency map maps the file name on the client to the file name
    % on the worker.
    dependencyMap{ii, 1} = originalPaths{ii};
    dependencyMap{ii, 2} = fullfile( dependencyDir, newPath );
end

function dependencyMapString = iDependencyMapToString( dependencyMap )
% iDependencyMapToString - Return a string that represents the dependency map,
%   which is a 2-column cell array of strings with keys in column 1 and values
%   in column 2.
dependencyMapString = '';
for ii = 1:size( dependencyMap, 1 )
    dependencyMapString = [ dependencyMapString, ...
        sprintf( '  %s -> %s\n', dependencyMap{ii, 1}, dependencyMap{ii, 2} ) ]; %#ok<AGROW>
end

function [dependencyMap, pathsToAdd] = iCalculatePathsToAdd(rootdir, filelist, relativefilelist)
% filelist input is a cell array of strings that we need to match up to
% files or directories on the local machine. They might be relative or
% absolute and we need to distinguish.

dependencyMap = cell(numel(filelist), 2);
% Create a cell array of all the paths that we will need to add to the
% current MATLAB path in one go
pathsToAdd = cell(size(filelist));

for i = numel(filelist):-1:1
    name = relativefilelist{i};
    localName = [rootdir filesep name];
    dctSchedulerMessage(6, ['Searching for ' localName ' ...']);
    % check for a relative path to a file or directory
    if exist(localName,'dir')
        % it is a relative path to a directory
        pathsToAdd{i} = localName;
    elseif exist(localName,'file')
        % it is a relative path to a file
        pathsToAdd{i} = fileparts(localName);
    else
        % This is here for compatibility with API-1 and
        % NewDirectoryDependencyCommand.java.
        %
        % it is an absolute path to a file or directory
        % OR
        % it is a relative path to a directory that was empty.
        % A directory could look empty because either,
        %  - a relative or absolute path to a actual empty directory
        %    was specified as a file dependency, or
        %  - a directory was specified as a file dependency, and all
        %    the files in the directory were also specified as file
        %    dependencies by their absolute path.

        % if the directory name ends in a filesep, then the
        % directory's contents will be placed by UNZIP in rootdir,
        % otherwise the directory will be placed in a subdirectory
        % of the appropriate name

        % Get the last part of the filename - this is everything after
        % the last '/', or the whole string if there are no slashes.
        seplocations = strfind(name,'/');
        if ~isempty(seplocations)
            nameEnd = name(seplocations(end)+1:end);
        else
            nameEnd = name;
        end
        localName = [rootdir filesep nameEnd];
        if name(end) == '/'
            % THIS CODE PATH SHOULD NEVER BE CALLED - whilst setting the
            % file dependencies on the job we are checking to see if the
            % element ends with a file separator and removing it.
            dctSchedulerMessage(1, 'File dependency with trailing file separator found - this was NOT expected.');
            % there was a filesep on the end of the directory name
            pathsToAdd{i} = rootdir;
        elseif exist(localName,'dir')
            % there was no filesep on the end of the directory name
            pathsToAdd{i} = localName;
        elseif exist(localName,'file')
            % it is an absolute file name
            pathsToAdd{i} = rootdir;
        else 
            % To get here the filelist entry can not be found locally.
            % This is probably because it was an empty dir and so didn't
            % get added to the zip file. We need to create it here so
            % any files added to this dir can get updated correctly.
            dctSchedulerMessage(1, 'File dependency can not be found - creating dir %s', localName);
            mkdir(localName);
            pathsToAdd{i} = localName;            
        end
    end
    dependencyMap(i, :) = { filelist{i}  localName };
end

function iAddPaths( pathsToAdd )

% Add the pathStart of specialSplitPath() to the path rather than the folder
% name itself since special folder path names are not allowed in the path.
% Normal folders paths should be the same as pathStart and be unaffected.
pathsToAdd = cellfun(@parallel.internal.apishared.FilenameUtils.specialSplitPath, pathsToAdd, 'UniformOutput', false );

% Remove any empty elements
pathsToAdd(cellfun(@isempty, pathsToAdd)) = [];

% Remove any file entry to avoid MATLAB:dispatcher:pathWarning 
pathsToAdd(cellfun(@parallel.internal.apishared.FilenameUtils.fileExists, pathsToAdd)) = [];

pathsToAdd = unique(pathsToAdd, 'stable');
if ~isempty(pathsToAdd)
    parallel.internal.general.addpathWithFoldersFilteredIfDeployed(pathsToAdd{:});
end
