function [dbase,list0,list,flag] = dbbatch(dbase,namemask,exprmask,varargin)
% dbbatch  Run a batch job within a database.
%
% Syntax
% =======
%
%     [D,LIST] = dbbatch(D,NAME,COMMAND,...)
%
% Input arguments
% ================
%
% * `D` [ struct ] - Input database.
%
% * `NAME` [ char ] - Pattern that will be used to create names for new
% database entries; the pattern can include $0, $1, etc to refer to tokens
% capture in the regular expression specified throught the `'nameFilter='`
% option.
%
% * `COMMAND` [ char ] - Command that will be evaluated on a selection of
% existing database entries to create new database entries.
%
% Output arguments
% =================
%
% * `D` [ struct ] - Output database.
%
% * `LIST` [ cellstr ] - List of database names that have been processed.
%
% Options
% ========
%
% * `'classFilter='` [ char | *`Inf`* ] - From the existing database entries,
% select only those that are objects of the specified class or classes, and
% evaluate the `COMMAND` on these.
%
% * `'fresh='` [ `true` | *`false`* ] - If true, the output database will only
% contain the newly created entries; if false the output database will also
% include all the entries from the input database.
%
% * `'nameFilter='` [ char | *`Inf`* ] - From the existing database entries,
% select only those that match this regular expression, and evaluate the
% `COMMAND` on these.
%
% * `'nameList='` [ cellstr | *`Inf`* ] - Evaluate the `COMMAND` on this
% list of existing database entries.
%
% * `'stringList='` [ cellstr | *empty* ] - Evaluate the `COMMAND` on this
% list of strings; the strings do not need to be names existing in the
% database; this options cannot be comined with `'nameFilter='`,
% `'nameList='`, or `'classFilter='`.
%
% Description
% ============
%
% Example
% ========
%
% Suppose that in database `D` you want to seasonally adjust all time
% series whose names end with `_u`, and give these seasonally adjusted series
% names without the _u.
%
%     d = dbbatch(d,'$1','x12(d.$0)','nameFilter','(.*)u');
%
% or, if you want to make sure only tseries objects will be selected (in
% case there are database entries ending with a `u` other than tseries
% objects)
%
%     d = dbbatch(d,'$1','x12(d.$0)', ...
%         'nameFilter=','(.*)u','classFilter=','tseries');
%

% -IRIS Toolbox.
% -Copyright (c) 2007-2012 Jaromir Benes.

% TODO: Streamline dbquery/unmask.

if ~isstruct(dbase) ...
        || ~ischar(namemask) ...
        || ~ischar(exprmask) ...
        || ~iscellstr(varargin(1:2:nargin-3))
    error('Incorrect type of input argument(s).');
end

options = passvalopt('data.dbbatch',varargin{:});

% Bkw compatibility.
% Deprecated options 'merge' and 'append'.
if ~isempty(options.merge)
    options.fresh = ~options.merge;
elseif ~isempty(options.append)
    options.fresh = ~options.append;
end

if ischar(options.namelist)
    options.namelist = regexp(options.namelist,'\w+','match');
end

if ischar(options.stringlist)
    options.stringlist = regexp(options.stringlist,'\w+','match');
end

