% setevent() - Load/remove events in a dataset. If the EEG dataset
%              and graphic interface type are the only inputs, a window 
%              pops up to ask for the relevant parameter values.
%
% Usage: >> [EEG,eventnumbers] = setevent( EEG, interf, 'key1', 'value1', ...);
%
% Input:
%   EEG      - input dataset
%   interf   - [0|1], call different type of graphic interface. 0=full
%              file reading. 1=variable name editing. This value is 
%              ignored if more than 3 arguments are given to the function
%              and the graphic interface is not needed.
%
% Optional input
%  'append'   - ['yes'|'no'] 'yes' = Append to the current events of the
%               EEG dataset {default}: 'no' = Erase the previous events.
%  'event'    - [ 'filename'|array ] filename of a text file or
%               Matlab array of the global workspace containing an
%               array of events in the folowing format: The first column
%               is the type of the event, the second the latency and
%               the other ones are user defined fields.
%  'fields'   - { nested cell array } name for each user-defined column, 
%               optionally followed by a description. Ex: { 'type', 'latency' }
%  'latency'  - [ 'filename'|array|number ], 1-column file or
%               array containing the latency of the events. If the arg
%               is a single number, it will be used for all events.
%  'type'     - [ 'filename'|array|number ], Type (numbers) of the events.
%               Same format as 'latency' {Default event type is 1}.
%  'USER_VAR' - [ 'filename'|array|number|[] ], here 'USER_VAR'
%               is the name of a user-defined field. If the argument
%               is [], the field is removed from all the events.
%  'skipline' - number of rows to skip for text files 
%  'rename'   - ['USER_VAR1->USER_VAR2'] rename field 'USER_VAR1' into
%               'USER_VAR2'. Ex: { 'rename', 'type->condition' }.    
%  'indices'  - vector indicating the indices of the events to modify 
%  'timeunit' - [ latency unit in second ]. Default unit is 1 second. 
%  'delim'    - delimiting characters in file. Default is tab and space.
%  'align'    - [num] align the first event latency to existing 
%               event latency (number num) and check latency
%               consistency. Negative values can also be used and then
%               event number -num is aligned with the first pre-existing
%               event. Default is 0, no latency alignment.
%  'latencyinfo'  - comment string for the latency field
%  'typeyinfo'    - comment string for the type field
%  'USER_VARinfo' - comment string for the variable USERVAR
%
% Outputs:
%   EEG          - dataset with updated event field
%   eventnumbers - indexes of the appended events
%
% Example: [EEG, eventnumbers] = setevent(EEG, 'type', 1, ...
%                         'sleepstage', 'sleepstage_values.txt', ...
%                         'latency', [0.100 0.130 0.123 0.400] );
%
%         This appends 4 events to the EEG struct, all of type 1,
%         with user-defined field ('sleepstage') values read from an
%         ascii 1-column file ('sleepstage_values.txt').
%
% Notes: As indicated above, to remove a user=defined field, simply
%           enter an empty array [].
%            Ex: >> EEG = setevent(EEG,'sleepstage',[]);
%        To save the events first convert them in 'array' format and
%        then save the array.
%            >> tmp = eventformat(EEG.event, 'array');
%            >> save -ascii filename.txt tmp 
%
% Author: Arnaud Delorme & Scott Makeig, CNL / Salk Institute, 9 Feb 2002
%
% See also: eventformat(), selectevent()

%123456789012345678901234567890123456789012345678901234567890123456789012

% Copyright (C) Arnaud Delorme, CNL / Salk Institute, 9 Feb 2002, arno@salk.edu
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

%02/13/2001 fix bug if EEG.event is empty -ad
%03/12/2001 add timeunit option -ad
%03/18/2001 debug rename option -ad & sm

function [EEG, com] = setevent(EEG, interf, varargin);

com ='';
if nargin < 2
   help setevent;
   return;
end;	

if isempty(EEG.data)
    disp('Setevent error: can not process empty dataset'); return;
end;    

I = [];

