function [cost, grad] = getCostGrad(problem, x, storedb, key)
% Computes the cost function and the gradient at x in one call if possible.
%
% function [cost, grad] = getCostGrad(problem, x)
% function [cost, grad] = getCostGrad(problem, x, storedb)
% function [cost, grad] = getCostGrad(problem, x, storedb, key)
%
% Returns the value at x of the cost function described in the problem
% structure, as well as the gradient at x.
%
% storedb is a StoreDB object, key is the StoreDB key to point x.
%
% See also: canGetCost canGetGradient getCost getGradient

% 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):
%       The gradient is now 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;
    
    % Check if the cost or gradient are readily available from the store.
    force_grad_caching = true;
    if isfield(store, 'cost__')
        cost = store.cost__;
        if force_grad_caching && isfield(store, 'grad__')
            grad = store.grad__;
            return;
        else
            grad = getGradient(problem, x, storedb, key); % caches grad
            return;
        end
    end
    % If we get here, the cost was not previously cached, but maybe the
    % gradient was?
    if force_grad_caching && isfield(store, 'grad__')
        grad = store.grad__;
        cost = getCost(problem, x, storedb, key); % this call caches cost
        return;
    end

    % Neither the cost nor the gradient were available: let's compute both.

    if isfield(problem, 'costgrad')
    %% Compute the cost/grad pair using costgrad.
    
        % Check whether this function wants to deal with storedb or not.
        switch nargin(problem.costgrad)
            case 1
                [cost, grad] = problem.costgrad(x);
            case 2
                [cost, grad, store] = problem.costgrad(x, store);
            case 3
                % Pass along the whole storedb (by reference), with key.
                [cost, grad] = problem.costgrad(x, storedb, key);
                store_is_stale = true;
            otherwise
                up = MException('manopt:getCostGrad:badcostgrad', ...
                    'costgrad should accept 1, 2 or 3 inputs.');
                throw(up);
        end

    else
    %% Revert to calling getCost and getGradient separately
    
        % The two following calls will already cache cost and grad, then
        % the caches will be overwritten at the end of this function, with
        % the same values (it is not a problem).
        cost = getCost(problem, x, storedb, key);
        grad = getGradient(problem, x, storedb, key);
        store_is_stale = true;
        
    end
    
    if store_is_stale
        store = storedb.getWithShared(key);
    end
    
    % Cache here.
    store.cost__ = cost;
    if force_grad_caching
        store.grad__ = grad;
    end
    
    storedb.setWithShared(store, key);
    
end
