function data = ft_anonimizedata(cfg, data)

% FT_ANONIMIZEDATA clears the value of potentially identifying fields in
% the data and in the provenance information, i.e., it updates the data and
% the configuration structure and history that is maintained by FieldTrip
% in the cfg field.
%
% Use as
%   output = ft_anonimizedata(cfg, data)
% where data is any FieldTrip data structure and cfg is a configuration
% structure that should contain
%   cfg.keepnumeric = 'yes' or 'no', keep numeric fields (default = 'yes')
%   cfg.keepfield   = cell-array with strings, fields to keep (default = {})
%   cfg.removefield = cell-array with strings, fields to remove (default = {})
%   cfg.keepvalue   = cell-array with strings, values to keep (default = {})
%   cfg.removevalue = cell-array with strings, values to remove (default = {})
%
% The graphical user interface consists of a table that shows the name and
% value of each provenance element, and whether it should be kept or
% removed. Furthermore, it has a number of buttons:
%   - sort        specify which column is used for sorting
%   - apply       apply the current selection of "keep" and "remove" and hide the corresponding rows
%   - keep all    toggle all visibe rows to "keep"
%   - remove all  toggle all visibe rows to "keep"
%   - clear all   clear all visibe rows, i.e. neither "keep" nor "remove"
%   - quit        apply the current selection of "keep" and "remove" and exit
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile  = ...
%   cfg.outputfile  = ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure.
%
% See also FT_ANALYSISPIPELINE

% Copyright (C) 2014, Robert Oostenveld, DCCN
%
% This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
% for the documentation and details.
%
%  FieldTrip 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 3 of the License, or
% (at your option) any later version.
%
%  FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id: ft_anonimizedata.m 10196 2015-02-11 09:15:07Z roboos $

revision = '$Id: ft_anonimizedata.m 10196 2015-02-11 09:15:07Z roboos $';

% do the general setup of the function
ft_defaults               % this ensures that the path is correct and that the ft_defaults global variable is available
ft_preamble init          % this will reset warning_once and show the function help if nargin==0 and return an error
ft_preamble provenance    % this records the time and memory usage at the beginning of the function
ft_preamble trackconfig   % this converts the cfg structure in a config object, which tracks the cfg options that are being used
ft_preamble debug         % this allows for displaying or saving the function name and input arguments upon an error
ft_preamble loadvar data  % this reads the input data in case the user specified the cfg.inputfile option

% the abort variable is set to true or false in ft_preamble_init
if abort
  return
end

% get the options
cfg.keepfield   = ft_getopt(cfg, 'keepfield', {});
cfg.removefield = ft_getopt(cfg, 'removefield', {});
cfg.keepvalue   = ft_getopt(cfg, 'keepvalue', {});
cfg.removevalue = ft_getopt(cfg, 'removevalue', {});
cfg.keepnumeric = ft_getopt(cfg, 'keepnumeric', 'yes');

if isfield(data, 'cfg')
  % ensure that it is a structure, not a config object
  data.cfg = struct(data.cfg);
end

% determine the name and value of each element in the structure
[name, value] = splitstruct('data', data);

% we can rule out the numeric values as identifying
sel = cellfun(@ischar, value);
if istrue(cfg.keepnumeric)
  name  = name(sel);
  value = value(sel);
else
  % the numeric values are also to be judged by the end-user, but cannot be displayed easily
  % FIXME it would be possible to display single scalar values by converting them to a string
  value(~sel) = {'<numeric>'};
end

% do not bother with fields that are empty
sel = cellfun(@numel, value)>0;
name  = name(sel);
value = value(sel);

% all values are char, but some might be a char-array rather than a single string
sel = cellfun('size', value, 1)>1;
value(sel) = {'<multiline char array>'};

for i=1:length(value)
  % remove all non-printable characters
  sel = value{i}<32 | value{i}>126;
  value{i}(sel) = [];
end

keep = false(size(name));
for i=1:numel(cfg.keepfield)
  expression = sprintf('\\.%s$', cfg.keepfield{i});
  keep = keep | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
  expression = sprintf('\\.%s\\.', cfg.keepfield{i});
  keep = keep | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
  expression = sprintf('\\.%s\\(', cfg.keepfield{i});
  keep = keep | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
  expression = sprintf('\\.%s\\{', cfg.keepfield{i});
  keep = keep | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
end

keep = keep | ismember(value, cfg.keepvalue);

