classdef genericobj < handle
    % genericobj  [Not a public class] Generic report object.
    %
    % Backed IRIS class.
    % No help provided.
    
    % -IRIS Toolbox.
    % -Copyright (c) 2007-2012 Jaromir Benes.
    
    properties
        parent = [];
        children = {};
        childof = {};
        caption = '';
        options = struct();
        autodata = {};
        default = {};
        footnotecontainer = {};
    end
    
    properties (Dependent)
        title
        subtitle
    end
    
    methods
        
        function THIS = genericobj(varargin)
            THIS.default = [THIS.default,{ ...
                'captionformat',[],@(x) isempty(x) ...
                || ischar(x) || ...
                (iscell(x) && length(x) == 2 ...
                && (ischar(x{1}) || isequal(x{1},Inf)) ...
                && (ischar(x{2}) || isequal(x{2},Inf))),true, ...
                'captiontypeface',{'\large\bfseries',''}, ...
                @(x) ischar(x) || ...
                (iscell(x) && length(x) == 2 ...
                && (ischar(x{1}) || isequal(x{1},Inf)) ...
                && (ischar(x{2}) || isequal(x{2},Inf))),true, ...
                'footnote','',@ischar,false, ...
                'saveas','',@ischar,false, ...
                }];
            if ~isempty(varargin)
                THIS.caption = varargin{1};
            end
        end
        
        function [this,varargin] = specargin(this,varargin)
        end
        
        function this = setoptions(this,default,parentopt,varargin)
            % The function `setoptions` is called from within `add`.
            try
                % Convert argins to struct.
                names = varargin(1:2:end);
                values = varargin(2:2:end);
                % Make option names lower-case and remove equal signs.
                names = lower(names);
                names = strrep(names,'=','');
                useropt = cell2struct(values,names,2);
            catch Error
                utils.error('report',...
                    ['Invalid structure of optional input arguments.\n', ...
                    'Matlat says: %s'],...
                    Error.message);
            end
            % Copy parent options.
            this.options = parentopt;
            % Add or rewrite whatever user-supplied options, this was
            % children can inherit options set at parents level.
            for i = 1 : length(names)
                this.options.(names{i}) = values{i};
            end
            % Process the object-specific options.
            for i = 1 : 4 : length(default)
                % First, assign defaults.
                this.options.(default{i}) = default{i+1};
                % Then, inherit if it is inheritable and is available from
                % parent.
                if default{i+3} && isfield(parentopt,default{i})
                    this.options.(default{i}) = parentopt.(default{i});
                end
                % Last, get it from user if supplied.
                invalid = {};
                if isfield(useropt,default{i})
                    % Validate user option.
                    if default{i+2}(useropt.(default{i}))
                        this.options.(default{i}) = useropt.(default{i});
                    else
                        invalid{end+1} = default{i}; %#ok<AGROW>
                        invalid{end+1} = func2str(default{i+2}); %#ok<AGROW>
                    end
                    % Report values that do not pass validation.
                    if ~isempty(invalid)
                        utils.error('report',...
                            ['Value assigned to option ''%s'' does not pass ', ...
                            'validation test ''%s''.'],...
                            invalid{:});
                    end
                end
            end
            % Obsolete option names.
            if ~isempty(this.options.captionformat)
                utils.warning('report', ...
                    ['The option ''captionformat'' is obsolete ', ...
                    'and will be removed from future IRIS versions. ', ...
                    'Use ''captiontypeface'' instead.']);
                this.options.captiontypeface = this.options.captionformat;
                this.options.captionformat = [];
            end
        end
        
        function this = add(this,child,varargin)
            % Go down this object and all its descendants and find the
            % youngest among possible parents.
            par = [];
            x = this;
            while true
                if any(strcmpi(shortclass(x),child.childof)) ...
                        && accepts(x)
                    par = x;
                end
                if isempty(x.children)
                    break
                end
                x = x.children{end};
            end
            % `x` is now the last child in the last generation.
            if ~isequal(par,[])
                [child,varargin] = specargin(child,varargin{:});
                child = setoptions(child, ...
                    child.default,par.options,varargin{:});
                child.parent = par;
                par.children{end+1} = child;
                child.autodata = par.autodata;
            else
                label1 = shortclass(child);
                if ~isempty(child.title)
                    label1 = [label1,' ''',child.title,''''];
                end
                label2 = shortclass(x);
                if ~isempty(x.title)
                    label2 = [label2,' ''',x.title,''''];
                end
                utils.error('report',...
                    'This is not the right place to add %s after %s.',...
                    label1,label2);
            end
        end
        
        function new = copy(this)
            thisclass = class(this);
            new = feval(thisclass);
            new.children = cell(size(this.children));
            for i = 1 : length(this.children)
                new.children{i} = copy(this.children{i});
            end
            mc = metaclass(this);
            for i = 1 : length(mc.Properties)
                name = mc.Properties{i}.Name;
                if ~isequal(name,'children') ...
                        && ~mc.Properties{i}.Dependent ...
                        && ~mc.Properties{i}.Constant
                    new.(name) = this.(name);
                end
            end
        end
        
        function [C,TEMPS] = latexcode(THIS)
            [C,TEMPS] = speclatexcode(THIS);
            if ~isempty(THIS.options.saveas)
                [ans,filetitle] = fileparts(THIS.options.saveas); %#ok<NOANS,ASGLU>
                char2file(C,[filetitle,'.tex']);
            end
            % Dump remaining footnotes into parent's container.
            if ~isempty(THIS.footnotecontainer) && ~isempty(THIS.parent)
                THIS.parent.footnotecontainer = [ ...
                    THIS.parent.footnotecontainer, ...
                    THIS.footnotecontainer];
            end
        end
        
        function [C,TEMPS] = speclatexcode(THIS) %#ok<MANU>
            C = '';
            TEMPS = {};
        end
        
        function C = shortclass(THIS)
            C = strrep(class(THIS),'report.','');
        end
        
        function disp(this,level)
            if nargin == 1
                level = 0;
            end
            tab = sprintf('\t');
            fprintf('%s',tab(ones(1,1+level)));
            if level > 0
                fprintf('+');
            end
            %fprintf('<a href="">%s</a>',shortclass(this));
            fprintf('%s',shortclass(this));
            if ~isempty(this.caption)
                if iscell(this.caption)
                    cap = this.caption{1};
                else
                    cap = this.caption;
                end
                fprintf(' ''%s''',cap);
            end
            fprintf('\n');
            for i = 1 : length(this.children)
                disp(this.children{i},level+1);
            end
            if level == 0
                strfun.loosespace();
            end
        end
        
        function display(this)
            strfun.loosespace();
            disp([inputname(1),' =']);
            strfun.loosespace();
            disp(this);
        end
        
        function collect = findall(this,varargin)
            collect = {};
            for i = 1 : length(this.children)
                flag = false;
                for j = 1 : length(varargin)
                    if isa(this.children{i},varargin{j})
                        flag = true;
                        break
                    end
                end
                if flag
                    collect{end+1} = this.children{i}; %#ok<AGROW>
                end
            end
        end
        
        function title = get.title(this)
            if ischar(this.caption)
                title = this.caption;
            elseif iscellstr(this.caption) && ~isempty(this.caption)
                title = this.caption{1};
            else
                title = '';
            end
        end
        
        function subtitle = get.subtitle(this)
            if iscellstr(this.caption) && length(this.caption) > 1
                subtitle = this.caption{2};
            else
                subtitle = '';
            end
        end
        
    end
    
    methods (Access=protected,Hidden)
        
        function c = mytitletypeface(THIS)
            if iscell(THIS.options.captiontypeface)
                c = THIS.options.captiontypeface{1};
            else
                c = THIS.options.captiontypeface;
            end
            if isinf(c)
                c = '\large\bfseries';
            end
        end
        
        function c = mysubtitletypeface(THIS)
            if iscell(THIS.options.captiontypeface)
                c = THIS.options.captiontypeface{2};
            else
                c = '';
            end
            if isinf(c)
                c = '';
            end
        end
        
        % When adding a new object, we find the right place by checking two
        % things: first, we match the child's childof list, and second, we
        % ask the parent if it accepts new childs. The latter test is true
        % for all parents except align objects with no more room.
        function flag = accepts(this) %#ok<MANU>
            flag = true;
        end
        
        function x = adam(this)
            % Return a handle to the top level report object.
            x = this;
            while ~isequal(x.parent,[])
                x = x.parent;
            end
        end
        
        function C = printcaption(THIS,ncol,just,space)
            % PRINTCAPTION  [Not a public function] Typeset title and subtitle in mutlicolumn mode.
            br = sprintf('\n');
            title = THIS.title;
            subtitle = THIS.subtitle;
            if ~exist('space','var')
                space = '';
            else
                space = sprintf('[%gpt]',space);
            end
            if ~exist('just','var')
                just = 'c';
            end
            if ~exist('ncol','var')
                ncol = 1;
            end
            ncol = sprintf('%g',ncol);
            C = '';
            if ~isempty(title)
                c1 = ['\multicolumn{',ncol,'}{',just,'}'];
                C = [C,c1, ...
                    '{',mytitletypeface(THIS), ...
                    ' ',latex.stringsubs(title),footnotemark(THIS),'}'];
                if ~isempty(subtitle)
                    C = [C,'\\',br,c1, ...
                        '{',mysubtitletypeface(THIS), ...
                        ' ',latex.stringsubs(subtitle),'}'];
                end
                C = [C,'\\',space];
            else
                C = [C,'\\[-2.8ex]'];
            end
        end
        
        % TYPEFACE
        %==========
        
        function C = begintypeface(THIS)
            C = '';
            if isfield(THIS.options,'typeface') ...
                    && ~isempty(THIS.options.typeface)
                br = sprintf('\n');
                C = [C,'{',br,THIS.options.typeface,br];
            end
        end
        
        function C = endtypeface(THIS)
            C = '';
            if isfield(THIS.options,'typeface') ...
                    && ~isempty(THIS.options.typeface)
                br = sprintf('\n');
                C = [C,'}',br];
            end
        end
        
        
        % FOOTNOTES AND FOOTNOTE COUNTER
        %================================
        
        function C = footnotemark(THIS)
            if isempty(THIS.options.footnote)
                C = '';
                return
            end
            n = sprintf('[%g]',footnotenumber(THIS));
            C = ['\footnotemark',n];
            THIS.footnotecontainer{end+1} = [ ...
                sprintf('\n'),'\footnotetext',n, ...
                '{',latex.stringsubs(THIS.options.footnote),'}'];
        end
        
        function C = footnotetext(THIS)
            if isempty(THIS.footnotecontainer)
                C = '';
                return
            end
            C = [THIS.footnotecontainer{:}];
            THIS.footnotecontainer = {};
        end
        
        function n = footnotenumber(THIS)
            a = adam(THIS);
            a.footnotecounter = a.footnotecounter + 1;
            n = a.footnotecounter;
        end
        
    end
    
    methods (Static,Hidden)
        
        function c = makebox(text,format,colw,pos,color)
            c = ['{',text,'}'];
            if ~isempty(format)
                c = ['{',format,c,'}'];
            end
            if ~isnan(colw)
                c = ['\makebox[',sprintf('%g',colw),'em]', ...
                    '[',pos,']{',c,'}'];
            end
            if ~isempty(color)
                c = ['\colorbox{',color,'}{',c,'}'];
            end
        end
        
        function c = sprintf(data,options)
            if ~isempty(options.purezero) && data == 0
                c = options.purezero;
                return
            end
            if isnan(data)
                c = options.nan;
                return
            end
            if isinf(data)
                c = options.inf;
                return
            end
            d = sprintf(options.format,data);
            if ~isempty(options.printedzero) ...
                    && isequal(sscanf(d,'%g'),0)
                d = options.printedzero;
            end
            c = ['\ensuremath{',d,'}'];
        end
        
        function C = turnbox(C)
            C = ['\settowidth{\tableColNameHeight}{',C,' }',...
                '\rule{0pt}{\tableColNameHeight}',...
                '\turnbox{90}{',C,'}'];
        end
        
        function valid = validatecolstruct(COLSTRUCT)
            valid = true;
            for i = 1 : length(COLSTRUCT)
                c = COLSTRUCT(i);
                if ~(ischar(c.name) ...
                        || ( ...
                        iscell(c.name) && length(c.name) == 2 ...
                        && (ischar(c.name{1}) || isequalwithequalnans(c.name{1},NaN)) ...
                        && (ischar(c.name{2}) || isequalwithequalnans(c.name{2},NaN)) ...
                        ))
                    valid = false;
                end
                if ~isempty(c.func) ...
                        && isa(c,'function_handle')
                    valid = false;
                end
                if ~isempty(c.date) ...
                        && ~isnumericscalar(c.date)
                    valid = false;
                end
            end
        end
        
    end
    
end