% remove the event field
% ----------------------
if ~isempty(EEG.event)
    allfields = fieldnames(EEG.event);
    if isfield(EEG, 'tmpevent') & strmatch('event', allfields)
        indexmatch = strmatch('event', allfields);
	   allfields = { allfields{1:indexmatch-1} allfields{indexmatch+1:end}};
    end;    
else
    allfields = {};
end;
    
if nargin<3
    commandload = [ '[filename, filepath] = uigetfile(''*'', ''Select a text file'');' ...
                    'if filename ~=0,' ...
                    '   set(findobj(''parent'', gcbf, ''tag'', tagtest), ''string'', [ filepath filename ]);' ...
                    'end;' ...
                    'clear filename filepath tagtest;' ];
    uilist = { ...
         { 'Style', 'text', 'string', 'Event indices', 'fontweight', 'bold' }, ...
         { 'Style', 'text', 'string', 'Append events?', 'fontweight', 'bold' } };
    if interf == 0
        geometry    = { [ 1 1.1 1.1 1] [ 1 1 2] [1 1 2] [ 2 1 1] };
        uilist = { uilist{:}, ...     
         { 'Style', 'text', 'string', 'Event file or array', 'horizontalalignment', 'right', 'fontweight', 'bold' }, ...
         { 'Style', 'pushbutton', 'string', 'Browse', 'callback', [ 'tagtest = ''globfile'';' commandload ] }, ...
         { 'Style', 'edit' } ...
         { 'Style', 'checkbox', 'string', 'Yes/No', 'value', 0 }, ...
         { 'Style', 'edit', 'string', '', 'horizontalalignment', 'left', 'tag',  'globfile' }, ...
         { }, { 'Style', 'text', 'string', 'NB: No = overwrite', 'value', 0 }, { }, ...
         { 'Style', 'text', 'string', 'Input field (column) names       ', 'fontweight', 'bold' } ...
         { 'Style', 'edit', 'string', '' } { 'Style', 'text', 'string', 'Ex: type latency' } };
         geometry = { geometry{:} [2 1 1] [2 1 1] [2 1 1] };
         uilist   = { uilist{:}, ...
                { 'Style', 'text', 'string', 'Number of file header lines', 'horizontalalignment', 'left' }, { 'Style', 'edit', 'string', '0' }, { },...
                { 'Style', 'text', 'string', 'Latency time unit (sec)', 'horizontalalignment', 'left' }, { 'Style', 'edit', 'string', '1' } { 'Style', 'text', 'string', 'Ex: If ms, 1E-3' },...
                { 'Style', 'text', 'string', 'Align event latencies to data events', 'horizontalalignment', 'left' }, { 'Style', 'edit', 'string', int2str(~isempty(EEG.event)) } { 'Style', 'text', 'string', 'See help' },...
               };
        results = inputgui( geometry, uilist, 'formaththelp(''setevent'');', 'Load or append event file' );
        if length(results) == 0, return; end;

	    % decode top inputs
	    % -----------------
	    args = {};
	    if ~isempty( results{1} ), args = { args{:}, 'indices', eval( [ '[' results{1} ']' ]) }; end;
	    if results{2} == 0       , args = { args{:}, 'append', '''no''' }; end;
	    if ~isempty( results{3} ) 
	        if exist(results{3}) == 2,  args = { args{:}, 'event', [ '''' results{3} '''' ] }; 
	        else                        args = { args{:}, 'event', results{3} }; end; 
	    end;
	    if ~isempty( results{4} )  % FIELDS
	        args = { args{:}, 'fields', { parsetxt(results{4}) } };
	    end;
	    % handle skipline 
	    % ---------------     
	    if ~isempty(eval(results{end-2})), if eval(results{end-2}) ~= 0,  args = { args{:}, 'skipline', eval(results{end-2}) }; end; end;

	    % handle timeunit 
	    % -------------     
	    if ~isempty(eval(results{end-1})), if eval(results{end-1}) ~= 0,  args = { args{:}, 'timeunit', eval(results{end-1}) }; end; end;

	    % handle alignment 
	    % ----------------     
	    if ~isempty(eval(results{end})), if eval(results{end}) ~= 0,  args = { args{:}, 'align', eval(results{end}) }; end; end;
    else
        geometry    = { [ 1 1.1 1.1 1 1] [ 1 1 2.7 0.3] };
        uilist = { uilist{:} {} {} {} ...
         { 'Style', 'edit' } ...
         { 'Style', 'checkbox', 'string', 'Yes/No', 'value', 1 }, ...
         { 'Style', 'text', 'string', 'NB: No = overwrite current epoch info' }, { }, { }, ...   
         { 'Style', 'text', 'string', 'Edit fields:', 'fontweight', 'bold'  }, ...
         { 'Style', 'text', 'string', 'Values file|array', 'fontweight', 'bold'  }, ...
         {   }, ...
         { 'Style', 'text', 'string', 'Field description', 'fontweight', 'bold'  }, ...
         { 'Style', 'text', 'string', 'Delete field', 'fontweight', 'bold'  } ...
         };
        geometry = { geometry{:} [1] [1 1.1 0.8 1.2 1] };

	    listboxtext = 'No field selected';  
	    for index = 1:length(allfields) 
	        geometry = { geometry{:} [1 1 1 1 0.45 0.35 0.45] };
	        description = '';
	        try, description = fastif(isempty(EEG.eventdescription{index}), '', EEG.eventdescription{index});
	        catch, end;
	        uilist   = { uilist{:}, ...
	         { 'Style', 'text', 'string', allfields{index} }, ...
	         { 'Style', 'edit', 'string', '', 'horizontalalignment', 'left', 'tag',  allfields{index} }, ...
	         { 'Style', 'pushbutton', 'string', 'Browse', 'callback', ['tagtest = ''' allfields{index} ''';' commandload ] }, ...
	         { 'Style', 'edit', 'string', description }, ...
	         { }, { 'Style', 'checkbox', 'string', '    ' },{ } };
	         listboxtext = [ listboxtext '|' allfields{index} ]; 
	    end;
	    geometry = { geometry{:} [1 1 1 1 0.45 0.35 0.45] [1] [1 2 0.6 1.3 1.5] };
	    uilist   = { uilist{:}, ...
	         { 'Style', 'edit', 'string', '' }, ...
	         { 'Style', 'edit', 'string', '', 'horizontalalignment', 'left', 'tag',  'newfield' }, ...
	         { 'Style', 'pushbutton', 'string', 'Browse', 'callback', ['tagtest = ''newfield'';' commandload ] }, ...
	         { 'Style', 'edit', 'string', '' }, ...
	         { }, { },{ } };
         uilist   = { uilist{:}, ...
                { } ...
                { 'Style', 'text', 'string', 'Rename', 'fontweight', 'bold' }, ...
                { 'Style', 'listbox', 'string', listboxtext }, ...
                { 'Style', 'text', 'string', 'as', 'fontweight', 'bold' }, ...
                { 'Style', 'edit', 'string', '' } ...
                fastif(isunix,{ 'Style', 'text', 'string', 'NB: click on field!' },{ })};

        results = inputgui( geometry, uilist, 'formaththelp(''setevent'');', 'Edit event fields' );
        if length(results) == 0, return; end;

	    % decode top inputs
	    % -----------------
	    args = {};
	    if ~isempty( results{1} ), args = { args{:}, 'indices', eval( [ '[' results{1} ']' ]) }; end;
	    if results{2} == 0       , args = { args{:}, 'append', '''no''' }; end;
	    
	    % dealing with existing fields
	    %-----------------------------
	    for index = 1:length(allfields) 
	        if results{2+index*3} == 1, args = { args{:}, allfields{index}, [] };
	        else if ~isempty( results{index*3} )
	                if exist(results{index*3}) == 2,  args = { args{:}, allfields{index}, [ '''' results{index*3} '''' ] }; % file
	                else                              args = { args{:}, allfields{index}, results{index*3} }; end;
	             end;
	        end;     
	        if ~isempty( results{1+index*3} ), args = { args{:}, [ allfields{index} 'info' ], results{1+index*3} }; end;
	    end;
	    
	    % dealing with the new field
	    %---------------------------
	    sub = 2;
	    if ~isempty( results{end-sub-2} )
	        if ~isempty( results{end-sub-1} )
	            if exist(results{end-sub-1}) == 2,  args = { args{:}, results{end-sub-2}, [ '''' results{end-sub-1} '''' ] }; % file
	            else                            args = { args{:}, results{end-sub-2}, results{end-sub-1} }; end;
	            if ~isempty( results{end-sub} ),  args = { args{:}, [ results{end-sub-2} 'info' ], results{end-sub} }; end;
	        else
	            disp(['The new field' results{end-sub-2} ' was ignored since no input data were given for it.' ]);
	        end;
	    end;  
        % handle rename 
        % -------------
        if results{end-1} ~= 1, args = { args{:}, 'rename', [ '''' allfields{results{end-1}-1} '->' results{end} '''' ] }; end;  
    end;
else % no interactive inputs
    args = varargin;
    % scan args to modify array/file format
    % array are transformed into string 
    % files are transformed into string of string
    % (this is usefull to build the string command for the function)
    % --------------------------------------------------------------
    for index=1:2:length(args)
        if iscell(args{index+1}), args{index+1} = { args{index+1} }; end; % double nested 
        if isstr(args{index+1})                 args{index+1} = [ '''' args{index+1} '''' ]; % string 
        else if ~isempty( inputname(index+2) ), args{index+1} = inputname(index+2); end;
        end;
    end;                
end;
     
% create structure
% ----------------
if ~isempty(args)
   try, g = struct(args{:});
   catch, disp('Setevent: wrong syntax in function arguments'); return; end;
else
    g = [];
end;
g

% test the presence of variables
% ------------------------------
try, g.fields; 	 	  catch, g.fields = {}; end;
try, g.skipline;      catch, g.skipline = 0; end;
try, g.indices;  g.append = '''yes'''; catch, g.indices = []; end;
try, g.append; 	      catch, g.append = '''yes'''; end;
try, g.timeunit; 	  catch, g.timeunit = 1; end;
try, g.align; 	      catch, g.align = 0; end;
try, g.delim; 	      catch, g.delim = char([9 32]); end;

% determine latency for old event alignment
% -----------------------------------------
g.align.val = g.align;
if g.align.val ~= 0
    if isempty(EEG.event)
        error('Setevent: no pre-existing event, cannot perform alignment');
    end;    
    if ~isfield(EEG.event, 'latency')
        error('Setevent: pre-existing events do not have a latency field for re-alignment');
    end;    
    switch g.append
        case 'yes', disp('Setevent warning: using align, events should not be appended but erased');
    end;
    if g.align.val < 0
        g.align.event = EEG.event(1).latency;
    else
        g.align.event = EEG.event(g.align.val).latency;
    end
    g.align.nbevent = length(EEG.event);
    { EEG.event(1:3).latency }
    g.align
end;

tmpfields = fieldnames(g);
% scan all the fields of g
% ------------------------
for curfield = tmpfields'
    switch lower(curfield{1})
        case {'append', 'fields', 'skipline', 'indices', 'timeunit', 'align', 'delim' }, ; % do nothing now
        case 'event', % load an ascii file
            switch g.append 
                case '''no'''
                      EEG.event = load_file_or_array( g.event, g.skipline, g.delim );
                      allfields = g.fields(1:min(length(g.fields), size(EEG.event,2)));
                      EEG.event = eventformat(EEG.event, 'struct', allfields);
                      EEG.event = recomputelatency( EEG.event, 1:length(EEG.event), EEG.srate, g.timeunit, g.align);
                case '''yes'''
                      % match existing fields
                      % ---------------------
                      tmparray = load_file_or_array( g.event, g.skipline, g.delim );
                      if isempty(g.indices) g.indices = [1:size(tmparray,1)] + length(EEG.event); end;
                      if length(g.indices) ~= size(tmparray,1)
                            error('Set error: number of row in file does not match the number of event given as input'); 
                      end;

                      % add field
                      % ---------
                      g.fields = getnewfields( g.fields, size(tmparray,2)-length(g.fields));
                      
                      % add new values
                      % ---------------------
                      for eventfield = 1:size(tmparray,2)
                          EEG.event = setstruct( EEG.event, g.fields{eventfield}, g.indices, cell2mat(tmparray(:,eventfield)));
                      end;      
                      EEG.event = recomputelatency( EEG.event, g.indices, EEG.srate, g.timeunit, g.align);
            end;
       case 'rename',
            if isempty( findstr(g.rename, '->') ), disp('Set warning: bad syntax for rename'); end;
            oldname = g.rename(2:findstr(g.rename, '->')-1);
            newname = g.rename(findstr(g.rename, '->')+2:end-1);
            indexmatch = strmatch(oldname, allfields);
            if isempty(indexmatch), disp('Set warning: name not found for rename'); 
            else
                for index  = 1:length(EEG.event)
                     eval([ 'EEG.event(index).' newname '=EEG.event(index).' oldname ';']);  
                end;    
                EEG.event = rmfield(EEG.event, oldname);
            end;
       otherwise, % user defined field command
                  % --------------------------
            infofield = findstr(curfield{1}, 'info');
            if ~isempty(infofield) & infofield == length( curfield{1} )-3
                % description of a field
                % ----------------------     
                fieldname = curfield{1}(1:infofield-1);
                indexmatch = strmatch( fieldname, allfields);
                if isempty( indexmatch )
                    disp(['Set warning: Field ' fieldname 'not found to add description, ignoring' ...
                    '(if it is a new field form the command line, put the description after adding the field)']);
                else
                    EEG.eventdescription{indexmatch} = getfield(g, curfield{1});
                end;
            else              
                % not an field for description
                % ----------------------------      
	            if isempty( getfield(g, curfield{1}) ) % delete
	                 indexmatch = strmatch( curfield{1}, allfields);
                     if isempty( indexmatch )
                        disp(['Set warning: Field ''' curfield{1} ''' not found for deletion, ignoring']);
                     else
	                    EEG.event = rmfield(EEG.event, curfield{1}); 
	                    allfields = { allfields{1:indexmatch-1} allfields{indexmatch+1:end}};
	                 end;    
	            else % interpret
		            switch g.append 
		                case '''no'''
		                      EEG.event = load_file_or_array( getfield(g, curfield{1}), g.skipline, g.delim );
		                      allfields = { curfield{1} };
                              EEG.event = eventformat(EEG.event, 'struct', allfields);
                              EEG.event = recomputelatency( EEG.event, 1:length(EEG.event), EEG.srate, g.timeunit, g.align);
		                 case'''yes''' % match existing fields
		                           % ---------------------
		                      tmparray = load_file_or_array( getfield(g, curfield{1}), g.skipline, g.delim );
		                      if isempty(g.indices) g.indices = [1:size(tmparray(:),1)] + length(EEG.event); end;
		                      
		                      indexmatch = strmatch(curfield{1}, allfields);
		                      if isempty(indexmatch) % no match
		                          disp(['Set: field ''' curfield{1} ''' not found, creating new field']);
		                      end;
		                      
		                      EEG.event = setstruct(EEG.event, curfield{1}, g.indices, cell2mat(tmparray));     
                              EEG.event = recomputelatency( EEG.event, g.indices, EEG.srate, g.timeunit, g.align);
		             end;
	            end;
	        end;    
      end;
end;

if isempty(EEG.event) % usefull 0xNB empty structure
    EEG.event = [];
end;

% remove the events which latency are out of boundary
% ----------------------------------------------------
alllatencies = cell2mat( { EEG.event.latency } );
I1 = find(alllatencies < 0);
I2 = find(alllatencies > EEG.pnts*EEG.trials);
if (length(I1) + length(I2)) > 0 
    fprintf('Setevent: %d/%d events had out of boudary latencies and were removed\n', length(I1) + length(I2), length(EEG.event));
    EEG.event(union(I1, I2)) = [];
end;

% generate the output command
% ---------------------------
com = sprintf('%s = setevent( %s', inputname(1), inputname(1));
for i=1:2:length(args)
    if ~isempty( args{i+1} )
        if isstr( args{i+1} ) com = sprintf('%s, ''%s'', %s', com, args{i}, args{i+1} );
        else    if ~iscell( args{i+1} ) com = sprintf('%s, ''%s'', [%s]', com, args{i}, num2str(args{i+1}) );
                else 
                    com = sprintf('%s, ''%s'', { ', com, args{i});
                    for index = 1:length( args{i+1}{1} ), com = sprintf('%s ''%s'' ', com, args{i+1}{1}{index}); end;
                    com = sprintf('%s }', com);
                end;                    
        end;
    end;
end;
com = [com ');'];

% interpret the variable name
% ---------------------------
function array = load_file_or_array( varname, skipline, delim );
    if varname(1) == '''', % mean that it is a filename
                          % --------------------------
        varname = eval(varname);
        if exist(varname) ~= 2, error( [ 'Set error: no filename ' varname ] ); end;

		fid=fopen(varname,'r','ieee-le');
		if fid<0, error( ['Set error: file ' varname ' found but error while opening file\n'] ); end;  

		for index=1:skipline	fgetl(fid); end; % skip lines ---------
        inputline = fgetl(fid);
        linenb = 1;
        fprintf('\n');
        while inputline~=-1
            colnb = 1;
            while ~isempty(deblank(inputline))
                [tmp inputline] = strtok(inputline, delim);
                tmp2 = str2num( tmp );
                if isempty( tmp2 ), array{linenb, colnb} = tmp;
                else                array{linenb, colnb} = tmp2;
                end;
                colnb = colnb+1;
            end;
            linenb = linenb +1;
            inputline = fgetl(fid);
            if ~mod(linenb,10), fprintf('%d ', linenb); end;
        end;        
        fprintf('\n');
		fclose(fid); 

        %varname = eval(varname);
        %if exist(varname) ~= 2, error( [ 'Set error: no filename ' varname ] ); end;
		%fid=fopen(varname,'r','ieee-le');
		%if fid<0, error( ['Set error: file ' varname ' found but error while opening file\n'] ); end;  
		%for index=1:skipline	fgetl(fid); end; % skip lines ---------
        %firstline = fgetl(fid);
        %tmparray = sscanf(firstline, '%f', [Inf]);
        %fseek(fid, 0, 'bof');
		%for index=1:skipline	fgetl(fid); end; % skip lines ---------
		%array 		    = fscanf(fid, '%f', [length(tmparray), inf]);
		%fclose(fid); 
		%array = array';

    else % variable in the global workspace
         % --------------------------
         array = evalin('base', varname);
    end;     
return;

% update latency values
% ---------------------
function event = recomputelatency( event, indices, srate, timeunit, align);
    if ~isfield(event, 'latency'), return; end;
    for index = indices
        event(index).latency = round(event(index).latency*srate*timeunit);
    end;
    align
    event(1).latency
    srate*timeunit
    if align.val ~= 0
        if align.val > 0, alignlatency = event(1).latency;
        else              alignlatency = event(-align.val).latency;
        end;
        for index = indices
             event(index).latency = event(index).latency-alignlatency+align.event;
        end;
        if length(event) ~= align.nbevent
            disp([ 'Setevent warning: the number of pre-existing events do not correspond to the ' ...
                   'number of event that were read, so their latencies may have been wrongly re-aligned' ]);
        end;           
    end;
    { event(1:30).latency }
         
% create new field names
% ----------------------
function epochfield = getnewfields( epochfield, nbfields )
   count = 1;
   while nbfields > 0
       if isempty( strmatch([ 'var' int2str(count) ], epochfield ) )
               epochfield =  { epochfield{:} [ 'var' int2str(count) ] };
               nbfields = nbfields-1;
       else    count = count+1;
       end;                    
   end;     
return;
    
function var = setstruct( var, fieldname, indices, values )
    if exist('indices') ~= 1, indices = 1:length(var); end;
    for index = 1:length(indices)
        var = setfield(var, {indices(index)}, fieldname, values(index));
    end;
return;
