function returnData = generateData(functionHandle, argin, nargout, ...
    inputDatabase, outputDatabase, outCaseName, stdOptionStruct)
% handling input/output pairs of databases
%
% Syntax: generateData(functionHandle, argin)
%   OR    generateData(functionHandle, argin, nargout, ...
%           inputDatabaseOptions, outputDatabaseOptions)
%
% This function wraps some extended use cases for databases. A first 
%   database contains input parameters for some time consuming operations 
%   that are stored in an output database. It fully takes care of input and 
%   output handling. Dependent on the function handle, there are three 
%   different use cases:
% Case 1: the input file contains the expensive calculations. In this case,
%   you can assign the fHandle to @(x)(x). View a template, <a href="matlab: help generateData>helpSimpleTemplate" >here</a>.
% Case 2: the data of from the input database is used as an input to an
%   fHandle: @(in)(MAGIC(in)). View a template, <a href="matlab: help generateData>helpNormalTemplate" >here</a>.
% Case 3: the data from the input database AND additional parameters in a
%   structure are used as input to the fHandle @(in, par)(MAGIC(in, par)).
%   In this case generateData must be called as generateData(fHandle, 
%   argin, nargout, {}, {}, @outCaseName). The parameters must be properly 
%   reflected by a file name (without extension) from outCaseName as a 
%   handle @(caseName, par). View a template, <a href="matlab: help generateData>helpExtendedTemplate" >here</a>.
%
% The input and output database configuration can be adopted, based on 
%  <a href="matlab: help obtainData">obtainData</a>. The standard types and database names are set to:
%     inputDatabase  = {'type' , 'm', 'database', ./database}
%     outputDatabase = {'type' , 'mat', 'database', ./data}
% Changing the type in the latter case is, however, not wise. You should 
%   also exclude the 'prompt' or 'databaseRoot' options as they are
%   required for a meaningful operation of generateData.
%
% See also: obtainData, mapOptionStruct

% MDT: MATLAB developement tools
%   Copyright (C) 2013 Alexander Reinhold
%
% This program is free software: you can redistribute it and/or modify it 
%   under the terms of the GNU Lesser General Public License as published 
%   by the Free Software Foundation, either version 3 of the License, or 
%   (at your option) any later version.
%
% This program is distributed in the hope that it will be useful, but 
%   WITHOUT ANY WARRANTY; without even the implied warranty of 
%   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
%   Lesser General Public License for more details.
%
% You should have received a copy of the GNU Lesser General Public License 
%   along with this program.  If not, see <http://www.gnu.org/licenses/>.

%! ToDo: using scripts in functions is not the very best practice..
%    ..refactoring is required, here.
%/

%% Input handling
% not provided input variables
if ~exist('inputDatabase', 'var')
    inputDatabase = {};
end
if ~exist('outputDatabase', 'var')
    outputDatabase = {};
end
if ~exist('outCaseName', 'var')
    outCaseName = '';
end
if ~exist('stdOptionStruct', 'var')
    stdOptionStruct = '';
end

% handle varying input (argin) that consists of caseName, enforce flag and
%   a cell array of optional input arguments
[caseName par flagEnforce] = handleInput(argin, outCaseName);

% the enforce option is also required, if the caseName is valid, but no
%   actual case. These options are 'init' and ''.
if strcmpi(caseName, 'init') || isempty(caseName)
    flagEnforce = 1;
end

%% Determine database root
% note that the caller of obtainData is not the original function, but this
%   wrapper. Hence, we need to handle this here and now, identifying the
%   callers location
if isempty(dbstack(1))
    % called from console
    databaseRoot = pwd;
else
    % who called me:
    callerName = getfield(dbstack(1), 'name');
    % where is the caller located:
    databaseRoot = fileparts(which(callerName));
end


%% Processing options
if isempty(stdOptionStruct)
    % input database does not provide options for output generation
    
    % outcase name can be defined directly
    if  ~isa(outCaseName, 'function_handle')
        outCaseName = caseName;
    else
        outCaseName = outCaseName(caseName, par);
    end
    
    % try to load output data
    scriptLoadOutputData;
    if flagExit
        scriptApplyOutput;
        return;
    end
    
    % load input data
    scriptLoadInputData;
    