remove = false(size(name));
for i=1:numel(cfg.removefield)
  expression = sprintf('\\.%s$', cfg.removefield{i});
  remove = remove | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
  expression = sprintf('\\.%s\\.', cfg.removefield{i});
  remove = remove | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
  expression = sprintf('\\.%s\\(', cfg.removefield{i});
  remove = remove | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
  expression = sprintf('\\.%s\\{', cfg.removefield{i});
  remove = remove | ~cellfun(@isempty, regexp(name, expression), 'uniformoutput', 1);
end

remove = remove | ismember(value, cfg.removevalue);

% ensure there is no overlap
keep(remove) = false;

%% construct the graphical user interface

h = figure;
set(h, 'menuBar', 'none')
mp = get(0, 'MonitorPosition');
if size(mp,1)==1
  % there is only a single monitor, we can try to go fullscreen
  set(h,'units','normalized','position',[0 0 1 1])
else
  set(h,'units','normalized');
end

%% add the table to the GUI

t = uitable;
set(t, 'ColumnEditable', [true true false false]);
set(t, 'ColumnName', {'keep', 'remove', 'name', 'value'});
set(t, 'RowName', {});

%% add the buttons to the GUI

uicontrol('tag', 'button1', 'parent', h, 'units', 'pixels', 'style', 'popupmenu', 'string', {'remove', 'keep', 'name', 'value'}, 'userdata', 'sort', 'callback', @sort_cb);
uicontrol('tag', 'button2', 'parent', h, 'units', 'pixels', 'style', 'pushbutton', 'string', 'apply',       'userdata', 'a',  'callback', @keyboard_cb)
uicontrol('tag', 'button3', 'parent', h, 'units', 'pixels', 'style', 'pushbutton', 'string', 'keep all',    'userdata', 'ka', 'callback', @keyboard_cb)
uicontrol('tag', 'button4', 'parent', h, 'units', 'pixels', 'style', 'pushbutton', 'string', 'remove all',  'userdata', 'ra', 'callback', @keyboard_cb)
uicontrol('tag', 'button5', 'parent', h, 'units', 'pixels', 'style', 'pushbutton', 'string', 'clear all',   'userdata', 'ca', 'callback', @keyboard_cb)
uicontrol('tag', 'button6', 'parent', h, 'units', 'pixels', 'style', 'pushbutton', 'string', 'quit',        'userdata', 'q',  'callback', @keyboard_cb)

% use manual positioning of the buttons in pixel units
ft_uilayout(h, 'tag', 'button1', 'hpos', 20+(100+10)*0, 'vpos', 10, 'width', 100, 'height', 25);
ft_uilayout(h, 'tag', 'button2', 'hpos', 20+(100+10)*1, 'vpos', 10, 'width', 100, 'height', 25);
ft_uilayout(h, 'tag', 'button3', 'hpos', 20+(100+10)*2, 'vpos', 10, 'width', 100, 'height', 25);
ft_uilayout(h, 'tag', 'button4', 'hpos', 20+(100+10)*3, 'vpos', 10, 'width', 100, 'height', 25);
ft_uilayout(h, 'tag', 'button5', 'hpos', 20+(100+10)*4, 'vpos', 10, 'width', 100, 'height', 25);
ft_uilayout(h, 'tag', 'button6', 'hpos', 20+(100+10)*5, 'vpos', 10, 'width', 100, 'height', 25);

ft_uilayout(h, 'tag', 'button1', 'retag', 'buttongroup')
ft_uilayout(h, 'tag', 'button1', 'retag', 'buttongroup')
ft_uilayout(h, 'tag', 'button1', 'retag', 'buttongroup')
ft_uilayout(h, 'tag', 'button1', 'retag', 'buttongroup')
ft_uilayout(h, 'tag', 'button1', 'retag', 'buttongroup')
ft_uilayout(h, 'tag', 'button1', 'retag', 'buttongroup')

ft_uilayout(h, 'tag', 'buttongroup', 'BackgroundColor', [0.8 0.8 0.8]);

% this structure is passed around as appdata
info         = [];
info.table   = t;
info.name    = name;
info.value   = value;
info.keep    = keep;
info.remove  = remove;
info.hide    = false(size(name));
info.cleanup = false;
info.cfg     = cfg;

% this is consistent with the sort button
[dum, indx] = sort(~info.remove);
info.keep   = info.keep(indx);
info.remove = info.remove(indx);
info.name   = info.name(indx);
info.value  = info.value(indx);
info.hide   = info.hide(indx);

