cosmo warning

function varargout=cosmo_warning(message, varargin)
% show a warning message; by default just once for each message
%
% cosmo_warning(message, ...)
% cosmo_warning(state)
% state=cosmo_warning()
%
% Inputs:
%   message      warning message to be shown, or one of:
%                'on'   : show all warning messages
%                'off'  : show no warning messages
%                'once' : show each warning message once [default]
%                'reset':
%   ...          if a warning message is provided according with
%                placeholders as used in sprintf, then the subsequent
%                arguments should contain their values
%   state        if a struct, then this queries or sets the state of
%                cosmo_warning.
%
% Notes:
%   - this function works more or less like matlab's warning function,
%     except that by default each warning is just shown once.
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #


    if isempty(get_from_state('when'))
        set_default_state();
    end

    if nargin==0
        varargout={get_state()};
        return
    end

    if isstruct(message)
        set_state(message);
        return
    end

    lmessage=lower(message);

    switch lmessage
        case {'on','off','once'}
            update_state('when',lmessage);
            return;

        case 'reset'
            set_default_state();
            return;

        otherwise
            show_warning(message,varargin{:});
    end

function show_warning(message,varargin)
    [identifier,full_message]=get_identifier_and_message(...
                                    message,varargin{:});

    shown_warnings=get_from_state('shown_warnings');
    has_warning=cosmo_match({full_message},shown_warnings);
    if ~has_warning
        shown_warnings{end+1}=full_message;
        update_state('shown_warnings',shown_warnings);
    end

    when=get_from_state('when');
    switch when
        case 'once'
            do_show_warning=~has_warning;

            me=mfilename();
            postfix=sprintf(['\n\nThis warning is shown only once, '...
                           'but the underlying issue may occur '...
                           'multiple times. To show each warning:\n'...
                           ' - every time:   %s(''on'')\n'...
                           ' - once:         %s(''once'')\n'...
                           ' - never:        %s(''off'')\n'],me,me,me);
            full_message=[full_message postfix];

        case 'off'
            do_show_warning=false;
        case 'on'
            do_show_warning=true;
        otherwise
            assert(false);
    end

    if do_show_warning
        state=warning(); % store state
        state_resetter=onCleanup(@()warning(state));

        warning('on','all');
        % avoid extra entry on the stack
        has_identifier=~isempty(identifier);

        if has_identifier
            warning(identifier,'%s',full_message);
        else
            warning('%s',full_message);
        end
    end

function [identifier,full_message]=get_identifier_and_message(...
                                            message,varargin)
    args=varargin;
    has_identifier=numel(args)>0 && has_warning_identifier(message);
    if has_identifier
        identifier=message;
        message=args{1};
        args=args(2:end);
    else
        identifier='';
    end

    if numel(args)>0
        full_message=sprintf(message, args{:});
    else
        full_message=message;
    end



function tf=has_warning_identifier(s)
    alpha_num='([a-z_A-Z0-9]+)';
    pat=sprintf('^%s(:%s)?:%s$',alpha_num,alpha_num,alpha_num);
    tf=~isempty(regexp(s,pat,'once'));


function s=get_state()
    s=get_or_set_state();

function set_state(s)
    get_or_set_state(s);

function set_default_state()
    s=struct();
    s.when='once';
    s.shown_warnings=cell(0);
    set_state(s);

function value=get_from_state(key)
    s=get_state();
    value=s.(key);

function update_state(key, value)
    s=get_state();
    s.(key)=value;
    set_state(s);

function varargout=get_or_set_state(s)
    persistent state;
    switch nargin
        case 0
            % get state
            if isempty(state)
                set_default_state();
            end

            varargout={state};

        case 1
            % set state
            state=s;
            varargout={};

        otherwise
            assert(false);
    end