else
    % input database DOES provide standard options for output generation >>
    %   the input data must be loaded first to know this standard dataset
    
    % load input data
    scriptLoadInputData;
    par = mergeStruct(par, inData.(stdOptionStruct));
    %! ToDo: error handling for missing stdOptionStruct
    %/
    
    % outcase name can now be defined
    if  ~isa(outCaseName, 'function_handle')
        outCaseName = caseName;
    else
        outCaseName = outCaseName(caseName, par);
    end
    
	% try to load output data
    scriptLoadOutputData;
    if flagExit
        scriptApplyOutput;
        return
    end;
end

%% Catch special case names
% If this function was called with caseName euqal to 'init' or '', there is
%   nothing to be calculated.
if strcmpi(caseName, 'init') || isempty(caseName)
    % we ensure that outData contains the case list, if demanded
    if nargout == 1
        returnData = {inData};
    else
        returnData = {};
    end
    % we simply abort this function
    return
end

%% Perform calculations
% Well, we simple use a pause command here to demonstrate time
%   improvements.
if isempty(par)
    outData = functionHandle(inData);
else
    outData = functionHandle(inData, par);
end

%% Save data
% first we need to find out, if the user has specified a different folder
%   for output data
outputFile = [databaseRoot filesep 'data' filesep outCaseName];
for iArgument = 1:2:length(outputDatabase)
    if strcmpi(outputDatabase{iArgument}, 'database')
        if outputDatabase{iArgument+1}(1) == '.'
            % relative path
            outputFile = [databaseRoot ...
                outputDatabase{iArgument+1}(2:end) filesep outCaseName];
        else
            % absolute path
            outputFile = [outputDatabase{iArgument+1} filesep outCaseName];
        end
    end
end
% To make the data available for future calls, we have to store it. To
%   ensure it is stored in the ./data folder relative to the location of
%   this function, we store this location as absolute path.

% Save data
save(outputFile, 'outData');
% We should then update the database in ./data. The command is similar to 
%   the one used for "Try to load existent data", while caseName is 'init'.
% We don't want any output and we dont want any printout, so we use the
%   'prompt' option with -1.
obtainData('init', ...
    'databaseRoot', databaseRoot, ...
    'database', ['.' filesep 'data'], ...
    'type', 'mat', ...
    'prompt', -1, ...
    outputDatabase{:});

% wrap output
scriptApplyOutput

end

function helpSimpleTemplate
%%%% Template function:
% function varargout = generateExample(varargin)
% % execute and save, or load calculations from database
% %
% % Syntax: data = generateExample(caseName)
% %
% % Use caseName='init' to initialize the input database, stored in
% %   ./database. Use generateExample() to list existing cases and
% %   caseList=generateExample() to obtain existing cases as a structure
% %   array.
% %
% % Use generateExample(caseName, enforce) with enforce=1 to enforce the
% %   recalculation of a case.
%
% varargout = generateData(@(x)(x), varargin, nargout)
%
% end
%%/ 
end

function helpNormalTemplate
%%%% Template function:
% function varargout = generateExample(varargin)
% % execute and save, or load calculations for a specific case
% %
% % Syntax: data = generateExample(caseName)
% %
% % Use caseName='init' to initialize the input database, stored in
% %   ./database. Use generateExample() to list existing cases and
% %   caseList=generateExample() to obtain existing cases as a structure
% %   array.
% %
% % Use generateExample(caseName, enforce) with enforce=1 to enforce the
% %   recalculation of a case.
%
% varargout = generateData(@myMAGIC, varargin, nargout)
% end
%
% function outData = myMAGIC(inData)
% outData = HOCUSPOCUS(inData)
% end
%%/ 
end

function helpExtendedTemplate
%%%% Template function:
% function varargout = generateExample(varargin)
% % execute and save, or load calculations for a specific case
% %
% % Syntax: data = generateExample(caseName)
% %
% % Use caseName='init' to initialize the input database, stored in
% %   ./database. Use generateExample() to list existing cases and
% %   caseList=generateExample() to obtain existing cases as a structure
% %   array.
% %
% % Use generateExample(caseName, enforce) with enforce=1 to enforce the
% %   recalculation of a case.
%
% varargout = generateData(@myMAGIC, varargin, nargout, {}, {}, ...
%      @myOutCaseName)
% 
% end
%
% function outData = myMAGIC(inData, par)
% outData = HOCUSPOCUS(inData, par)
% end
%
% function outCaseName = myOutCaseName(caseName, par)
% % the operation above is dependent on the wand, we use. It's type and
% %   size is reflected py the parameters. And the parameters must not
% %   contain any other data that is changed for the individual cases.
% outCaseName = [caseName num2str(par.wandType) '_' num2str(par.wandSize)];
% end
%%/ 
end
