function [FF,AA,PDB] = qreport(FNAME,D,RANGE,varargin)
% qreport  [Not a publich function] Quick-report master file.
%
% Backend IRIS function.
% No help provided.

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

[opt,varargin] = passvalopt('qreport.qreport',varargin{:});

% Choose default plot function for calls by `dbplot`.
opt = xxplotfunc(opt);

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

if ~isempty(opt.saveas)
    [~,~,opt.saveasformat] = fileparts(opt.saveas);
end

% Create qreport struct.
Q = xxinp2struct(FNAME,opt);

% Resolve auto subplots.
Q = xxresolveautosubplot(Q);

% Evaluate expressions.
Q = xxevalexpressions(Q,D,opt);

% Replace empty titles with eval strings.
Q = xxemptytitles(Q,opt);

% Create figures and output database (if requested).
opt.outputdata = nargout > 2 ...
    || (~isempty(opt.saveas) || strcmpi(opt.saveasformat,'.csv'));
[FF,AA,PDB,FTIT] = xxrender(Q,RANGE,opt,varargin{:});

% Apply ex-post options.
xxpostmortem(FF,AA,PDB,FTIT,opt);

if opt.pagenumber
    xxpagenumber(FF);
end

if ~isempty(opt.saveas)
    xxsaveas(FF,PDB,opt);
end

end

% Subfunctions.

%**************************************************************************
function Q = xxinp2struct(INP,OPT)

if isa(INP,'function_handle')
    % Allow function handles.
    INP = char(INP);
end

