function saved = dbsave(d,fname,dates,varargin)
% dbsave  Save database as CSV file.
%
% Syntax
% =======
%
%     LIST = dbsave(D,FNAME)
%     LIST = dbsave(D,FNAME,DATES,...)
%
% Output arguments
% =================
%
% * `LIST` [ cellstr ] - - List of actually saved database entries.
%
% Input arguments
% ================
%
% * `D` [ struct ] - Database whose tseries and numeric entries will be
% saved.
%
% * `FNAME` [ char ] - Filename under which the CSV will be saved,
% including its extension.
%
% * `DATES` [ numeric | *`Inf`* ] Dates or date range on which the tseries
% objects will be saved.
%
% Options
% ========
%
% * `'class='` [ *`true`* | false ] - Include a row with class and size
% specifications.
%
% * `'comment='` [ *`true`* | `false` ] - Include a row with comments for tseries
% objects.
%
% * `'decimal='` [ numeric | *empty* ] - Number of decimals up to which the
% data will be saved; if empty the `'format'` option is used.
%
% * `'format='` [ char | *`'%.8e'`* ] Numeric format that will be used to
% represent the data, see `sprintf` for details on formatting, The format
% must start with a `'%'`, and must not include identifiers specifying
% order of processing, i.e. the `'$'` signs, or left-justify flags, the
% `'-'` signs.
%
% * `'freqLetters='` [ char | *`'YHQBM'`* ] - Five letters to represent the
% five possible date frequencies (annual, semi-annual, quarterly,
% bimonthly, monthly).
%
% * `'nan='` [ char | *`'NaN'`* ] - String that will be used to represent
% NaNs.
%
% * `'userData='` [ char | *'userdata'* ] - Field name from which
% any kind of userdata will be read and saved in the CSV file.
%
% Description
% ============
%
% The data saved include also imaginary parts of complex numbers.
%
% Saving user data with the database
% ------------------------------------
%
% If your database contains field named `'userdata='`, this will be saved
% in the CSV file on a separate row. The `'userdata='` field can be any
% combination of numeric, char, and cell arrays and 1-by-1 structs.
%
% You can use the `'userdata='` field to describe the database or preserve
% any sort of metadata. To change the name of the field that is treated as
% user data, use the `'userData='` option.
%
% Example 1
% ==========
%
% Create a simple database with two time series.
%
%     d = struct();
%     d.x = tseries(qq(2010,1):qq(2010,4),@rand);
%     d.y = tseries(qq(2010,1):qq(2010,4),@rand);
%
% Add your own description of the database, e.g.
%
%     d.userdata = {'My database',datestr(now())};
%
% Save the database as CSV using `dbsave`,
%
%     dbsave(d,'mydatabase.csv');
%
% When you later load the database,
%
%     d = dbload('mydatabase.csv')
%
%     d = 
%
%        userdata: {'My database'  '23-Sep-2011 14:10:17'}
%               x: [4x1 tseries]
%               y: [4x1 tseries]
%
% the database will preserve the `'userdata='` field.
%
% Example 2
% -----------
%
% To change the field name under which you store your own user data, use
% the `'userdata='` option when running `dbsave`,
%
%     d = struct();
%     d.x = tseries(qq(2010,1):qq(2010,4),@rand);
%     d.y = tseries(qq(2010,1):qq(2010,4),@rand);
%     d.MYUSERDATA = {'My database',datestr(now())};
%     dbsave(d,'mydatabase.csv',Inf,'userData=','MYUSERDATA');
%
% The name of the user data field is also kept in the CSV file so that
% `dbload` works fine in this case, too, and returns a database identical
% to the saved one,
%
%     d = dbload('mydatabase.csv')
%
%     d = 
%
%        MYUSERDATA: {'My database'  '23-Sep-2011 14:10:17'}
%                 x: [4x1 tseries]
%                 y: [4x1 tseries]

% -IRIS Toolbox.
% -Copyright (c) 2007-2012 Jaromir Benes.

