function [Resp, RT] = collectResponse(varargin)
% function [Resp, RT] = collectResponse(varargin)
%
% Author: Cendri Hutcherson
% Last modified: 05-27-18
%
% DESCRIPTION: Waits a specified amount of time for subject to respond,
% records response and RT. Note: the RT that is returned is technically the
% computer timestamp for when the response occurred, not the relative RT.
% Thus, outside this function, to properly calculate the RT, you will want
% to collect a timestamp for when a display began, then compare the RT to
% that timestamp to get the duration of the response.
%
% USAGE: collectResponse([waitTime],[moveOn],[allowedKeys],[KbDevice],[PTBParams])
%
% EXPLANATION: defaults to infinite waitTime, moving on as soon as any
% button is pressed [i.e. moveOn = 1], with any key press triggering the
% move.  If you wish the program to wait out the remainder of the waitTime
% even if a key has been pressed, use moveOn = 0.  If you wish the program
% to accept as input only certain keys, enter the allowed keys as a cell
% (e.g. {'1' '2' '3' '4' '5' or {'RightKey','LeftKey'}. KbDevice specifies
% which keyboard to use (default = any keyboard [-1]).
%
% If you have counterbalancing of key order (e.g., some subjects or trials
% response right-left, others left-right, you have the option to recode the
% raw response into its interpreted meaning using the KeyOrder field in the
% PTBParams structure. For example, PTBParams.KeyOrder = {4 3 2 1} would
% remap 1 to 4, 2 to 3, 3 to 2, and 4 to 1.
%
% The function can also be run in test mode, which allows you to run
% through an experiment quickly without having to sit there entering
% responses yourself. If this behavior is desired, the PTBParams structure
% supplied to the function should have a field called "testMode" which
% should be set to true

% \ Set defaults  \
% i.e., wait forever, move on as soon as an acceptable key has been
% pressed, with any key allowed
ListenTime = Inf;
moveOn = 1;
allowedKeys = [];
KbDevice = -1;
testMode = false;

if length(varargin) >= 1
    ListenTime = varargin{1};
end

if isempty(ListenTime); ListenTime = Inf; end

if length(varargin) >= 2
    moveOn = varargin{2};
end

if isempty(moveOn); moveOn = 1; end

if length(varargin) >= 3
    allowedKeys = varargin{3};
end

if length(varargin) >= 4
    KbDevice = varargin{4};
end

if length(varargin) >= 5
    PTBParams = varargin{5};
    if isfield(PTBParams,'testMode')
        testMode = PTBParams.testMode;
    end
end

if ListenTime == Inf && moveOn ~= 1
    error(['Infinite loop: You asked me to wait forever, even AFTER the' ...
        'subject has responded!'])
end

% \ Main body \
% The first part of this tests to make sure that the function has not been
% called in test mode. If it has not, it will first check to make sure no
% key
if ~testMode
    % Start the clock, with null responses as the default
    StartWaiting = GetSecs();
    Resp = 'NULL';
    RT = NaN;

    % Prevent keys that were already pressed before the function started
    % from triggering a response. This means that the person has to
    % actually press a NEW key to trigger the end of the cycle.
    while KbCheck(KbDevice)
    end

    % Check to see whether any key will work, or if we are waiting for a
    % specific key to be pressed.
    if isempty(allowedKeys)
        % Initiate a loop to listen for any key press, for as long as is
        % specified by the variable WaitTime (can be infinite). Once a
        % key has been pressed, log the key and the RT, and finish.
        while (GetSecs() - StartWaiting) < ListenTime
            [keyDown, secs, key] = KbCheck(KbDevice);
            if keyDown == 1
                Resp = KbName(find(key==1));
                RT = secs;
                break;
            end
        end
    else
        % Initiate a loop to listen for a specific key press, for as long as is
        % specified by the variable WaitTime (can be infinite). Once a
        % key has been pressed, log the key and the RT, and finish.
        while (GetSecs() - StartWaiting) < ListenTime
            [keyDown, secs, key] = KbCheck(KbDevice);
            GetMouse(); % needed to keep focus on PTB screen
            if keyDown == 1
                if ~iscell(KbName(find(key==1)))
                    Resp = KbName(find(key==1));

                    if any(strcmp(allowedKeys,Resp))
                        RT = secs;
                        break;
                    else
                        Resp = 'NULL';
                    end
                end
            end
        end
    end

    % If no action should be taken immediately after the key press
    % occurred (i.e., moveOn is false), then wait out the remainder of the
    % specified wait time before exiting the function
    if moveOn ~= 1
        while (GetSecs() - StartWaiting) < ListenTime
        end
    end
else
    % In test mode, the following code will generate random responses,
    % restricted tothose responses that are possible (or plausible in the
    % case of any allowed key). The function will also generate
    % approximately 5% non-responses to mimic behavior of a
    % semi-inattentive subject. Allows for faster, easier testing.

    % select a random response
    if isempty(allowedKeys)
        allowedKeys = KbName('KeyNames');
        allowedKeys = allowedKeys(4:40); % sensible keys
    end

    randomKey = allowedKeys{randi(length(allowedKeys),1)};
    Resp = randomKey;

    % select a random RT
    if isinf(ListenTime)
        ListenTime = 10;
        RT = ListenTime*rand(1);
    else
        RT = 1.05*ListenTime*rand(1); % generates a small number of missed responses
        if RT > ListenTime
            Resp = 'NULL';
            RT = NaN;
        end
    end
    RT = GetSecs + RT;
    WaitSecs(.05);
end