if (~isequal(options.namelist,Inf) ...
        || ~isequal(options.classfilter,Inf) ...
        || ~isequal(options.namefilter,Inf)) ...
        && ~isempty(options.stringlist)
    utils.error('data', ...
        ['In DBBATCH, option ''stringList'' cannot be combined with ', ...
        '''classFilter'', ''nameFilter'', or ''nameList''.']);
end

%**************************************************************************

% When called P = dbase(P,...) within a function, P is locked as a variable
% under construction. We therefore need to create a temporary database
% IRIS_TEMP_DBASE in caller's workspace.
assignin('caller','IRIS_TEMP_DBASE__',dbase);
exprmask = regexprep(exprmask, ...
    sprintf('\\<%s\\>',inputname(1)),'IRIS_TEMP_DBASE__');

[list0,list,expr] = xxquery(dbase,namemask,exprmask, ...
    options.classfilter,options.namefilter,options.namelist, ...
    options.freqfilter,options.stringlist);
expr = strrep(expr,'"','''');

flag = true;
if options.fresh
    dbase = struct();
end

errorlist = {};
for i = 1 : length(list0)
    try
        lastwarn('');
        value = evalin('caller',expr{i});
        dbase.(list{i}) = value;
        msg = lastwarn();
        if ~isempty(msg)
            tmpexpr = strrep(expr{i},'IRIS_TEMP_DBASE__',inputname(1));
            fprintf(1,[ ...
                '\n*** The above warning occurred when DBBATCH ', ...
                'attempted to evaluate ''%s''.\n'],tmpexpr);
        end
    catch Error
        errorlist(end+(1:2)) = {expr{i},Error.message};
    end
end

evalin('caller','clear IRIS_TEMP_DBASE__');

if ~isempty(errorlist)
    errorlist = strrep(errorlist,'IRIS_TEMP_DBASE__',inputname(1));
    flag = false;
    warning('iris:data',[ ...
        '\n*** Error evaluating DBBATCH expression ', ...
        '''%s''.\n*** Matlab says: %s'], ...
        errorlist{:});
end

end

% Subfunctions.

%**************************************************************************
function [list0,list,expr] = xxquery(dbase0,namemask,exprmask, ...
    classfilter,namefilter,namelist,freqfilter,stringlist)

list0 = fieldnames(dbase0).';
tokens = cell(size(list0));
tokens(:) = {{}};

if ~isempty(stringlist)
    % String list.
    list0 = stringlist;
    tokens = cell(size(list0));
    tokens(:) = {{}};
else
    % Name list.
    if iscellstr(namelist)
        list0 = intersect(list0,namelist);
    end
    
    % Name filter.
    if ~isequal(namefilter,Inf)
        if namefilter(1) ~= '^'
            namefilter = ['^',namefilter];
        end
        if namefilter(end) ~= '$'
            namefilter = [namefilter,'$'];
        end
        [index,ans,tokens] = strfun.matchindex(list0,namefilter);
        list0 = list0(index);
    end
    
    % Class filter.
    if ~(isnumeric(classfilter) && isinf(classfilter))
        classlist = cell(size(list0));
        for i = 1 : numel(list0)
            classlist{i} = class(dbase0.(list0{i}));
        end
        index = strfun.matchindex(classlist,classfilter);
        list0 = list0(index);
        tokens = tokens(index);
    end
    
    % Frequency filter.
    if ~isequal(freqfilter,Inf)
        index = false(size(list0));
        for i = 1 : numel(list0)
            index(i) = ~isa(dbase0.(list0{i}),'tseries') ...
                || any(freq(dbase0.(list0{i})) == freqfilter);
        end
        list0 = list0(index);
        tokens = tokens(index);
    end
end

% New names.
if nargout > 1
    if ~isempty(namemask)
        list = cell(size(list0));
        for i = 1 : numel(list0)
            list{i} = xxunmask(namemask,list0{i},tokens{i}{:});
        end
    else
        list(1:length(list0)) = {''};
    end
end

% Expressions.
if nargout > 2
    if ~isempty(exprmask)
        expr = cell(size(list0));
        for i = 1 : numel(list0)
            expr{i} = xxunmask(exprmask,list0{i},tokens{i}{:});
        end
    else
        expr(1:length(list0)) = {''};
    end
end

end
% xxquery().

%**************************************************************************
function unmask = xxunmask(mask,varargin)
if isempty(mask)
    unmask = '';
else
    unmask = mask;
    for i = 1 : nargin-1
        unmask = strrep(unmask, ...
            sprintf('lower($%g)',i-1),lower(varargin{i}));
        unmask = strrep(unmask, ...
            sprintf('upper($%g)',i-1),upper(varargin{i}));
        unmask = strrep(unmask, ...
            sprintf('$%g',i-1),varargin{i});
    end
    unmask = regexprep(unmask,'\$\d*','');
end
end
% xxunmask().