% Allow both dbsave(d,fname) and dbsave(fname,d).
if ischar(d) && isstruct(fname)
    [d,fname] = deal(fname,d);
end

if nargin < 3
    dates = Inf;
end

% Parse input arguments.
P = inputParser();
P.addRequired('d',@isstruct);
P.addRequired('fname',@ischar);
P.addRequired('dates',@isnumeric);
P.parse(d,fname,dates);

% Parse options.
opt = passvalopt('data.dbsave',varargin{:});

% Run dates/datdefaults to substitute the default (irisget) date format
% options for 'config'.
opt = datdefaults(opt);

% Remove double quotes from the date format string. This is because the
% double quotes are used to delimit the CSV cells.
[flag,opt.dateformat] = strfun.findremove(opt.dateformat,'"');
if flag
    warning('iris:data', ...
        '\n*** Double quotes removed from date format string.');
end

% Set up the formatting string.
if isempty(opt.decimal)
    format = opt.format;
else
    format = ['%.',sprintf('%g',opt.decimal),'f'];
end

%**************************************************************************

if isequal(dates,Inf)
    dates = dbrange(d);
else
    dates = dates(:)';
end

% Create saving struct.
o = struct();

% Handle userdata first, and remove them from the database so that they are
% not processed as a regular field.
if ~isempty(opt.userdata) && isfield(d,opt.userdata)
    o.userdata = d.(opt.userdata);
    o.userdatafieldname = opt.userdata;
    d = rmfield(d,opt.userdata);
end

list = fieldnames(d)';

% Initialise the data matrix as a N-by-1 vector of NaNs to mimic the dates.
% This first column will fill in all entries.
data = nan(length(dates),1);

namerow = {};
classrow = {};
commentrow = {};
savedindex = false(size(list));

for i = 1 : numel(list)
    
    if istseries(d.(list{i}))
        tmpdata = d.(list{i})(dates);
        tmpcomment = comment(d.(list{i}));
        savedindex(i) = true;
        tmpclass = 'tseries';
    elseif isnumeric(d.(list{i}))
        tmpdata = d.(list{i});
        tmpcomment = {''};
        savedindex(i) = true;
        tmpclass = class(d.(list{i}));
    else
        continue
    end
    
    tmpdata = double(tmpdata);
    tmpsize = size(tmpdata);
    tmpdata = tmpdata(:,:);
    [tmprows,tmpcols] = size(tmpdata);
    if tmpcols == 0
        continue
    elseif tmpcols > 1
        tmpcomment(end+1:tmpcols) = {''};
    end
    
    % Add data, expand first dimension if necessary.
    nrows = size(data,1);
    if nrows < tmprows
        data(end+1:tmprows,:) = NaN;
    elseif size(data,1) > tmpsize(1)
        tmpdata(end+1:nrows,:) = NaN;
    end
    data = [data,tmpdata]; %#ok<*AGROW>
    namerow{end+1} = list{i};
    classrow{end+1} = [tmpclass,xxprintsize(tmpsize)];
    commentrow(end+(1:tmpcols)) = tmpcomment;
    if tmpcols > 1
        namerow(end+(1:tmpcols-1)) = {''};
        classrow(end+(1:tmpcols-1)) = {''};
    end
    
end

% Remove the pretend date column.
data(:,1) = [];

saved = list(savedindex);

o.dates = dat2str(dates(:),opt);
o.data = data;
o.namerow = namerow;
o.nanstring = opt.nan;
o.format = format;
if opt.comment
    o.commentrow = commentrow;
end
if opt.class
    o.classrow = classrow;
end

utils.savecsvdata(o,fname);

end

% Subfunctions.

%**************************************************************************
function c = xxprintsize(s)
% xxprintsize  Print the size of the saved variable in the format
% 1-by-1-by-1 etc.

c = [sprintf('%g',s(1)),sprintf('-by-%g',s(2:end))];
c = ['[',c,']'];

end
% xxprintsize().
