function priorityLevel=MaxPriority(varargin)
% priorityLevel=MaxPriority([windowPtrOrScreenNumber],['WaitBlanking'],['PeekBlanking'],...
% 							['BlankingInterrupt'],['SetClut'],['ClutMovie'],...
% 							['SND'],['sound'],['speak'],...
% 							['GetSecs'],['WaitSecs'],['cputime'],...
% 							['KbCheck'],['KbWait'],['CharAvail'],['GetChar'],...
% 							['EventAvail'],['GetClicks'],['GetMouse'],['GetTicks'])
%
% MaxPriority.m receives a list of one or more function names, in any
% order, and returns the maximum priorityLevel that will allow all the
% named functions to work normally on this computer. Use MaxPriority
% before calling RUSH, to select the highest priorityLevel that's
% compatible with all the functions that you're rushing.
%
% The name matching ignores case.
%
% OS X: ___________________________________________________________________
%
% On OS X all priority levels are safe for all functions. MaxPriority
% always returns 9, the highest priority level.
%
% To preserve compatibility with other platforms we recommend using
% MaxPriority in your script on OS X, instead of the constant 9.
%
% WINDOWS: ________________________________________________________________
%
% Priority levels returned by MaxPriority are 0, 1 and 2.
% Although use of priority levels > 1 is possible and allowed by MaxPriority
% if you don't try to acquire input from keyboard or mouse, it is discouraged
% to use levels > 1 as this can interfere with execution of a lot of
% important system processes and severely reduce the stability of
% Windows execution.
%
% LINUX: __________________________________________________________________
%
% MaxPriority always returns 1, although levels of up to 99 are possible.
% We recommend to sticking to the lowest level, unless some tweaking for a
% specific setup or situation is necessary.
%
% _________________________________________________________________________
%
% See RUSH, Priority, MovieDemo, ScreenTest, SCREEN Preference MaxPriorityForBlankingInterrupt,
% SCREEN Preference SetClutPunchesBlankingClock.

% HISTORY
% 2/1/98 dgp wrote it.
% 2/8/98 dgp Added priorityLevel 0.5.
% 2/28/98 dgp Mention GetSecs feature of PeekBlanking.
% 2/28/98 dgp Reduced priority for WaitBlanking and PeekBlanking from 1 to 0.5
% 3/13/98 dgp Use the new SCREEN preference MaxPriorityForBlankingInterrupt.
% 3/24/98 dgp Use SCREEN preference WaitBlankingAlwaysCallsSetClut.
% 3/24/98 dgp Add 'SetClut'. Enhance 'SND' case to check for 6100.
% 7/25/98 dgp Updated for new SCREEN Preference names.
% 8/1/98 dgp Make smarter analysis of PeekBlanking.
% 3/18/99 dgp Check for VM and RAM Doubler.
% 3/23/99 dgp Cosmetic.
% 2/4/00 dgp Updated GetSecs test to directly test for UpTime trap instead of testing for PCI.
% 3/13/01 dgp Updated to accept struct from SCREEN('Computer'), as suggested by Harriet Allen.
% 2/12/02 dgp Fixed logic for 'WaitBlanking' to allow high priority if SetClutDriverWaitsForBlanking.
%             Eliminated unnecessary variables "analyzePeekBlanking" and "peekBlankingNeedsInterrupt".
% 4/3/02  awi Added section for Windows.  Priorities are based on the comment at the bottom of the
%             priority help file.
% 4/9/02 dgp Give VM warning only once.
% 6/20/02  awi Decrease Win priorities for GetMouse and GetClicks from to 2 to 1.
% 6/21/02 dgp Deleted obsolete ClutMovie option. Use new MaximumSetClutPriority pref.
% 6/22/02 dgp On second thought, DON'T use the new MaximumSetClutPriority pref. That's already
%             taken care of by the C code surrounding the SetClut call, so we don't need
% 			  to worry about it here. The rest of the MATLAB loop can usefully run at
% 			  higher priority.
% 7/29/02 awi fixed comment of 6/20/02.
% 7/17/04 awi Added OS X condition.  Partitioned help platformwise.
% 3/12/05 dgp Changed "strcmp" to "streq".
% 10/10/05 awi Noted changes by dgp on 3/12/05
% 12 /31/05 mk Bugfix for windows part. Never worked when passing in a windowPtrOrScreenNumber
%              instead of keywords. Even if passing in keywords, the priority matching gave
%              wrong results (possibly too high priority) when multiple keywords were supplied.