if ischar(INP)
    % Pre-parser the q-file.
    p = preparser(INP,'removecomments=',{{'%{','%}'},'(?<!\\)%'});
    
    % Put labels back into the code.
    c = labelsback(p);
    
    % Replace escaped % signs.
    c = strrep(c,'\%','%');
    
    % Replace single quotes with double quotes.
    c = strrep(c,'''','"');
else
    c = INP;
end

Q = {};
first = true;
while ~isempty(c)
    [c,q] = xxgetnext(c,OPT);
    if strcmp(q.tag,'#')
        OPT.subplot = getsubplot(q.title);
        continue
    end
    % Add a new figure if there's none at the beginning of the qreport.
    if first && ~strcmp(q.tag,'!++')
        q0 = struct();
        q0.tag = '!++';
        q0.title = '';
        q0.subplot = OPT.subplot;
        q0.children = {};
        Q{end+1} = q0; %#ok<AGROW>
    end
    if strcmp(q.tag,'!++')
        Q{end+1} = q; %#ok<AGROW>
    else
        Q{end}.children{end+1} = q;
    end
    first = false;
end

    function x = getsubplot(c)
        % getsubplot  Convert subplot string to vector or 'auto'.
        x = sscanf(c,'%gx%g');
        if isnumeric(x) && length(x) == 2 ...
                && all(~isnan(x) & x > 0 & x == round(x))
            x = x(:).';
        else
            x = 'auto';
        end
    end

end
% xxinp2struct().

%**************************************************************************
function [INP,S] = xxgetnext(INP,OPT)

S = struct();
S.tag = '';
S.title = '';

if isempty(INP)
    return
end

if ischar(INP)
    % Replace old syntax !** with !..
    INP = strrep(INP,'!**','!..');
    % Q-file code from `qplot`.
    tags = '#|!\+\+|!\-\-|!::|!ii|!II|!\.\.|!\^\^';
    [tok,e] = regexp(INP,['(',tags,')(\^?)(.*?)(?=',tags,'|$)'], ...
        'tokens','end','once');
    if ~isempty(tok)
        S.tag = tok{1};
        dotransform = ~strncmp(tok{2},'^',1);
        tok = regexp(tok{3},'([^\n]*)(.*)','once','tokens');
        S.title = tok{1};
        body = tok{2};
        INP = INP(e+1:end);
    end
elseif iscellstr(INP)
    % Cellstr from `dbplot`.
    c = strtrim(INP{1});
    INP = INP(2:end);
    if ~isempty(c)
        S.tag = OPT.plotfunc;
        dotransform = ~strncmp(c,'^',1);
        if ~dotransform
            c = c(2:end);
        end
        [body,S.title] = strfun.labexpr(c);
    else
        S.tag = '!..';
        S.title = '';
        S.eval = {};
        S.legend = {};
        S.tansform = [];
        return
    end
else
    return
end

% Title.
S.title = strtrim(S.title);

if strcmp(S.tag,'#')
    return
end

if strcmp(S.tag,'!++')
    S.subplot = OPT.subplot;
    S.children = {};
    return
end

% Expressions and legends.
[S.eval,S.legend] = xxreadbody(body);

S.transform = [];
if dotransform && ~strcmp(S.tag,'!++')
    S.transform = OPT.transform;
end

end
% xxgetnext().

%**************************************************************************
function [eval,leg] = xxreadbody(c)
c = strtrim(c);
c = strfun.strrepoutside(c,',',sprintf('\n'),'()','[]','{}');
c = strfun.strrepoutside(c,' & ',sprintf('\n'),'()','[]','{}');
lines = regexp(c,'[^\n]*','match');
[eval,leg] = strfun.labexpr(lines);
end
% xxreadbody().

%**************************************************************************
function Q = xxresolveautosubplot(Q)
nfig = length(Q);
for i = 1 : nfig
    if strcmp(Q{i}.subplot,'auto')
        Q{i}.subplot = utils.autosubplot(length(Q{i}.children));
    end
end
end
% xxresolveautosubplot().

%**************************************************************************
function Q = xxevalexpressions(Q,D,OPT)

doround = ~isinf(OPT.round) && ~isnan(OPT.round);
for i = 1 : length(Q)
    for j = 1 : length(Q{i}.children)
        ch = Q{i}.children{j};
        if strcmp(ch.tag,'!..')
            continue
        end
        neval = length(ch.eval);
        series = cell(1,neval);
        [series{:}] = dbeval(D,OPT.sstate,ch.eval{:});
        if isa(ch.transform,'function_handle')
            for k = 1 : length(series)
                series{k} = ch.transform(series{k});
            end
        end
        if doround
            series = myround(series);
        end
        Q{i}.children{j}.series = series;
    end
end

    function x = myround(x)
        for ii = 1 : length(x)
            if isa(x{ii},'tseries')
                x{ii} = round(x{ii},OPT.round);
            elseif isnumeric(x{ii})
                factor = 10^OPT.round;
                x{ii} = round(x{ii}*factor)/factor;
            end
        end
    end

end
% xxevalexpressions().

%**************************************************************************
function Q = xxemptytitles(Q,OPT)

for i = 1 : length(Q)
    for j = 1 : length(Q{i}.children)
        ch = Q{i}.children{j};
        if strcmp(ch.tag,'!..')
            continue
        end
        if isempty(ch.title)
            k = i*j;
            if iscellstr(OPT.title) ...
                    && length(OPT.title) >= k && ~isempty(OPT.title{k})
                ch.title = OPT.title{k};
            elseif isa(OPT.title,'function_handle')
                ch.title = OPT.title;
            else
                ch.title = [sprintf('%s & ',ch.eval{1:end-1}),ch.eval{end}];
                if isa(ch.transform,'function_handle')
                    c = char(ch.transform);
                    c = regexprep(c,'^@\(.*?\)','','once');
                    ch.title = [ch.title,', ',c];
                end
            end
        end
        Q{i}.children{j} = ch;
    end
end

end
% xxemptytitles().

%**************************************************************************
function [FF,AA,PLOTDB,FTIT] = xxrender(Q,RANGE,OPT,varargin)

FF = [];
AA = {};
PLOTDB = struct();

count = 1;
nrow = NaN;
ncol = NaN;
pos = NaN;
FTIT = {};
for i = 1 : length(Q)
    % New figure.
    donewfigure();
    
    nchild = length(Q{i}.children);
    for j = 1 : nchild
        tag = Q{i}.children{j}.tag;
        % If `'overflow='` is true we automatically open a new figure when the
        % subplot count overflows; this is the default behaviour for `dbplot`.
        % Otherwise, an error occurs; this is the default behaviour for `qplot`.
        if pos > nrow*ncol && OPT.overflow
            % Open a new figure and reset the subplot position `pos`.
            donewfigure();
        end
        switch tag
            case {'!..'}
                % Blank space, do not count.
                pos = pos + 1;
            otherwise
                % New panel/subplot.
                donewpanel();
                
                x = Q{i}.children{j}.series;
                leg = Q{i}.children{j}.legend;
                
                % Get title. It can be either a string or a function handle that will be
                % applied to the plotted tseries object.
                tit = xxgettitle(Q{i}.children{j}.title,x);
                
                finalleg = docreatelegend();
                % Create an entry for the current panel in the output database. Do not
                % if plotting the panel fails.
                addtooutput = true;
                try
                    [range,data] = ...
                        xxplot(tag,aa,RANGE,x,finalleg,OPT,varargin{:});
                catch me
                    addtooutput = true;
                    utils.warning('qplot/dbplot',...
                        'Error plotting ''%s''.\nMatlab says: %s',...
                        Q{i}.children{j}.title,me.message);
                end
                if ~isempty(tit)
                    grfun.title(tit,'interpreter',OPT.interpreter);
                end
                % Create a name for the entry in the output database based
                % on the (user-supplied) prefix and the current panel's
                % name. Substitute '_' for any [^\w]. If not a valid Matlab
                % name, replace with "Panel#".
                if OPT.outputdata && addtooutput
                    tmpname = [sprintf(OPT.prefix,count), ...
                        regexprep(tit,'[^\w]+','_')];
                    if ~isvarname(tmpname)
                        tmpname = sprintf('Panel%g',count);
                    end
                    try
                        PLOTDB.(tmpname) = tseries(range,data,finalleg);
                    catch %#ok<CTCH>
                        PLOTDB.(tmpname) = NaN;
                    end
                end
                if ~isempty(OPT.xlabel)
                    xlabel(OPT.xlabel);
                end
                if ~isempty(OPT.ylabel)
                    ylabel(OPT.ylabel);
                end                
                count = count + 1;
                pos = pos + 1;
        end
    end
end

    function finalleg = docreatelegend()
        % Splice legend and marks.
        finalleg = {};
        for ii = 1 : length(x)
            for jj = 1 : size(x{ii},2)
                c = '';
                if ii <= length(leg)
                    c = [c,leg{ii}]; %#ok<AGROW>
                end
                if jj <= length(OPT.mark)
                    c = [c,OPT.mark{jj}]; %#ok<AGROW>
                end
                finalleg{end+1} = c; %#ok<AGROW>
            end
        end
    end

    function donewfigure()
        ff = figure('selectionType','open');
        FF = [FF,ff];
        orient('landscape');
        AA{end+1} = [];
        nrow = Q{i}.subplot(1);
        ncol = Q{i}.subplot(2);
        pos = 1;
        FTIT{end+1} = Q{i}.title;
    end

    function donewpanel()
        aa = subplot(nrow,ncol,pos);
        AA{i} = [AA{i},aa];
        set(aa,'activePositionProperty','position');
    end

end
% xxrender().

%**************************************************************************
function [RANGE,DATA] = xxplot(TAG,AA,RANGE,X,LEG,OPT,varargin)

isxgrid = OPT.grid;
isygrid = OPT.grid;

switch TAG
    case '!--' % Line graph.
        DATA = [X{:}];
        if istseries(DATA)
            [h,RANGE,DATA] = plot(AA,RANGE,DATA,varargin{:}); %#ok<*ASGLU>
        else
            plot(RANGE,DATA,varargin{:});
        end
    case '!::' % Bar graph.
        DATA = [X{:}];
        if istseries(DATA)
            [h,RANGE,DATA] = bar(RANGE,[X{:}],varargin{:});
        else
            bar(RANGE,DATA,varargin{:});
        end
    case '!ii' % Stem graph
        DATA = [X{:}];
        if istseries(DATA)
            [h,RANGE,DATA] = stem(RANGE,[X{:}],varargin{:});
        else
            stem(RANGE,DATA,varargin{:});
        end
    case '!II' % Error bar graph.
        [h1,h2,RANGE,DATA] = errorbar(RANGE,X{:},varargin{:});
    case '!>>' % Prediction plot.
        [h1,h2,RANGE,DATA] = plotpred(RANGE,X{:},varargin{:});
    case '!^^' % Histogram.
        DATA = [X{:}];
        DATA = DATA(RANGE,:);
        [count,pos] = hist(DATA);
        h = bar(pos,count,'barWidth',0.8); %#ok<NASGU>
        isxgrid = false;
    case '!??' % Plotcmp.
        [AA,ll,rr,RANGE,DATA] = plotcmp(RANGE,[X{:}],varargin{:});
end

if OPT.tight
    grfun.yaxistight(AA);
end

if isxgrid
    set(AA,'xgrid','on');
end

if isygrid
    set(AA,'ygrid','on');
end

if OPT.addclick
    grfun.clicktocopy(AA);
end

% Display legend if there is at least one non-empty entry.
if any(~cellfun(@isempty,LEG))
    legend(LEG{:},'Location','Best');
end

if OPT.zeroline
    grfun.zeroline(AA);
end

if ~isempty(OPT.highlight)
    grfun.highlight(AA,OPT.highlight);
end

if ~isempty(OPT.vline)
    grfun.vline(AA,OPT.vline);
end

end
% xxplot().

%**************************************************************************
function xxpostmortem(FF,AA,PLOTDB,FTIT,OPT)

if ~isempty(OPT.style)
    qstyle(OPT.style,FF);
end

if OPT.addclick
    grfun.clicktocopy([AA{:}]);
end

if ~isempty(OPT.clear)
    h = [AA{:}];
    h = h(OPT.clear);
    for ih = h(:).'
        cla(ih);
        set(ih, ...
            'xTickLabel','','xTickLabelMode','manual', ...
            'yTickLabel','','yTickLabelMode','manual', ...
            'xgrid','off','ygrid','off');
        delete(get(ih,'title'));
    end
end

for i = 1 : length(FTIT)
    % Figure titles must be created last because the `subplot` commands clear
    % figures.
    if ~isempty(FTIT{i})
        grfun.ftitle(FF(i),FTIT{i});
    end
end

if OPT.drawnow
    drawnow();
end

end
% xxpostmortem().

%**************************************************************************
function xxpagenumber(FF)

npage = length(FF);
count = 0;
for f = FF(:).'
    figure(f);
    count = count + 1;
    grfun.ftitle({'','',sprintf('%g/%g',count,npage)});
end

end
% xxpagenumber().

%**************************************************************************
function xxsaveas(FF,PLOTDB,OPT)

if strcmpi(OPT.saveasformat,'.csv')
    dbsave(PLOTDB,OPT.saveas,Inf,OPT.dbsave{:});
    return
end

if any(strcmpi(OPT.saveasformat,{'.pdf'}))
    [fpath,ftit] = fileparts(OPT.saveas);
    psfile = fullfile([ftit,'.ps']);
    if exist(psfile,'file')
        delete(psfile);
    end
    for f = FF(:).'
       figure(f);
       orient('landscape');
       print('-dpsc','-append',psfile);
    end
    latex.ps2pdf(psfile);
    delete(psfile);
end 

end
% xxsaveas().

%**************************************************************************
function opt = xxplotfunc(opt)
% xxplotfunc  Convert the `'plotFunc='` option in `dbplot` to the corresponding tag.

switch char(opt.plotfunc)
    case 'plot'
        opt.plotfunc = '!--';
    case 'bar'
        opt.plotfunc = '!::';
    case 'errorbar'
        opt.plotfunc = '!II';
    case 'stem'
        opt.plotfunc = '!ii';
    case 'hist'
        opt.plotfunc = '!^^';
    case 'plotpred'
        opt.plotfunc = '!>>';
    case 'plotcmp'
        opt.plotfunc = '!??';
    otherwise
    % Error bar graphs are not available in `dbplot`.
        opt.plotfunc = '!--';
end

end
% xxplotfunc().

%**************************************************************************
function TIT = xxgettitle(TITLEOPT,X)
% xxgettitle  Title is either a user-supplied string or a function handle
% that will be applied to the plotted tseries object.
invalid = '???';
if isa(TITLEOPT,'function_handle')
    try
        TIT = TITLEOPT([X{:}]);
        if iscellstr(TIT)
            TIT = sprintf('%s,',TIT{:});
            TIT(end) = '';
        end
        if ~ischar(TIT)
            TIT = invalid;
        end
    catch %#ok<CTCH>
        TIT = invalid;
    end
elseif ischar(TITLEOPT)
    TIT = TITLEOPT;
else
    TIT = invalid;
end
end
% xxgettitle().