function [X,FLAG,INVALID] = dbfun(FUNC,D,varargin)
% dbfun  Apply function to each database field.
%
% Syntax
% =======
%
%     [D,FLAG,INVALID] = dbfun(FUNC,D1,...)
%     [D,FLAG,INVALID] = dbfun(FUNC,D1,D2,...)
%
% Input arguments
% ================
%
% * `FUNC` [ function_handle | char ] - Function that will be applied to each
% field.
%
% * `D1` [ struct ] - First input database.
%
% * `D2` [ struct ] - Second input database (when `fun` accepts two input
% arguments).
%
% Output arguments
% =================
%
% * `D` [ struct ] - Output database whose fields will be created by
% applying `fun` to each field of the input database or databases.
%
% * `FLAG` [ `true` | `false` ] - True if no error occurs when evaluating
% the function.
%
% * `INVALID` [ cellstr ] - List of fields on which the function fails.
%
% Options
% ========
%
% * `'cascade='` [ *`true`* | `false` ] - Cascade through subdatabases applying
% the function `fun` to their fields, too.
%
% * `'classFilter='` [ cell | cellstr | *`Inf`* ] - Apply `fun` only to the
% fields of selected classes.
%
% * `'fresh='` [ `true` | *`false`* ] - Keep uprocessed fields in the output
% database.
%
% Description
% ============
%
% Example
% ========
%

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

% Parse input arguments.
P = inputParser();
P.addRequired('fun',@(x) isa(x,'function_handle') || ischar(x));
P.addRequired('d',@isstruct);
P.parse(FUNC,D);

% Find last database in varargin
last = find(cellfun(@isstruct,varargin),1,'last') ;
if isempty(last)
    last = 0;
end

options = passvalopt('data.dbfun',varargin{last+1:end});

% Bkw compatibility.
if ~isempty(options.merge)
    options.fresh = ~options.merge;
end

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

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

list = fieldnames(D);

if options.merge
    X = D;
else
    X = struct();
end

FLAG = true;
INVALID = {};
for i = 1 : length(list)
    if isstruct(D.(list{i}))
        % Process subdatabases.
        if ~options.cascade
            % If not cascading, include the unprocessed subdatabase from the
            % first input database in the output database only if the option
            % `'fresh='` is false.
            if ~options.fresh
                X.(list{i}) = D.(list{i});
            end
            continue
        end
        try
            arglist = dogetarglist();
            if all(cellfun(@isstruct,arglist))
                X.(list{i}) = dbfun(FUNC,arglist{:}, ...
                    'classFilter',options.classfilter, ...
                    'fresh',options.fresh);
            else
                X.(list{i}) = struct();
            end
        catch %#ok<CTCH>
            X.(list{i}) = struct();
            INVALID{end+1} = list{i}; %#ok<AGROW>
        end
    elseif ~isnumeric(options.classfilter) ...
            && ~any(strcmp(class(D.(list{i})),options.classfilter))
        % This field fails to pass the class test.
        continue
    else
        % Process this field.
        try
            arglist = dogetarglist();
            X.(list{i}) = FUNC(arglist{:});
        catch %#ok<CTCH>
            X.(list{i}) = NaN;
            INVALID{end+1} = list{i}; %#ok<AGROW>
        end
    end
end

% Remove invalid entries.
X = rmfield(X,INVALID);

%**************************************************************************
    function arglist = dogetarglist()
        arglist = cell(1,last+1);
        arglist{1} = D.(list{i});
        for k = 1 : last
            arglist{k+1} = varargin{k}.(list{i});
        end
    end
% dogetarglist().

end