% NOTES
% 7/17/04 awi
% The OS X section does not do thorough validation of arguments.  It does do:
%  * If the window pointer or screen number argument is present it checks that it is
%  valid.
%  * If a command is included, it checks that it is a valid command
%  for MacPriority.
%  * It checks that at least one argument was passed.
%
% It does not check other requirements, for example some command arguments
% require the screen number or window pointer.
%
% 7/17/04 awi
% If we can figure out which MATLAB routines cause prolonged kernel locks
% then we could have MaxPriority on OS X return 0 for those.  See Mario
% Kleiner's list of Posix functions which cause Kernel locking and ask
% Scott French when MATLAB calls those.  Sigsetjmp is embedded in MATLAB
% run loop and that just has to go.  But other POSIX functions called only
% from some particular MATLAB functions could be avoided by using
% MaxPriority.
%
% Also, since we can uncover the name of a function from within itself, we
% could improve MaxPriority by having it digest the script from which it is
% called, filter out comments, and search for functions which restrict
% priority level.  Thats not going to to be foolproof because quoted text
% is ambiguous, may be rushed or not.  The problem remains that you have to
% remember what in your script you have to pass to MaxPriority.  How about
% MaxPriorityScan. Returns keywords in script which are candidates for
% MaxPriority. Can be used as a programming tool from the command line, or
% embedded in a script and the results passed right into MacPriority.
%

%_______________________________________________________________________________________________________________________________
%The Windows Part

