function job = batch( obj, scriptName, varargin )
%BATCH Run MATLAB script or function as batch job
%   j = BATCH(cluster, 'aScript') runs the script aScript.m on a worker
%   using the identified cluster.  The function returns j, a handle to the
%   job object that runs the script. The script file aScript.m is copied
%   to the worker. If the cluster object's Profile property is not empty,
%   the profile is applied to the job and task that run the script.
%
%   j = BATCH(cluster, fcn, N, {x1,..., xn}) runs the function specified by
%   a function handle or function name, fcn, on a worker using the
%   identified cluster.  The function returns j, a handle to the job object
%   that runs the function. The function is evaluated with the given
%   arguments, x1,...,xn, returning N output arguments.  The function file
%   for fcn is copied to the worker.  If the cluster object's Profile
%   property is not empty, the profile is applied to the job and task that
%   run the function.
%
%   j = BATCH( ..., P1, V1, ..., Pn, Vn) allows additional parameter-value
%   pairs that modify the behavior of the job.  These parameters can be
%   used with both functions and scripts, unless otherwise indicated.  The
%   accepted parameters are:
%
%   - 'Workspace' - A 1-by-1 struct to define the workspace on the worker
%     just before the script is called. The field names of the struct
%     define the names of the variables, and the field values are assigned
%     to the workspace variables. By default this parameter has a field for
%     every variable in the current workspace where batch is executed. This
%     parameter can only be used with scripts.
%
%   - 'AdditionalPaths' - A string, character vector, string array, or cell array
%     of character vectors that defines paths to be added to the workers' MATLAB path
%     before the script or function is executed.
%
%   - 'AttachedFiles' - A string, character vector, string array, or cell array
%     of character vectors.  Each entry in the list identifies either a file or a folder,
%     which is transferred to the worker.
%
%   - 'CurrentFolder' - A string or character vector to indicate in what folder the
%     script executes. There is no guarantee that this folder exists on the
%     worker. The default value for this property is the current folder of
%     MATLAB when the batch command is executed. If the value for this
%     argument is '.', there is no change in folder before batch execution.
%
%   - 'CaptureDiary' - A boolean flag to indicate that diary output should be
%     retrieved from the script execution or function call.  See the DIARY
%     function for how to return this information to the client.  The
%     default is true.
%
%   - 'Pool' - A nonnegative integer or a range specified as a 2-element
%     vector of integers that defines the size of the parallel pool to use
%     when running the job. If the value is a range, the resulting pool has
%     size as large as possible in the range requested. This value
%     overrides the NumWorkersRange specified in the profile. The default
%     is 0, which causes the script or function to run on only the single
%     worker without a pool.
%
%   - 'EnvironmentVariables' - A character vector, string, string array, or cell
%     array of character vectors that defines the names of environment variables
%     which will be copied from the client session to the workers.
%
%   Examples:
%   % Run a batch script on a worker
%   myCluster = parcluster; % creates the default cluster
%   j = batch(myCluster, 'script1');
%
%   % Run a batch script, capturing the diary, adding a path to the workers
%   % and transferring some required files.
%   j = batch(myCluster, 'script1', ...
%             'AdditionalPaths', '\\Shared\Project1\HelperFiles',...
%             'AttachedFiles', {'script1helper1', 'script1helper2'});
%   % Wait for the job to finish
%   wait(j)
%   % Display the diary
%   diary(j)
%   % Get the results of running the script in this workspace
%   load(j)
%
%   % Run a batch script on a remote cluster using a pool of 8 workers:
%   j = batch(myCluster, 'script1', 'Pool', 8);
%
%   % Run a batch function on a remote cluster that generates a 10-by-10
%   % random matrix
%   j = batch(myCluster, @rand, 1, {10, 10});
%   % Wait for the job to finish
%   wait(j)
%   % Display the diary
%   diary(j)
%   % Get the results of running the job into a cell array
%   r = fetchOutputs(j)
%   % Get the generated random number from r
%   r{1}
%
%   % Run some batch scripts and then use findJob to retrieve the jobs.
%   batch(myCluster, 'script1');
%   batch(myCluster, 'script2');
%   batch(myCluster, 'script3');
%   myBatchJobs = findJob(myCluster, 'Username', 'myUsername')
%   
%   See also batch, parcluster, parallel.Cluster/parpool,
%            parallel.Cluster/Jobs, parallel.Cluster/findJob,
%            parallel.Job/wait, parallel.Job/load, parallel.Job/diary.

% Copyright 2011-2018 The MathWorks, Inc.

import parallel.internal.cluster.BatchHelper2
import parallel.internal.apishared.ProfileConfigHelper

% Convert any string inputs to character vectors
[scriptName, varargin{:}] = convertStringsToChars(scriptName, varargin{:});
        
validateattributes( obj, {'parallel.Cluster'}, {'scalar'}, 'batch', 'cluster', 1 );

parallel.internal.cluster.checkNumberOfArguments('input', 1, inf, ...
                                                 nargin, 'batch');

pch = ProfileConfigHelper.buildApi2();
% Parse the arguments in
try
    batchHelper = BatchHelper2( pch, scriptName, varargin );
catch err
    % Make all errors appear from batch
    throw(err);
end

% Deal with the Workspace and configuration.  Set the WorkspaceIn to the
% caller if one wasn't supplied.  Note that this code has to exist here
% because multiple evalin calls cannot be nested.  This code can't even be
% put into a script, because evalin('caller') from the script just gets
% this function's workspace and not the caller of this function.  If you
% change this code, make sure you change the versions in @jobmanager/batch.m
% and batch.m as well.
if batchHelper.needsCallerWorkspace
    where = 'caller';
    % No workspace supplied - we need to make our own from the calling workspace
    vars = evalin(where, 'whos');
    % Loop over each variable in the calling workspace to get its value
    numVars = numel(vars);
    varNames = cell(numVars, 1);
    varValues = cell(numVars, 1);
    for ii = 1:numVars
        varNames{ii} = vars(ii).name;
        varValues{ii} = evalin(where, vars(ii).name);
    end
    batchHelper.filterAndSetCallerWorkspace(varNames, varValues);
end

% Check that a configuration is not set in the args
if ~isempty(batchHelper.Configuration)
    error(message('parallel:cluster:BatchMethodProfileSpecified'));
end
batchHelper.Configuration = obj.Profile;

% Actually run batch on the scheduler
try
    job = batchHelper.doBatch(obj);
catch err
    % Make all errors appear from batch
    throw(err);
end
end
