% POP_ADJUSTEVENTS - Adjust event offset of all (or specified) events
%
% Usage:
% >> EEG = pop_adjustevent(EEG); % launch a GUI
% >> EEG = pop_adjustevent(EEG,'key', val);
%
% Inputs:
%   EEG           - Input dataset
%
% Optional Inputs
%   'addms'       - [float] Positive or negative floating point number specifying
%                   the number of millisecond to add to each event latency
%   'addsamples'  - [float] Positive or negative floating point number specifying
%                   the number of samples to add to each event latency
%   'eventtype'   - [cell] Cell array of one or more strings giving the list 
%                   of event type(s) to apply the latency shift to. Default
%                   is all events.
%   'force'       - ['on|'off'] force processing data containing 'boundary'
%                   events. Default is 'off' from the GUI but 'on' from the
%                   command line for backward compatibility.
%
% Outputs:
%   EEG           - Input dataset with latencies shifted
%   com           - Command for EEGLAB history
%
% Authors: Ramon Martinez-Cancino, Arnaud Delorme, Scott Makeig

% Copyright (C) 2017 Ramon Martinez-Cancino, SCCN ramon@sccn.ucsd.edu 
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function [EEG,com] = pop_adjustevents(EEG,varargin)
com = [];

% Call help
if nargin <1 
    help pop_adjustevents;
    return;
end

% GUI if inputs not provided
if nargin < 2
    srate = EEG.srate;
    cback_time   = ['set(findobj(''tag'', ''edit_samples''),    ''string'', num2str(str2num(get(gcbo,''string''))*' num2str(srate) '))'];
    cback_sample = ['set(findobj(''tag'', ''edit_time''),       ''string'', num2str(str2num(get(gcbo,''string''))/' num2str(srate) '))'];
    
    cbevent = ['if ~isfield(EEG.event, ''type'')' ...
        '   errordlg2(''No type field'');' ...
        'else' ...
        '   tmpevent = EEG.event;' ...
        '   if isnumeric(EEG.event(1).type),' ...
        '        [tmps,tmpstr] = pop_chansel(unique([ tmpevent.type ]));' ...
        '   else,' ...
        '        [tmps,tmpstr] = pop_chansel(unique({ tmpevent.type }));' ...
        '   end;' ...
        '   if ~isempty(tmps)' ...
        '       set(findobj(''parent'', gcbf, ''tag'', ''events''), ''string'', tmpstr);' ...
        '   end;' ...
        'end;' ...
        'clear tmps tmpevent tmpv tmpstr tmpfieldnames;' ];
    
    uilist = { ...
        { 'style' 'text' 'string' 'Event type(s) to adjust (all by default): '} ...
        { 'style' 'edit' 'string'  ''   'tag' 'events' } ...
        { 'style' 'pushbutton' 'string' '...' 'callback' cbevent } ...
        { 'style' 'text' 'string' 'Add in milliseconds (can be negative)'} ...
        { 'style' 'edit' 'string'  '' 'tag' 'edit_time'  'callback' cback_time} ...
        { }...
        { 'style' 'text' 'string' 'Or add in samples'} ...
        { 'style' 'edit' 'string'  '' 'tag' 'edit_samples' 'callback' cback_sample}...
        { }...
        { 'style' 'checkbox' 'string' 'Force adjustment even when boundaries are present' 'tag' 'force'}...
        };
        
    uigeom = { [1 0.7 0.5] [1 0.7 0.5] [1 0.7 0.5] 1 };
    result = inputgui('uilist', uilist, 'geometry', uigeom, 'title', 'Adjust event latencies - pop_adjustevents()');
    if isempty(result)
        return;
    end
    if isempty(result{1}) || (isempty(result{2}) && isempty(result{3}))
        disp('Not enough parameters selected')
        return;
    end
    
    % Collecting inputs
    options = { };
    if ~isempty(result{2})
        options = { options{:} 'addms' str2num(result{2}) };
    elseif ~isempty(result{3})
        options = { options{:} 'addsamples' str2num(result{3}) };
    end
    if isequal(result{4}, 1)
        options = { options{:} 'force' 'on' };
    end
    
    % Parse events
    eventTypes = result{1};
    if ~isempty(eventTypes)
        if strcmpi(eventTypes, '{}'), eventTypes = ''; end
        if ~isempty(eventTypes)
            if strcmpi(eventTypes(2),'''')
                g.eventtype = eval( [ '{' eventTypes '}' ] );
            else
                g.eventtype = parsetxt( eventTypes );
            end
        end
        options = { options{:} 'eventtypes' eventTypes };
    end
else
    options = varargin;
end

g = finputcheck(options, { 'addms'       'real'  []    [];
                           'addsamples'  'real'  []    [];
                           'force'       'string' { 'on' 'off' 'auto' }   'auto';
                           'eventtypes'  {'cell' 'string'}  []    {}});
if ischar(g)
    error(g);
end
if strcmpi(g.force, 'auto')
    if nargin < 2
        g.force = 'off';
    else
        g.force = 'on';
    end
end
if ~iscell(g.eventtypes)
    g.eventtypes = { g.eventtypes };
end

% check for boundary events
if strcmpi(g.force, 'off')
    if EEG.trials > 1
        error('Be careful when adjusting latencies for data epochs. Use the ''force'' option to do so.')
    end
    if ~isempty(EEG.event) && isfield(EEG.event, 'type')
        if ischar(EEG.event(1).type)
            if any(strmatch('boundary', {EEG.event.type}))
                error('Be careful when adjusting latencies when boundary events are present. Use the ''force'' option to do so.')
            end
        else
            eeglab_options;
            if option_boundary99 && any( [ EEG.event.type ] == 99 )
                error('Be careful when adjusting latencies when boundary events are present. Use the ''force'' option to do so.')
            end
        end
    end
end

% Finding events indices
indx2shift = [];
if isempty(g.eventtypes)
    indx2shift = 1:length({EEG.event.type});
else
    for i=1:length(g.eventtypes)
        indxtmp = find(ismember({EEG.event.type}, g.eventtypes{i}));
        
        if isempty(indxtmp)
            warning(['Event type ''' g.eventtypes{i} ''' not found.']);
        end
        indx2shift = cat(2,indx2shift,indxtmp);
    end
end

% Checking eventnums and eventrange
if isempty(indx2shift) || any(isnan(indx2shift))
    error(['Unable to proceed. Event type(s) requested not found']);
end

% Shifting events
if ~isempty(g.addms)
    tmpevents = num2cell([EEG.event(indx2shift).latency]+g.addms/1000*EEG.srate);
    [EEG.event(indx2shift).latency] = tmpevents{:};
elseif ~isempty(g.addsamples)
    tmpevents = num2cell([EEG.event(indx2shift).latency]+g.addsamples);
    [EEG.event(indx2shift).latency] = tmpevents{:};
else
    error('To adjust event latencies, you need to specify a number of samples or ms');
end
    
% Checking new event latencies
EEG = eeg_checkset(EEG, 'eventconsistency');

if nargout > 1
    com = sprintf('[EEG,com] = pop_adjustevents(EEG, %s);', vararg2str(options));
end