if IsWin
    if nargin<1
        error(['Usage: priorityLevel=MaxPriority([windowPtrOrScreenNumber],[''WaitBlanking''],[''PeekBlanking''],...' ...
            char(13) '                        [''BlankingInterrupt''],[''SetClut''],...'...
            char(13) '                        [''SND''],[''sound''],[''speak''],...'...
            char(13) '                        [''GetSecs''],[''WaitSecs''],[''cputime''],...'...
            char(13) '                        [''KbCheck''],[''KbWait''],[''CharAvail''],[''GetChar''],...'...
            char(13) '                        [''EventAvail''],[''GetClicks''],[''GetMouse''],[''GetTicks''])']);
    end
    
    match=0;
    priorityLevel = 2;
    for i=1:nargin
        if ~ischar(varargin{i}) && ~isnumeric(varargin{i})
            error([ 'argument ' num2str(i) ' is of wrong type']);
        end
        if ischar(varargin{i})
            name=lower(varargin{i});
            %****cases which change the priority****
            if streq(name,lower('CharAvail'))
                priorityLevel=min(priorityLevel,1);
                match=1;
            end
            if streq(name,lower('GetChar'))
                priorityLevel=min(priorityLevel,1);
                match=1;
            end
            if streq(name,lower('GetMouse'))
                priorityLevel=min(priorityLevel,1);
                match=1;
            end
            if streq(name,lower('GetClicks'))
                priorityLevel=min(priorityLevel,1);
                match=1;
            end
            %****cases which are just here to check for valid arguments****
            if streq(name,lower('SND'))
                priorityLevel=min(priorityLevel,0);
                match=1;
            end
            if streq(name,lower('sound'))
                priorityLevel=min(priorityLevel,0);
                match=1;
            end
            if streq(name,lower('speak'))
                priorityLevel=min(priorityLevel,0);
                match=1;
            end
            if streq(name,lower('CopyWindow'))
                match=1;
            end
            if streq(name,lower('WaitBlanking')) || streq(name,lower('WaitVBL'))
                match=1;
            end
            if streq(name,lower('PeekBlanking')) || streq(name,lower('PeekVBL'))
                match=1;
            end
            if streq(name,lower('BlankingInterrupt'))
                match=1;
            end
            if streq(name,lower('SetClut'))
                match=1;
            end
            if streq(name,lower('GetSecs')) || streq(name,lower('WaitSecs'))
                match=1;
            end
            if streq(name,'cputime')
                match=1;
            end
            if streq(name,lower('EventAvail'))
                match=1;
            end
            if streq(name,lower('GetTicks'))
                match=1;
            end
            if streq(name,lower('KbCheck'))
                priorityLevel=min(priorityLevel,1);
                match=1;
            end
            if streq(name,lower('KbWait'))
                priorityLevel=min(priorityLevel,1);
                match=1;
            end
            if streq(name,lower('fopen'))
                match=1;
            end
            if streq(name,lower('fclose'))
                match=1;
            end
            if streq(name,lower('fprintf'))
                match=1;
            end
        end
        if isnumeric(varargin{i})
            % If a generic windowPtrOrScreenNumber argument is supplied, instead of the
            % more specific keywords, we lower priority to 1. This may be a bit
            % strict, but better safe than sorry...
            priorityLevel=min(priorityLevel,1);
            match=1;
        end
        if ~match
            error(['Unknown function ''' varargin{i} '''']);
        end
    end
    %_______________________________________________________________________________________________________________________________
    % OSX
elseif IsOSX
    
    %validate the arguments and return 9.
    if nargin<1
        error(['Usage: priorityLevel=MaxPriority([windowPtrOrScreenNumber],[''WaitBlanking''],[''PeekBlanking''],...' ...
            char(13) '                        [''BlankingInterrupt''],[''SetClut''],...'...
            char(13) '                        [''SND''],[''sound''],[''speak''],...'...
            char(13) '                        [''GetSecs''],[''WaitSecs''],[''cputime''],...'...
            char(13) '                        [''KbCheck''],[''KbWait''],[''CharAvail''],[''GetChar''],...'...
            char(13) '                        [''EventAvail''],[''GetClicks''],[''GetMouse''],[''GetTicks''])']);
    end %if
    %some of these things only exist on OS 9, accepting them as arguments
    %to MacPriority on OS X seems harmless and may be a minor convenience.
    validFunctionArgsOS9=upper({'WaitBlanking','PeekBlanking','BlankingInterrupt','SetClut','SND','sound', 'speak', 'GetSecs', 'WaitSecs', ...
        'cputime','KbCheck', 'KbWait', 'CharAvail','GetChar', 'EventAvail', 'GetClicks', 'GetMouse','GetTicks'});
    validWindowsAndScreens=union(Screen('Windows'), Screen('Screens'));
    for i=1:nargin
        %its a window pointer or screen number
        if isnumeric(varargin{i})
            if ~ismember(varargin{i}, validWindowsAndScreens);
                error('Invalid window pointer or screen number');
            end
            %its a function name
        elseif ischar(varargin{i})
            if ~ismember(upper(varargin{i}), validFunctionArgsOS9)
                error(['Unknown function ''' varargin{i} '''']);
            end
            %its something else
        else
            error([ 'argument ' num2str(i) ' is of wrong type']);
        end %else
    end %for
    priorityLevel=9;
    
    %_______________________________________________________________________________________________________________________________
elseif IsLinux
    if nargin<1
        error(['Usage: priorityLevel=MaxPriority([windowPtrOrScreenNumber],[''WaitBlanking''],[''PeekBlanking''],...' ...
            char(13) '                        [''BlankingInterrupt''],[''SetClut''],...'...
            char(13) '                        [''SND''],[''sound''],[''speak''],...'...
            char(13) '                        [''GetSecs''],[''WaitSecs''],[''cputime''],...'...
            char(13) '                        [''KbCheck''],[''KbWait''],[''CharAvail''],[''GetChar''],...'...
            char(13) '                        [''EventAvail''],[''GetClicks''],[''GetMouse''],[''GetTicks''])']);
    end %if

    % We only differentiante between 0 = Normal priority, 1-99 = Realtime Priority mlocked Realtime.
    % We suggest 1 as a good level. The user is free to take higher numbers of course...
    priorityLevel=1;
else
    error('OS not supported by Psychtoolbox MaxPriority.m');
end