% these callbacks need the info appdata
setappdata(h, 'info', info);
set(h, 'CloseRequestFcn', 'delete(gcf)');
set(h, 'ResizeFcn', @resize_cb);

redraw_cb(h);
resize_cb(h);

while ~info.cleanup
  
  uiwait(h); % we only get part this point with abort or cleanup
  
  if ~ishandle(h)
    error('aborted by user');
  end
  
  info = getappdata(h, 'info');
  
  if info.cleanup
    if ~all(xor(info.keep, info.remove))
      warning('not all fields have been marked as "keep" or "remove"');
      info.cleanup = false;
    else
      delete(h);
    end
  end
end


fprintf('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n');
name = info.name(info.remove);
for i=1:length(name)
  str = sprintf('%s = ''removed by ft_anonimizedata'';', name{i});
  disp(str);
  eval(str);
end
fprintf('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n');

% deal with the output
ft_postamble debug  % this clears the onCleanup function used for debugging in case of an error
ft_postamble trackconfig  % this converts the config object back into a struct and can report on the unused fields
ft_postamble provenance  % this records the time and memory at the end of the function, prints them on screen and adds this information together with the function name and MATLAB version etc. to the output cfg
ft_postamble previous data  % this copies the datain.cfg structure into the cfg.previous field. You can also use it for multiple inputs, or for "varargin"
ft_postamble history data  % this adds the local cfg structure to the output data structure, i.e. dataout.cfg = cfg
ft_postamble savevar data  % this saves the output data structure to disk in case the user specified the cfg.outputfile option

end % function

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function h = getparent(h)
p = h;
while p~=0
  h = p;
  p = get(h, 'parent');
end
end % function

function redraw_cb(h, eventdata)
h = getparent(h);
info = getappdata(h, 'info');
data = cat(2, num2cell(info.keep), num2cell(info.remove), info.name, info.value);
data = data(~info.hide,:);
set(info.table, 'data', data);
end % function

function keyboard_cb(h, eventdata)
if isempty(eventdata)
  % determine the key that corresponds to the uicontrol element that was activated
  key = get(h, 'userdata');
else
  % determine the key that was pressed on the keyboard
  key = parseKeyboardEvent(eventdata);
end
h = getparent(h);
info = getappdata(h, 'info');

data = get(info.table, 'data');

sel = info.keep & info.remove;
if any(sel)
  warning('items that were marked both as "keep" and "remove" have been cleared');
  info.keep(sel) = false;
  info.remove(sel) = false;
end

info.keep  (~info.hide) = cell2mat(data(:,1));
info.remove(~info.hide) = cell2mat(data(:,2));

switch key
  case 'q'
    info.cleanup = true;
    uiresume
  case 'a'
    info.hide(info.keep)   = true;
    info.hide(info.remove) = true;
  case 'ka'
    info.keep  (~info.hide) = true;
    info.remove(~info.hide) = false;
  case 'ra'
    info.keep  (~info.hide) = false;
    info.remove(~info.hide) = true;
  case 'ca'
    info.keep  (~info.hide) = false;
    info.remove(~info.hide) = false;
end
setappdata(h, 'info', info);
redraw_cb(h)
end % function

function sort_cb(h, eventdata)
h = getparent(h);
info = getappdata(h, 'info');
val = get(findobj(h, 'userdata', 'sort'), 'value');
str = get(findobj(h, 'userdata', 'sort'), 'string');
switch str{val}
  case 'remove'
    [dum, indx] = sort(~info.remove);
  case 'keep'
    [dum, indx] = sort(~info.keep);
  case 'name'
    [dum, indx] = sort(info.name);
  case 'value'
    [dum, indx] = sort(info.value);
end
info.keep   = info.keep(indx);
info.remove = info.remove(indx);
info.name   = info.name(indx);
info.value  = info.value(indx);
info.hide   = info.hide(indx);
setappdata(h, 'info', info);
redraw_cb(h);
end % function

function resize_cb(h, eventdata)
drawnow
h = getparent(h);
info = getappdata(h, 'info');
set(h, 'units', 'pixels');
siz = get(h, 'position');
% the 15 is for the vertical scrollbar on the right
set(info.table, 'units', 'normalized', 'position', [0.05 0.1 0.90 0.85]);
set(info.table, 'ColumnWidth', {50 50 300 600});
end
