function data = obtainData(elementName, varargin)
% obtain data from files stored in database folder
%
% Syntax: obtainData(elementName)
%
% Handles a set of data in a database folder. It automatically generates
%   an index file which can be updated anytime with obtainData('init')). It
%   returns the variables in mat-files or function as a sructure. M-file
%   scripts are also supported. The following table gives some database
%   cases.
%
% Cases of database files:
%   * mat-file: variables in mat file are returned in the data structure
%       with their original names. If a single structure was strored in the
%       mat-file this structure is returned. This allows access via 
%       data.element instead of data.structureName.element.
%   * m-file containing a function: a single return variable is returned in
%       data directly. Multiple variables are returned as fields data.out1,
%       data.out2 and so on. You can specify the fieldnames, however with
%       the 'resultNameList' option that takes a cell array of strings
%       (Default: {}). With the option 'nResult' you can enforce the 
%       function to be called with the given number of expected output
%       variables (Default: []).
%   * m-files containing a script. The script variables are returned as
%       fields of data. If the script returns a single structure, the
%       behavior as for mat-files applies.
%
% Use obtainData(.., 'database', pathLocation) to set the path where
%   database files are stored. An absolute path can be used or a relative
%   path that starts with ['.' filesep] which equals './' in unix but '.\'
%   in windows. Default: ['.' filesep 'database']
%
% Use obtainData(.., 'type', type) to specify the type of your database
%   that might be either 'm' for mFiles or 'mat' for matlab matrices.
%   Default: 'm'
%
% Use obtainData(.., 'identifyer', identifyCode) to specify how a dataset
%   is identified. For identifyCode=0, the filename without extension is
%   used. For identifyCode being a string, it contains an element of the
%   structure that is returned by the data file (e.g. 'name' or 'out1'). 
%   Default: 0
%
% obtainData(.., 'indexFile', fileName) specifies the name of the
%   mat-file that is used to store indexing information. The file is
%   treated as a mat-file independent of its extension. 
%   Default: fileList.matx
%
% You might enforce printing available options, for elementName being
%   empty or equal to 'init', while additionally returning the caseList to
%   a variable. Use obtainData(.., 'prompt', 1) in that case. To prevent
%   displaying the cases, use obtainData(.., 'prompt', -1). Default: 0
%
% obtainData(.., 'verbose', 1) gives some additional verbosity. Default: 0
%
% see also: mergeStruct, generateData

% MDT: MATLAB developement tools
%   Copyright (C) 2012 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/>.

%% input handling
[databaseFolder, databaseType, databaseIdentifier databaseIndexFile, ...
    resultNameList, nResult, verbose, promptList, databaseRoot] = ...
    mapOption(varargin, ...
    'database', ['.' filesep 'database'], ...
    'type', 'm', ...
    'identifier', 0, ...
    'indexFile', 'fileList.matx', ...
    'resultNameList', {}, ...
    'nResult', [], ...
    'verbose', 0, ...
    'prompt', 0, ...
    'databaseRoot', '');

%% database location
% relative or absolute path..
if strcmp(databaseFolder(1:2), ['.' filesep])
    % relative path

    if isempty(databaseRoot)
        % database root not provided manually
        
        if isempty(dbstack(1))
            % called from console
            databasePath = [pwd databaseFolder(2:end)];
        else
            % who called me:
            callerName = getfield(dbstack(1), 'name');
            % where is the caller located:
            callerLocation = fileparts(which(callerName));
            % then this is the path
            databasePath = [callerLocation databaseFolder(2:end)];
        end
    else
        % manually provided database root
        if databaseRoot(end) == filesep
            databasePath = [databaseRoot databaseFolder(3:end)];
        else
            databasePath = [databaseRoot databaseFolder(2:end)];
        end
    end
   
    
    clear callerName callerLocation
else
    % absolute path
    databasePath = databaseFolder;
end
clear databaseFolder
if verbose,disp(['Database path: ' databasePath]);end;

% does database folder exist
if ~exist(databasePath, 'dir')
    error('mdt:obtainData:wrongInput', ['Database folder ' databasePath ...
        ' does not exist.']);
end

%% generate database (if required)

if ~exist([databasePath filesep databaseIndexFile], 'file') || ...
   (exist('elementName', 'var') && strcmpi(elementName, 'init'))

   % these are the database files
   fileList = dir([databasePath filesep '*.' databaseType]);
   % filter database index file (if required)
   if strcmp(databaseType, 'mat') && ...
      exist([databasePath filesep databaseIndexFile '.mat'], 'file')
       for iFile = 1:length(fileList)
            if strcmpi([databaseIndexFile '.mat'], fileList(iFile).name)
                fileList(iData) = [];
                break
            end
       end
   end
   
   % no files found
   if isempty(fileList)
      error('mdt:database:empty', ['Database directory ' databasePath ...
          ' seems to have no ' databaseType ' files']);
   end

   caseList(length(fileList)).name = [];
   caseList(length(fileList)).file = [];
   validEntryVector = nan(1, length(fileList));
   % cycle through files
   for iFile = 1:length(fileList)
       if verbose,disp(['File name: ' fileList(iFile).name]);end;
       
       if databaseIdentifier ~= 0
           % get data once
           thisData = handleFile(databasePath, fileList(iFile).name, ...
               resultNameList);
           try
               caseList(iFile).name = thisData.(databaseIdentifier);
           catch
               warning(['Database file ' fileList(iFile).name ' does not ' ...
                   'return the field/variable ''' databaseIdentifier '''.']);
               validEntryVector(iFile) = 0;
               continue;
           end
       elseif strcmpi(databaseType, 'm')
           caseList(iFile).name = fileList(iFile).name(1:(end-2));
       elseif strcmpi(databaseType, 'mat')
           caseList(iFile).name = fileList(iFile).name(1:(end-4));
       else
           error('mdt:obtainData:unknown', 'this should not happen');
       end
       caseList(iFile).file = fileList(iFile).name;
       validEntryVector(iFile) = 1;
       if verbose,disp(['  Identifier: ' caseList(iFile).name]);end;
   end
   % remove invalid entries
   caseList(validEntryVector==0) = [];
   % save entries
   save([databasePath filesep databaseIndexFile], 'caseList');
else
    caseList = getfield(load([databasePath filesep databaseIndexFile], ...
        '-mat'), 'caseList');
end

% Result: caseList contains the cases that can be obtained. And the index
%   file is generated.

%% print information
flagNoCaseName = ~exist('elementName', 'var') || ...
    isempty(elementName) || ...
    strcmpi(elementName, 'init');
if flagNoCaseName && (nargout == 0 || promptList) && ~(promptList == -1)
    disp('The following data sets can be called:')
    for iCase = 1:length(caseList)
        disp(['  ' caseList(iCase).name]);
    end
    
end
%% return case list / quit
if flagNoCaseName && nargout >= 1
    data = caseList;
    return
elseif flagNoCaseName
    clear data
    return
end


%% return data
% search the requested case
for iCase = 1:length(caseList)
    if strcmp(elementName, caseList(iCase).name)
        data = handleFile(databasePath, caseList(iCase).file, ...
            resultNameList);
        if isempty(data)
            error('mdt:database:noDataObtained', ['Previous warning ' ...
                'message is responsible']);
        end
        return
    end
end

%% Nothing found
error('mdt:obtainData:wrongInput', ['The specified case''' elementName ...
    ''' was not found.']);

end

