function cost = getCost(problem, x, storedb, key)
% Computes the cost function at x.
%
% function cost = getCost(problem, x)
% function cost = getCost(problem, x, storedb)
% function cost = getCost(problem, x, storedb, key)
%
% Returns the value at x of the cost function described in the problem
% structure.
%
% storedb is a StoreDB object, key is the StoreDB key to point x.
%
% See also: canGetCost

% This file is part of Manopt: www.manopt.org.
% Original author: Nicolas Boumal, Dec. 30, 2012.
% Contributors: 
% Change log: 
%
%   April 3, 2015 (NB):
%       Works with the new StoreDB class system.
%
%   Aug. 2, 2018 (NB):
%       The value of the cost function is now always cached.
%
%   Sep. 6, 2018 (NB):
%       If the gradient is computed too (because we had to call costgrad
%       with the store as input as per the user's request), then the
%       gradient is also cached.

    % Allow omission of the key, and even of storedb.
    if ~exist('key', 'var')
        if ~exist('storedb', 'var')
            storedb = StoreDB();
        end
        key = storedb.getNewKey();
    end


    % Contrary to most similar functions, here, we get the store by
    % default. This is for the caching functionality described below.
    store = storedb.getWithShared(key);
    store_is_stale = false;

    % If the cost function has been computed before at this point (and its
    % memory is still in storedb), then we just look up the value.
    if isfield(store, 'cost__')
        cost = store.cost__;
        return;
    end
    

    if isfield(problem, 'cost')
    %% Compute the cost function using cost.
    
        % Check whether this function wants to deal with storedb or not.
        switch nargin(problem.cost)
            case 1
                cost = problem.cost(x);
            case 2
                [cost, store] = problem.cost(x, store);
            case 3
                % Pass along the whole storedb (by reference), with key.
                cost = problem.cost(x, storedb, key);
                % The store structure in storedb might have been modified
                % (since it is passed by reference), so before caching
                % we'll have to update (see below).
                store_is_stale = true;
            otherwise
                up = MException('manopt:getCost:badcost', ...
                    'cost should accept 1, 2 or 3 inputs.');
                throw(up);
        end
        
    elseif isfield(problem, 'costgrad')
    %% Compute the cost function using costgrad.
    
        % Check whether this function wants to deal with storedb or not.
        switch nargin(problem.costgrad)
            case 1
                cost = problem.costgrad(x);
            case 2
                [cost, grad, store] = problem.costgrad(x, store);
            case 3
                % Pass along the whole storedb (by reference), with key.
                cost = problem.costgrad(x, storedb, key);
                store_is_stale = true;
            otherwise
                up = MException('manopt:getCost:badcostgrad', ...
                    'costgrad should accept 1, 2 or 3 inputs.');
                throw(up);
        end

    else
    %% Abandon computing the cost function.

        up = MException('manopt:getCost:fail', ...
            ['The problem description is not explicit enough to ' ...
             'compute the cost.']);
        throw(up);
        
    end
    
    % If we are not sure that the store structure is up to date, update.
    if store_is_stale
        store = storedb.getWithShared(key);
    end
    
    % Cache here.
    store.cost__ = cost;
    
    % If we got the cost via costgrad and it took the store as input, then
    % the gradient has also been computed and we can cache it.
    if exist('grad', 'var')
        store.grad__ = grad;
    end

    storedb.setWithShared(store, key);
    
end
