function [uploadedFiles, errors] = storeInCloud(clientpaths, cloudpaths)
% storeInCloud Stores files in the cloud storage area. 
%
% storeInCloud is intended for internal use only. 
%
% See also parallel.internal.cloud.uploadToCloud,
%          parallel.internal.cloud.getCloudFolder,
%          parallel.internal.cloud.addCloudFolderToPath
%
    
    % Convert strings to single element cell arrays. 
    if ischar(clientpaths)
        clientpaths = { clientpaths };
    end
    
    if ischar(cloudpaths)
        cloudpaths = { cloudpaths };
    end
    
    if ~iscell(clientpaths) || any(~cellfun(@ischar, clientpaths)) || ~isvector(clientpaths)
        error(... 
            'parallel:cloud:files:ClientpathsMustBeStringOrCell', ...
            'CLIENTPATHS must be a string or a 1D cell array of strings. ');
    end
    
    if ~iscell(cloudpaths) || any(~cellfun(@ischar, cloudpaths)) || ~isvector(cloudpaths)
        error(...
            'parallel:cloud:files:CloudpathsMustBeStringOrCell', ...
            'CLOUDPATHS must be a string or a 1D cell array of strings. ');
    end
    
    if numel(clientpaths) ~= numel(cloudpaths)
        error(...
            'parallel:cloud:files:ArgumentsMustBeSameLength', ...
            'CLIENTPATHS and CLOUDPATHS must contain the same number of elements');
    end
    
    job = getCurrentJob;
    if isempty(job)
        error(...
            'parallel:cloud:files:NotARemoteJob', ...
            'This function must be run as a remote job.');
    end
    
    cloudRootFolder = parallel.internal.cloud.getCloudFolder(); 
    if isempty(cloudRootFolder)
        error(...
            'parallel:cloud:files:StorageNotAvailable', ...
            'The cloud storage area is not available.')
    end
    
    attachedFilesLocation = getAttachedFilesFolder;
    attachedFilePathsMap = job.AttachedFilePaths;
    
    uploadedFiles = cell(size(clientpaths));
    errors = [];
    for i=1:numel(clientpaths)
        
        clientPath = clientpaths{i};
        cloudRelativePath = cloudpaths{i};
        
        try
            [clientFolder, clientName, clientExt] = fileparts(clientPath);
            clientFilename = [clientName clientExt];
            
            % The keys in the attachedFilePathsMap are all absolute paths
            % to folders on the originating file system. 
            % 
            % If the user attached a folder, the key is the path to that
            % folder. 
            % 
            % If the user attached a file, the key is the path to the
            % folder that contains that file. 
            if attachedFilePathsMap.isKey(clientPath)
                % The key was the whole path => the user attached a folder.
                workerFolder = attachedFilePathsMap.get(clientPath);
                workerPath = fullfile(attachedFilesLocation, workerFolder);
            elseif attachedFilePathsMap.isKey(clientFolder)
                % The key was the parent folder => the user attached a file. 
                workerFolder = attachedFilePathsMap.get(clientFolder);
                workerPath = fullfile(attachedFilesLocation, workerFolder, clientFilename);
            else
                % The attached file was not in the map. 
                errors = iAppendError(...
                    errors, ...
                    clientPath, ...
                    cloudRelativePath, ...
                    ['File ' clientPath ' could not be found.']);
                continue;
            end

            % Work out where we want to write to in the cloud storage area. 
            cloudAbsolutePath = fullfile(cloudRootFolder, cloudRelativePath);
            cloudAbsoluteFolder = fileparts(cloudAbsolutePath);
            
            % Create the destination folder (and parents) if it doesn't
            % already exist. 
            if ~exist(cloudAbsoluteFolder, 'file')
                [status, message, ~] = mkdir(cloudAbsoluteFolder);
                if ~status
                    errors = iAppendError(...
                        errors, ...
                        clientPath, ...
                        cloudRelativePath, ...
                        ['mkdir: ' message ' ' cloudAbsoluteFolder]);
                    continue;
                end
            end
                        
            % Perform the copy
            [status, message] = copyfile(workerPath, cloudAbsolutePath, 'f');
            if ~status
                errors = iAppendError(...
                    errors, ...
                    clientPath, ...
                    cloudRelativePath, ...
                    ['copyfile: ' message]);
                continue;
            end

            uploadedFiles{i} = cloudRelativePath; 
            
        catch e
            errors = iAppendError(...
                errors, ...
                clientPath, ...
                cloudRelativePath, ...
                ['Exception: ' e.message], ...
                e);
        end
    end
end

function errors = iAppendError(errors, clientFile, cloudFile, message, e)
    
    if nargin < 5
        e = [];
    end
    
    errorStruct = struct( ...
        'localfile', clientFile, ...
        'remotefile', cloudFile, ...
        'message', message, ...
        'exception', e);
    
    errors = [errors errorStruct];
end