function results = utl_run_batchjob(opts,d,appname,setnames)
% Internal: the actual processing function of bci_batchtrain.
% Results = ult_run_batchjob(Options,DatasetIndex,ApproachName,DatasetNames)
%
% This function executes one of the individual jobs generated by bci_batchtrain (application of one
% approach to one dataset).
%
% In:
%   Options : bci_batchtrain options struct
%
%   DatasetIndex : index into Options.datasets that determines which dataset to process
%
%   ApproachName : name of a field in Options.approaches that determines which approach to use
%
%   DatasetNames : names of the respective datasets for diagnostics
%   
% Out:
%   Results : result data structure
%
% See also:
%   bci_batchtrain
% 
%                                Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
%                                2011-08-19
dp;

% scaffolding to support custom error handlers (ultimately calls do_run to do the actual work)
results = [];
if strcmp(char(opts.handler),'rethrow')
    results = do_run(opts,d,appname,setnames);
else
    try
        results = do_run(opts,d,appname,setnames);
    catch e
        fprintf('Error processing data set "%s" with approach "%s".',setnames{d},appname);
        if ischar(opts.handler)
            try
                opts.handler = str2func(opts.handler); 
            catch e
                error('The given error handler is malformed: %s',hlp_tostring(opts.handler,100));
            end
        end
        opts.handler(e);
    end
end

function results = do_run(opts,d,appname,setnames)
    % run a particular approach on a particular data set
    results = [];    
    
    % determine where results would be saved
    storename = env_translatepath(strrep(strrep(opts.storepatt,'%set',setnames{d}),'%approach',appname));    
    if opts.reuse && ~isempty(storename) && exist(storename,'file')
        
        % attempt to load existing results from disk
        fprintf('Reusing existing result for approach "%s" on set "%s".\n',appname,setnames{d});
        t0=tic; fprintf('Trying to load file %s...',storename);
        if opts.cache_loaded
            persistent result_cache; %#ok<TLEV>
            storetag = ['x' hlp_cryptohash(storename)];
            if isfield(result_cache,storetag)
                res = result_cache.(storetag);
            else
                io_load(storename);
                result_cache.(storetag) = res; %#ok<NODEF>
            end
        else
            io_load(storename);
        end
        fprintf('%.1f seconds (%.1f MB/s).\n',toc(t0),getfield(whos('res'),'bytes')/2^20/toc(t0));
        
    elseif ~opts.loadonly
        
        % train a model on the Dataset using bci_train
        [res.loss,res.model,res.stats] = bci_train(opts.trainargs{:}, 'data',opts.datasets{d}, 'approach',opts.approaches.(appname), 'markers',opts.markers);
        
        % optionally run bci_predict on each of the PredictSets
        if ~isempty(opts.predictsets) && ~isempty(opts.predictsets{d})
            for k=1:length(opts.predictsets{d})
                try
                    [res.pred_predictions{k},res.pred_loss(k),res.pred_stats{k},res.pred_targets{k}] = bci_predict(opts.predictargs{:},'data',opts.predictsets{d}{k},'model',res.model, 'markers',opts.markers);
                catch e
                    [res.pred_predictions{k},res.pred_loss(k),res.pred_stats{k},res.pred_targets{k}] = deal([],NaN,struct(),[]);
                    fprintf('Error computing predictions for set "%s", prediction set #%i with approach "%s".\n',setnames{d},k,appname);
                    opts.handler(e);
                end
            end
            res.pred_loss = res.pred_loss';
        end
            
        % save results
        if ~isempty(storename)
            t0=tic; fprintf('Saving results as %s...',storename);
            io_save(storename,opts.saveargs{:},'res'); 
            fprintf('%.1f seconds (%.1f MB/s).\n',toc(t0),getfield(whos('res'),'bytes')/2^20/toc(t0));
        end
    end
    
    % assign results to result data structure
    if ~isempty(opts.resultpatt)
        try
            statement = [strrep(strrep(opts.resultpatt,'%num',num2str(d)),'%approach',appname) ' res;'];
            eval(statement); 
        catch e
            fprintf('Failed to evaluate ResultPattern (statement was "%s") with error: %s.\n',statement,e.message);
        end        
    end
    
    % print diagnostics
    try
        if isfield(res,'pred_loss')
            fprintf('%s@%s: train loss: %.4f, prediction loss: %.4f\n',appname,setnames{d},res.loss,res.pred_loss);
        else
            fprintf('%s@%s: train loss: %.4f\n',appname,setnames{d},res.loss);
        end
    catch e
        fprintf('Failed to display loss estimates with error: %s\n',e.message);
    end
