function statsfun = statsfunhelper(inp1, inp2)
% Helper tool to create a statsfun for the options structure of solvers.
%
% function statsfun = statsfunhelper(name, fun)
% function statsfun = statsfunhelper(S)
%
% Usage with (name, fun):
%
% Input 1: name is a string which is a valid field name (no spaces, starts
% with a letter or an underscore, only alphanumeric characters and
% underscores).
% 
% Input2: fun is a function handle with one output and 1 to 4 inputs, as
% follows (your choice):
% 
%  fun(x)  or  fun(problem, x)  or  
%  fun(problem, x, stats)  or  fun(problem, x, stats, store)
% 
% where the inputs are the ones that would be given to options.statsfun, as
% described in the help of the solver used. Typically, x is the point on
% the manifold at the current iterate, problem is the Manopt problem
% structure, stats is all the current statistics recorded for that iterate
% and store is the cache structure at the current iterate.
%
% When calling a Manopt solver with the options structure, such as for
% example with:
%
%  [x, xcost, info] = steepestdescent(problem, [], options);
%
% you may set a field of the options structure as follows:
%
%  options.statsfun = statsfunhelper('nameofthefield', fun);
%
% As a result, at each iteration, the stats structure will contain a field
% stats.nameofthefield with the value returned by the call to fun at that
% iterate. The stats structures are stored in the struct-array info.
% As an example, if the value returned by fun is a scalar, then
% [info.nameofthefield] is a vector containing all returned values.
%
%
% Usage with S:
%
% The input S is a structure. For each field of S, say S.field, the stats
% structure will be augmented with stats.field = fun(..), where fun is the
% function handle stored in S.field, and with the same conventions as
% above. This version allows to record more than one bit of information at
% each iteration. Example:
% 
%  metrics.nameofthefield = fun;
%  metrics.othername = otherfun;
%  options.statsfun = statsfunhelper(metrics);
%
% The different function handles (here, fun and otherfun) can take 1 to 4
% inputs too, and they do not have to take the same number of inputs.

% This file is part of Manopt: www.manopt.org.
% Original author: Nicolas Boumal, Dec. 17, 2014.
% Contributors: 
% Change log: 
%     Jan 2, 2021 (NB):
%         Passing S to thestatsfun explicitly for compatibility with Octave 6.

    if (nargin == 1) && isstruct(inp1)
        S = inp1;
    elseif (nargin == 2)
        S = struct(inp1, inp2);
    else
        error('statsfunhelper takes 1 or 2 inputs. If 1 input, it must be a structure.');
    end

    statsfun = @(problem, x, stats, store) thestatsfun(S, problem, x, stats, store);

    
    function stats = thestatsfun(S, problem, x, stats, store)
        names = fieldnames(S);
        for it = 1 : length(names)
            name = names{it};
            fun = S.(name);
            switch nargin(fun)
                case 1
                    stats.(name) = fun(x);
                case 2
                    stats.(name) = fun(problem, x);
                case 3
                    stats.(name) = fun(problem, x, stats);
                case 4
                    stats.(name) = fun(problem, x, stats, store);
                otherwise
                    error('The functions passed to statsfunhelper must take 1 to 4 inputs.');
            end
        end
    end

end
