function varargout = process_extract_maxfreq( varargin )
% PROCESS_EXTRACT_MAXFREQ: Find the maximum value in time (returns the latency or the peak values).

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2016; Martin Cousineau, 2017

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Find maximum in frequency';
    sProcess.FileTag     = @GetFileTag;
    sProcess.Category    = 'File';
    sProcess.SubGroup    = 'Extract';
    sProcess.Index       = 355;
    sProcess.Description = '';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'timefreq'};
    sProcess.OutputTypes = {'timefreq'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 1;
    sProcess.isSeparator = 1;

    % Definition of the options
    % === SELECT: FREQUENCY RANGE
    sProcess.options.freqrange.Comment  = 'Frequency range: ';
    sProcess.options.freqrange.Type     = 'freqrange';
    sProcess.options.freqrange.Value    = [];
    % === METHOD
    sProcess.options.labelmethod.Comment = '
What to detect:';
    sProcess.options.labelmethod.Type    = 'label';
    sProcess.options.method.Comment = {'Maximum amplitude  (positive or negative peak)', 'Maximum value  (positive peak)', 'Minimum value  (negative peak)'; ...
                                       'absmax', 'max', 'min'};
    sProcess.options.method.Type    = 'radio_label';
    sProcess.options.method.Value   = 'absmax';
    % === OUTPUT
    sProcess.options.labelout.Comment = '
Value to save in the output file:';
    sProcess.options.labelout.Type    = 'label';
    sProcess.options.output.Comment = {'Peak amplitude  (for each signal separately)', 'Frequency of the peak  (for each signal separately)'; ...
                                       'amplitude', 'frequency'};
    sProcess.options.output.Type    = 'radio_label';
    sProcess.options.output.Value   = 'amplitude';
    % === OVERWRITE
    sProcess.options.overwrite.Comment = 'Overwrite input files';
    sProcess.options.overwrite.Type    = 'checkbox';
    sProcess.options.overwrite.Value   = 0;
    sProcess.options.overwrite.Group   = 'output';
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    % Preprocess values to detect the correct peaks
    switch (sProcess.options.method.Value)
        case 'absmax',  Comment = 'Find maximum amplitude: ';
        case 'max',     Comment = 'Find maximum value: ';
        case 'min',     Comment = 'Find minimum value: ';
    end
    % Freq indices
    if isfield(sProcess.options, 'freqrange') && isfield(sProcess.options.freqrange, 'Value') && iscell(sProcess.options.freqrange.Value) && (length(sProcess.options.freqrange.Value) == 3) && (length(sProcess.options.freqrange.Value{1}) == 2)
        FreqRange = sProcess.options.freqrange.Value{1};
        if (FreqRange(1) == FreqRange(2))
            Comment = [Comment, ' Invalid frequency selection'];
            return;
        else
            Comment = [Comment, ' ' num2str(FreqRange(1)) '-' num2str(FreqRange(2)) 'Hz'];
        end
    end
    % Output
    if isequal(sProcess.options.output.Value, 'frequency')
        Comment = [Comment, ', frequency'];
    end
end

%% ===== GET FILE TAG =====
function fileTag = GetFileTag(sProcess)
    fileTag = sProcess.options.method.Value;
end

%% ===== RUN =====
function OutputFile = Run(sProcess, sInput) %#ok<DEFNU>

    % ===== GET OPTIONS =====
    Output = sProcess.options.output.Value;
    Method = sProcess.options.method.Value;
    isOverwrite = sProcess.options.overwrite.Value;
    % Freq indices
    if isfield(sProcess.options, 'freqrange') && isfield(sProcess.options.freqrange, 'Value') && iscell(sProcess.options.freqrange.Value) && (length(sProcess.options.freqrange.Value) == 3) && (length(sProcess.options.freqrange.Value{1}) == 2)
        FreqRange = sProcess.options.freqrange.Value{1};
    else
        FreqRange = [];
    end
    OutputFile = [];

    % ===== LOAD FILE =====
    % Load TF file
    TimefreqMat = in_bst_timefreq(sInput.FileName, 0);
    % Check for measure
    if strcmpi(TimefreqMat.Measure, 'none')
        bst_report('Error', sProcess, sInput, 'Cannot process complex values. Please apply a measure to the values before calling this function.');
        return;
    end
    % Get file frequency vector
    if iscell(TimefreqMat.Freqs)
        BandBounds = process_tf_bands('GetBounds', TimefreqMat.Freqs);
        FreqVector = mean(BandBounds,2);
        FreqBounds = [BandBounds(1,1), BandBounds(end,2)];
    else
        FreqVector = TimefreqMat.Freqs;
        FreqBounds = [FreqVector(1), FreqVector(end)];
    end
    % Rounds the frequency vector, to have the same level of precision as the process (3 significant digits)
    FreqVector = round(FreqVector * 1000) / 1000;
    % Keep only selected frequencies
    if ~isempty(FreqRange)
        % Find the selected frequencies
        iFreqs = find((FreqVector >= FreqRange(1)) & (FreqVector <= FreqRange(2)));
        if isempty(iFreqs)
            bst_report('Error', sProcess, sInput, 'Invalid frequency range.');
            return;
        end
        TimefreqMat.TF = TimefreqMat.TF(:,:,iFreqs);
        % Keep only the selected frequencies
        if iscell(TimefreqMat.Freqs)
            TimefreqMat.Freqs = TimefreqMat.Freqs(iFreqs,:);
        else
            TimefreqMat.Freqs = TimefreqMat.Freqs(iFreqs);
        end
        FreqVector = FreqVector(iFreqs);
    end

    % ===== FIND MAXIMUM =====
    % Preprocess values to detect the correct peaks
    minmaxFunc = @max;
    switch (Method)
        case 'absmax'
            TimefreqMat.TF = abs(TimefreqMat.TF);
        case 'max'
            % nothing to change
        case 'min'
            minmaxFunc = @min;
    end
    % Find maximum in frequency
    [MinMax, iMinMax] = minmaxFunc(TimefreqMat.TF, [], 3);
    % Save the expected value
    switch (Output)
        case 'amplitude'
            TimefreqMat.TF = MinMax;
            strMethod = '';
        case 'frequency'
            TimefreqMat.TF = reshape(FreqVector(iMinMax), size(iMinMax));
            strMethod = ', frequency';
            TimefreqMat.DisplayUnits = 'Hz';
            TimefreqMat.Measure = 'frequency';
    end

    % ===== SAVE FILE =====
    % Save as frequency band
    FreqBounds = round(FreqBounds * 10) / 10;
    TimefreqMat.Freqs = {Method, [num2str(FreqBounds(1)) ', ' num2str(FreqBounds(2))], Method};
    % Add file tag
    TimefreqMat.Comment = [TimefreqMat.Comment, ' | ' Method strMethod];
    % Do not keep the Std/TFmask fields in the output
    if isfield(sInput, 'Std') && ~isempty(sInput.Std)
        sInput.Std = [];
    end
    if isfield(sInput, 'TFmask') && ~isempty(sInput.TFmask)
        sInput.TFmask = [];
    end
    % Add history entry
    TimefreqMat = bst_history('add', TimefreqMat, 'maxfreq', ['Find the ' Method ' across frequencies.']);

    % Overwrite the input file
    if isOverwrite
        OutputFile = file_fullpath(sInput.FileName);
        bst_save(OutputFile, TimefreqMat, 'v6');
        % Reload study
        db_reload_studies(sInput.iStudy);
    % Save new file
    else
        % Output filename: add file tag
        OutputFile = strrep(file_fullpath(sInput.FileName), '.mat', '_maxfreq.mat');
        OutputFile = file_unique(OutputFile);
        % Save file
        bst_save(OutputFile, TimefreqMat, 'v6');
        % Add file to database structure
        db_add_data(sInput.iStudy, OutputFile, TimefreqMat);
    end
end


function varargout = process_tf_bands( varargin )
% PROCESS_TF_FREQBANDS: Group TF values in frequency bands.
%
% USAGE:  TimefreqMat = process_tf_bands('Compute', TimefreqMat, FreqBands, TimeBands)
%            strBands = process_tf_bands('FormatBands', FreqBands)
%               Bands = process_tf_bands('ParseBands', strBands)
%           FreqBands = process_tf_bands('Eval', FreqBands)
%          BandBounds = process_tf_bands('GetBounds', FreqBands)

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2012-2021

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Group in time or frequency bands';
    sProcess.Category    = 'File';
    sProcess.SubGroup    = 'Frequency';
    sProcess.Index       = 520;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/TimeFrequency#Process_options';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'timefreq'};
    sProcess.OutputTypes = {'timefreq'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 1;
    sProcess.isSeparator = 1;
    % === IS FREQ BANDS
    sProcess.options.isfreqbands.Comment = 'Group by frequency bands (name/freqs/function):';
    sProcess.options.isfreqbands.Type    = 'checkbox';
    sProcess.options.isfreqbands.Value   = 1;
    % === FREQ BANDS
    sProcess.options.freqbands.Comment = '';
    sProcess.options.freqbands.Type    = 'groupbands';
    sProcess.options.freqbands.Value   = bst_get('DefaultFreqBands');
    % === IS TIME BANDS
    sProcess.options.istimebands.Comment = 'Group by time bands (name/time/function):';
    sProcess.options.istimebands.Type    = 'checkbox';
    sProcess.options.istimebands.Value   = 0;
    % === TIME BANDS
    sProcess.options.timebands.Comment = '';
    sProcess.options.timebands.Type    = 'groupbands';
    sProcess.options.timebands.Value   = '';
    % === OVERWRITE
    sProcess.options.overwrite.Comment = 'Overwrite input files';
    sProcess.options.overwrite.Type    = 'checkbox';
    sProcess.options.overwrite.Value   = 0;
    sProcess.options.overwrite.Group   = 'output';
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
    Comment = sProcess.Comment;
end

%% ===== RUN =====
function OutputFile = Run(sProcess, sInput) %#ok<DEFNU>
    OutputFile = [];
    % Get options
    isFreqBands = sProcess.options.isfreqbands.Value;
    isTimeBands = sProcess.options.istimebands.Value;
    if isFreqBands
        FreqBands = sProcess.options.freqbands.Value;
    else
        FreqBands = [];
    end
    if isTimeBands
        TimeBands = sProcess.options.timebands.Value;
    else
        TimeBands = [];
    end
    isOverwrite = sProcess.options.overwrite.Value;
    % If no input
    if isempty(FreqBands) && isempty(TimeBands)
        bst_report('Error', sProcess, sInput, 'No time or frequency bands selected.');
        return;
    end

    % Load TF file
    TimefreqMat = in_bst_timefreq(sInput.FileName, 0);
    % Call function to group by frequency bands
    [TimefreqMat, Messages] = Compute(TimefreqMat, FreqBands, TimeBands);
    % Error
    if isempty(TimefreqMat)
        bst_report('Error', sProcess, sInput, Messages);
        return;
    end

    % Comment
    TimefreqMat.Comment = [TimefreqMat.Comment, ' | tfbands'];
    % Overwrite the input file
    if isOverwrite
        OutputFile = file_fullpath(sInput.FileName);
        bst_save(OutputFile, TimefreqMat, 'v6');
        % Reload study
        db_reload_studies(sInput.iStudy);
    % Save new file
    else
        % Output filename: add file tag
        OutputFile = strrep(file_fullpath(sInput.FileName), '.mat', '_tfbands.mat');
        OutputFile = file_unique(OutputFile);
        % Save file
        bst_save(OutputFile, TimefreqMat, 'v6');
        % Add file to database structure
        db_add_data(sInput.iStudy, OutputFile, TimefreqMat);
    end
end

%% ===== COMPUTE =====
function [TimefreqMat, Messages] = Compute(TimefreqMat, FreqBands, TimeBands)
    Messages = '';
    % Error: Cannot average complex values
    if ~isreal(TimefreqMat.TF)
        Messages = 'Cannot average complex values. Please apply a measure first.';
        TimefreqMat = [];
        return;
    end
    % Error: File is already in frequency bands
    if ~isempty(FreqBands) && iscell(TimefreqMat.Freqs)
        Messages = 'File is already averaged in frequency bands.';
        TimefreqMat = [];
        return;
    end
    % Error: File is already in time bands
    if ~isempty(TimeBands) && ~isempty(TimefreqMat.TimeBands)
        Messages = 'File is already averaged in time bands.';
        TimefreqMat = [];
        return;
    end

    % ===== FREQUENCY BANDS =====
    if ~isempty(FreqBands)
        % Check format
        if (size(FreqBands,2) ~= 3) || ~all(cellfun(@ischar, FreqBands(:))) || any(cellfun(@(c)isempty(strtrim(c)), FreqBands(:)))
            Messages = 'Invalid frequency band format.';
            TimefreqMat = [];
            return;
        end
        % Frequency bounds
        BandBounds = GetBounds(FreqBands);
        % Number of bands
        nBands = size(FreqBands,1);
        % Initialize the averaged matrix
        TF_bands = zeros(size(TimefreqMat.TF,1), size(TimefreqMat.TF,2), nBands);
        % Process each band
        for iBand = 1:nBands
            % Get frequency indices in this band
            iFreq = find((TimefreqMat.Freqs >= BandBounds(iBand,1)) & (TimefreqMat.Freqs <= BandBounds(iBand,2)));
            % No frequencies in this frequency band
            if isempty(iFreq)
                disp(['BST> Warning: Frequency band "' FreqBands{iBand,1} '" is empty.']);
                continue;
            end
            % Check the band function name
            if ~ismember(FreqBands{iBand,3}, {'mean', 'max', 'median', 'std'})
                Messages = ['Invalid function "' FreqBands{iBand,3} '".'];
                TimefreqMat = [];
                return;
            end
            % Average the values for the band
            switch lower(FreqBands{iBand,3})
                case 'mean',   TF_bands(:,:,iBand) = mean(TimefreqMat.TF(:,:,iFreq),    3);
                case 'median', TF_bands(:,:,iBand) = median(TimefreqMat.TF(:,:,iFreq),  3);
                case 'max',    TF_bands(:,:,iBand) = max(TimefreqMat.TF(:,:,iFreq), [], 3);
                case 'std',    TF_bands(:,:,iBand) = std(TimefreqMat.TF(:,:,iFreq), [], 3);
            end
        end
        % Update input structure
        TimefreqMat.TF    = TF_bands;
        TimefreqMat.Freqs = FreqBands;
    end

    % ===== TIME BANDS =====
    if ~isempty(TimeBands)
        dt = .0001;
        % Frequency bounds
        BandBounds = GetBounds(TimeBands);
        % Number of bands
        nBands = size(TimeBands,1);
        % Initialize the averaged matrix
        TF_bands = zeros(size(TimefreqMat.TF,1), nBands, size(TimefreqMat.TF,3));
        % Process each frequency band
        for iBand = 1:nBands
            % Get time indices in this band
            iTime = find((TimefreqMat.Time >= BandBounds(iBand,1) - dt) & (TimefreqMat.Time <= BandBounds(iBand,2) + dt));
            % No time samples in this band
            if isempty(iTime)
                disp(['BST> Warning: Time band "' TimeBands{iBand,1} '" is empty.']);
                continue;
            end
            % Check the band function name
            if ~ismember(TimeBands{iBand,3}, {'mean', 'max', 'median', 'std'})
                Messages = ['Invalid function "' TimeBands{iBand,3} '".'];
                TimefreqMat = [];
                return;
            end
            % Average the values for the band
            switch lower(TimeBands{iBand,3})
                case 'mean',    TF_bands(:,iBand,:) = mean(TimefreqMat.TF(:,iTime,:),    2);
                case 'median',  TF_bands(:,iBand,:) = median(TimefreqMat.TF(:,iTime,:),  2);
                case 'max',     TF_bands(:,iBand,:) = max(TimefreqMat.TF(:,iTime,:), [], 2);
                case 'std',     TF_bands(:,iBand,:) = std(TimefreqMat.TF(:,iTime,:), [], 2);
            end
        end
        % Update input structure
        TimefreqMat.TF = TF_bands;
        TimefreqMat.TimeBands = TimeBands;
    end

    % Do not keep the Std/TFmask fields in the output
    if isfield(TimefreqMat, 'Std') && ~isempty(TimefreqMat.Std)
        TimefreqMat.Std = [];
    end
    if isfield(TimefreqMat, 'TFmask') && ~isempty(TimefreqMat.TFmask)
        TimefreqMat.TFmask = [];
    end
end

%% ===== BANDS => STRING =====
function strBands = FormatBands(Bands) %#ok<DEFNU>
    strBands = '';
    for i = 1:size(Bands,1)
        if (size(Bands, 2) == 3)
            strBands = [strBands, sprintf('%s / %s / %s\n', Bands{i,1}, Bands{i,2}, Bands{i,3})];
        elseif (size(Bands, 2) == 2)
            strBands = [strBands, sprintf('%s / %s\n', Bands{i,1}, Bands{i,2})];
        end
    end
end

%% ===== STRING => BANDS =====
function [Bands, errMsg] = ParseBands(strBands) %#ok<DEFNU>
    Bands = {};
    errMsg = '';
    if isempty(strtrim(strBands))
        return
    end
    % Split by lines
    lineBand = str_split(strBands, 10);
    % Process each line
    for iBand = 1:length(lineBand)
        % Split line
        valBand = str_split(lineBand{iBand}, '/\|');
        if (length(valBand) ~= 3) || any(cellfun(@(c)isempty(strtrim(c)), valBand))
            errMsg = ['Invalid time or frequency band "' lineBand{iBand} '".'];
            disp(['BST> Error: ' errMsg]);
            Bands = {};
            return;
        end
        for i = 1:length(valBand)
            Bands{iBand,i} = strtrim(valBand{i});
        end
    end
end

%% ===== EVALUATE BANDS =====
function Bands = Eval(Bands)
    for iBand = 1:size(Bands,1)
        Bands{iBand,2} = eval(['[', Bands{iBand,2}, ']']);
    end
end

%% ===== GET BAND BOUNDS =====
function BandBounds = GetBounds(Bands)
    Bands = Eval(Bands);
    BandBounds = zeros(size(Bands,1),2);
    for iBand = 1:size(Bands,1)
        BandBounds(iBand,1) = Bands{iBand,2}(1);
        BandBounds(iBand,2) = Bands{iBand,2}(end);
    end
end


classdef NormDescriptor < handle

    properties (Access = protected)
        dist
    end

    methods (Access = protected, Abstract)
        computeDistance(obj)
    end

end


function jed = epoch_to_jed_macedonian ( )

%*****************************************************************************80
%
%% EPOCH_TO_JED_MACEDONIAN: epoch of the Macedonian calendar as a JED.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    14 June 2012
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Output, real JED, the Julian Ephemeris Date of the epoch.
%
  jed = 1607708.5;

  return
end


function [out]=snip(in,arg1,varargin)

%% SNIP FUNCTION  %2nd Version, 13.Sept.2013
% Snip something out of a vector or matrix.
%
% (C) for snip.m function implementation Nicolas Ummen, under BSD2 open source copyright license.
% Comments etc. to NicolasUmmen@web.de
%
% The function snip takes the following arguments:
%
% snip(input,argument_1,argument_2)
%
% %arg_1 = what to snip, arg_2 = for matrices, to define 'r'ow or 'c'olumn
%
% It then removes either the specified kinds of elements from the input, or
% removes the element at the stated position, then compresses the input by as
% much as was deleted out of it.
%
% LIST OF POSSIBLE ARGUMENT_1s:
% 'x'   -> remove all x's from the input vector, compress it afterwards
% '1'   -> remove all 1's from the input vector, compress it afterwards
% [all other comparable items to the above are possible]
% 1     -> remove THE FIRST element, move all one up, shorten vector by 1
% i     -> remove COMPLEX elements from input, compress it afterwards
% j     -> [same as above] - note that 'i' and 'j' would remove the characters i or j.
% nan   -> remove all nan's, compress it
% inf   -> remove all infs from the input vector, compress it afterwards
%
% etc.
%
% The rest of the possible arguments follows matlab notation, such as:
% last  -> snip off the last element
% :     -> snip from:to
%
% Note that 'partial' deletions, for example getting rid of elements from
% (2,3) to (2,6) in a 7x7 matrix = A, is much better accomplished by overwriting
% them directly as A(2,3:6) = 0; or similar. A compression of the data is
% not possible, as elements remain in e.g. (2,1:2)
%
% ALLOWED DATA TYPES: Everything except self-defined structures/objects.
%
% EXAMPLES:
%
% in = [2 1 3 NaN NaN 1]; snip(in,2)   = [2 3 NaN NaN 1]
% in = [2 1 3 NaN NaN 1]; snip(in,'1') = [2 3 NaN NaN]
% in = [2 1 3 NaN NaN 1]; snip(in,nan) = [2 1 3 1]
% in = [2 1 3 inf inf 1]; snip(in,inf) = [2 1 3 1]
% compl = complex(1,2); in = [2 1 3 compl compl 1]; snip(in,i) = [2 1 3 1]
% in = [2 1 3 NaN NaN 1]; snip(in,'last') = [2 1 3 NaN NaN]
% in = ['abcdefg']; snip(in,'c')   = abdefg
% in = ['abcdefg']; snip(in,'last') = abcdef
% in = ['abcdefg']; snip(in,2)     = acdefg
%
% Whether the input is a row or column vector doesn't matter, and the output
% is returned accordingly.
%
% Advanced vector, snip out a certain, continuous length:
%
% in = [2 1 3 NaN NaN 1]; snip(in,2:4) = [2 NaN 1]
%
% MATRIX EXAMPLES: either snip out completely rows/columns, or identify rows
% and columns that have the same elements throughout and delete these.
% Note: deleting all e.g. 3's therefore leaves 3's in mixed entry columns
% or rows. In some cases using snip.m column or row-wise on the matrix
% might help.
%
% in = [1 2 3; 3 3 3; 4 3 3]; snip(in,2,'r') = [1 2 3; 4 3 3]
% in = [1 2 3; 3 3 3; 4 3 3]; snip(in,2,'c') = [1 3; 3 3; 4 3]
% in = [1 2 3; 3 3 3; 4 3 3; 5 3 3; 6 3 3]; snip(in,2:4,'r') = [1 2 3; 6 3 3]
% in = [1 2 3; 3 3 3; 4 3 3]; snip(in,'1')     = [1 2 3; 3 3 3; 4 3 3]
% Note the above doesn't detect a complete row/column full of 1's...
%
% in = [1 2 3; 3 3 3; 4 3 3]; snip(in,'3') = [1 2; 4 3]
% While this one detected a row and a column of solely 3's. To overwrite
% all e.g. 3s in the above matrix, rather use find - or logical indexing.
% E.g. in(in == 3) = 0 for the result [1 2 0; 0 0 0; 4 0 0].
%
% in = [1 2 nan; nan nan nan; 4 nan nan]; snip(in,nan) = [1 2; 4 nan]
% in = ['abc'; 'acb'; 'abc']; snip(in,'a') = ['bc','cb','bc']
%
% etc as above for vectors
%
% For more complicated choices from a matrix; like the first, third and
% seventh column; use the notation A = A(:,[1,3,7])

% Change log:
% 2nd Version: Added infinity and complex number option on request.

%% (C) for snip.m function implementation Nicolas Ummen, under BSD2 open soure copyright license.
% Comments etc. to NicolasUmmen@web.de

% main function body

%scrutinize what the user wants, operate on most likely first
flagflipped = 0;
if isstruct(in)
   out = in;
   warning('snip function: structures cannot be snipped!')
   return
end

[z s] = size(in);
if z == 0
    out = in;
    warning('snip function: 0x0 input detected!')
    return
end

if z > s
    in = in';
    flagflipped = 1;
    [z s] = size(in);
end

%operate on numerical things first

%if it is a vector
if z == 1 && strcmpi(arg1,'last') %snip end command
    out = in(1,1:end-1);
    if flagflipped == 1
        out = out';
    end
    return
end

if z == 1 && isnan(arg1(1)) %sort nans out
    out = in(~isnan(in));
    if flagflipped == 1
        out = out';
    end
    return
end

if z == 1 && isinf(arg1(1)) %sort infs out
    out = in(~isinf(in));
    if flagflipped == 1
        out = out';
    end
    return
end

if z == 1 && ~isreal(arg1(1)) %sort complex numbers out
    out = in(~imag(in));
    if flagflipped == 1
        out = out';
    end
    return
end

if z == 1 && isnumeric(arg1) %snip out a specific place/length from vector
    if arg1(end) > s
        out = in;
        if flagflipped == 1
            out = out';
        end
        warning('snip function: the vector is too short for that snip!')
        return
    end
    out = [in(1:arg1(1)-1) in(arg1(end)+1:end)];
    if flagflipped == 1
        out = out';
    end
    return
end

if z == 1 && iscell(in) %if it is a cell vector
    if strcmpi(arg1,'last') %snip end command
        out = in{1,1:end-1};
        if flagflipped == 1
            out = out';
        end
        return
    end
    arg1 = str2num(arg1);
    if arg1(end) > s
        out = in;
        if flagflipped == 1
            out = out';
        end
        warning('snip function: the vector is too short for that snip!')
        return
    end
    out = [in{1:arg1(1)-1} in{arg1(end)+1:end}];
    if flagflipped == 1
        out = out';
    end
    return
end

if z == 1 %snip out all objects, like 'a', or '1' from a vector
    if length(str2num(arg1)) == 1
        arg1 = str2num(arg1);
    end
    out = in(in~=arg1);
    if flagflipped == 1
        out = out';
    end
    return
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%if it is a matrix

if z >= 1 && isnan(arg1(1)) %snip out all nans
    in = in(:,all(isnan(in),1) ~= 1);
    out = in(all(isnan(in),2) ~= 1,:);
    if flagflipped == 1
        out = out';
    end
    return
end

if z >= 1 && isinf(arg1(1)) %snip out all infs
    in = in(:,all(isinf(in),1) ~= 1);
    out = in(all(isinf(in),2) ~= 1,:);
    if flagflipped == 1
        out = out';
    end
    return
end

if z >= 1 && ~isreal(arg1(1)) %snip out complex numbers
    in = in(:,all(imag(in),1) ~= 1);
    out = in(all(imag(in),2) ~= 1,:);
    if flagflipped == 1
        out = out';
    end
    return
end

if z >= 1 && isempty(varargin) == 1 && ischar(arg1) && ischar(in(1,1)) %snip out all equal elements
    in = in(:,all(in==arg1,1) ~= 1);
    out = in(all(in==arg1,2) ~= 1,:);
    if flagflipped == 1
        out = out';
    end
    return
end

if z >= 1 && isempty(varargin) == 1 %snip out all equal elements
    arg1 = str2num(arg1);
    in = in(:,all(in==arg1,1) ~= 1);
    out = in(all(in==arg1,2) ~= 1,:);
    if flagflipped == 1
        out = out';
    end
    return
end

if z >= 1 && ischar(varargin{1}) %snip out specific row(s) or column(s)
    arg2 = varargin{1};
    if flagflipped == 1
        if strcmp(arg2,'c')
            arg2 = 'r';
        else
            if strcmp(arg2,'r')
            arg2 = 'c';
            end
        end
    end
    switch arg2
        case 'c'
            if arg1(end) > s
                out = in;
                if flagflipped == 1
                    out = out';
                end
                warning('snip function: the matrix is not large enough for this snip!')
                return
            end
            out = [in(:,1:arg1(1)-1) in(:,arg1(end)+1:end)];
        case 'r'
            if arg1(end) > z
                out = in;
                if flagflipped == 1
                    out = out';
                end
                warning('snip function: the matrix is not large enough for this snip!')
                return
            end
            out = [in(1:arg1(1)-1,:); in(arg1(end)+1:end,:)];
    end
    if flagflipped == 1
        out = out';
        return
    end
    return
end

%catch things that didn't go in the above cases

out = in;

if flagflipped == 1
    out = out';
end
warning('snip function: function completed, but no relevant case to execute occured!')
return

end


function start_num = p06_start_num ( )

%*****************************************************************************80
%
%% P06_START_NUM returns the number of starting points for problem 6.
%
%  Licensing:
%
%    This code is distributed under the GNU LGPL license.
%
%  Modified:
%
%    08 May 2011
%
%  Author:
%
%    John Burkardt
%
%  Parameters:
%
%    Output, integer START_NUM, the number of starting points.
%
  start_num = 2;

  return
end


function test_suite = test_surficial_io_niml_dset
% tests for AFNI NIML input/output
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions=localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_niml_dset_dataset_io()
    if cosmo_skip_test_if_no_external('afni')
        return;
    end

    ds=get_niml_dataset();
    fn=cosmo_make_temp_filename('_tmp','.niml.dset');
    cleaner=onCleanup(@()delete(fn));

    formats={'ascii','binary','binary.lsbfirst','binary.msbfirst'};

    n_formats=numel(formats);

    for k=1:n_formats
        format=formats{k};
        niml_str=get_niml_dset_string(format);

        fid=fopen(fn,'w');
        fwrite(fid,niml_str);
        fclose(fid);
        ds2=cosmo_surface_dataset(fn);
        assert_dataset_equal(ds,ds2);

        cosmo_map2surface(ds,fn,'encoding',format);
        ds3=cosmo_surface_dataset(fn);
        assert_dataset_equal(ds,ds3);

        % try mapping using functionality in AFNI Matlab library
        s=struct();
        s.data=ds.samples';
        s.node_indices=ds.a.fdim.values{1}(ds.fa.node_indices)-1;
        s.stats=ds.sa.stats;
        s.labels=ds.sa.labels;

        afni_niml_writesimple(s, fn, format);
        ds4=cosmo_surface_dataset(fn);
        assert_dataset_equal(ds,ds4);

        % ensure that mapping to struct works
        s2=cosmo_map2surface(ds,'-niml_dset','encoding',format);
        assertEqual(s2,s);

    end

function assert_dataset_equal(x,y)
    assertElementsAlmostEqual(x.samples,y.samples,'absolute',1e-4);
    assertEqual(sort(fieldnames(x)),sort(fieldnames(y)));
    assertEqual(x.fa,y.fa);
    assertEqual(x.sa,y.sa);
    assertEqual(x.a,y.a);

function ds=get_niml_dataset()
    ds=cosmo_synthetic_dataset('ntargets',2,'nchunks',1,...
                        'type','surface');
    ds.a.fdim.values{1}=1+[1 4 7 8 5 3];
    ds.sa=struct();
    ds.sa.stats={'Ftest(3,4)';'Zscore()'};
    % TODO: use these complicated labels
    % ds.sa.labels={'label1<>"&''';'label2'};
    ds.sa.labels={'label1';'label2'};

function string=get_niml_dset_string(format)
    string=[...
            get_niml_dset_header() ...
            get_niml_dset_data(format) ...
            get_niml_dset_footer()
            ];

function string=get_niml_dset_header()
    string=bytes_printf([...
            '<AFNI_dataset\n' ...
            '  dset_type="Node_Bucket"\n' ...
            '  self_idcode="XYZ_QJCWHYMMSUIUXCMBUZJASSJT"\n' ...
            '  filename="data.niml.dset"\n' ...
            '  label="data.niml.dset"\n' ...
            '  ni_form="ni_group" >\n' ...
            ]);

function string=get_niml_dset_footer()
    string=bytes_printf([...
            '<AFNI_atr\n' ...
            '  atr_name="COLMS_RANGE"\n' ...
            '  ni_type="String"\n' ...
            '  ni_dimen="1" >\n' ...
            '"-3.6849477 2.0316862 1 0;-1.3264908 2.3386572 2 4"'...
                        '</AFNI_atr>\n' ...
            '<AFNI_atr\n' ...
            '  atr_name="COLMS_LABS"\n' ...
            '  ni_type="String"\n' ...
            '  ni_dimen="1" >\n' ...
            '"label1;label2"</AFNI_atr>\n' ...
            '<AFNI_atr\n' ...
            '  atr_name="COLMS_TYPE"\n' ...
            '  ni_type="String"\n' ...
            '  ni_dimen="1" >\n' ...
            '"Generic_Float;Generic_Float"</AFNI_atr>\n' ...
            '<AFNI_atr\n' ...
            '  atr_name="COLMS_STATSYM"\n' ...
            '  ni_type="String"\n' ...
            '  ni_dimen="1" >\n' ...
            '"Ftest(3,4);Zscore()"</AFNI_atr>\n' ...
            '</AFNI_dataset>\n' ...
            ]);
    % TODO: use these complicated labels:
    % '"label1<>"&';label2"</AFNI_atr>\n' ...

function bytes=get_niml_dset_data(format)

    ds=get_niml_dataset();
    data=ds.samples;
    nodes_base0=ds.a.fdim.values{1}(ds.fa.node_indices)-1;

    [nrows,ncols]=size(data);

    row_sep=sprintf('\n');
    col_sep=' ';

    switch lower(format)
        case 'ascii'
            data_pat=[repmat(['%.6f' col_sep],1,nrows) row_sep];
            bucket_data=uint8(sprintf(data_pat,data));

            node_pat=['%d' row_sep];
            node_data=uint8(sprintf(node_pat,nodes_base0));
            ni_form='';

        case {'binary','binary.lsbfirst','binary.msbfirst'};
            data_single=single(data(:)');
            node_int=int32(nodes_base0(:)');

            [unused,unused,endian]=computer();
            computer_format=sprintf('binary.%ssbfirst',lower(endian));

            do_swap=~strcmp(format,'binary') && ...
                            ~strcmp(format, computer_format);

            if do_swap
                if cosmo_wtf('is_octave')
                    % Octave 3.8 does not support byteswap for single
                    data_single_int=typecast(data_single,'int32');
                    data_single_int_swap=swapbytes(data_single_int);
                    data_single=typecast(data_single_int_swap,'single');
                else
                    data_single=swapbytes(data_single);
                end
                node_int=swapbytes(node_int);
            end

            if strcmp(format,'binary')
                ni_form_format=computer_format;
            else
                ni_form_format=format;
            end

            bucket_data=typecast(data_single,'uint8');
            node_data=typecast(node_int,'uint8');

            ni_form=sprintf('  ni_form="%s"\n',ni_form_format);

        otherwise
            error('unsupported format %s', format);
    end

    bytes=[...
            bytes_printf([...
                '<SPARSE_DATA\n' ...
                '  data_type="Node_Bucket_data"\n' ...
                '  ni_type="2*float"\n' ...
                ni_form ...
                '  ni_dimen="6" >' ])...
            bucket_data ...
            bytes_printf([...
                '</SPARSE_DATA>\n' ...
                '<INDEX_LIST\n' ...
                '  data_type="Node_Bucket_node_indices"\n' ...
                '  sorted_node_def="No"\n' ...
                '  COLMS_RANGE="1 8 0 3"\n' ...
                '  COLMS_LABS="Node Indices"\n' ...
                '  COLMS_TYPE="Node_Index"\n' ...
                '  ni_type="int"\n' ...
                ni_form ...
                '  ni_dimen="6" >']) ...
            node_data ...
            bytes_printf([...
                '</INDEX_LIST>\n' ])...
            ];

function b=bytes_printf(varargin)
    b=uint8(sprintf(varargin{:}));


function [cfg] = ft_multiplotER(cfg, varargin)

% FT_MULTIPLOTER plots the event-related potentials or event-related fields verus
% time, or the oscillatory activity (power or coherence) versus frequency. Multiple
% datasets can be overlayed. The plots are arranged according to their location
% specified in the layout.
%
% Use as
%   ft_multiplotER(cfg, data)
% or
%   ft_multiplotER(cfg, data, data2, ..., dataN)
%
% The data can be an event-related potential or field produced by
% FT_TIMELOCKANALYSIS, a power spectrum produced by FT_FREQANALYSIS or a coherence
% spectrum produced by FT_FREQDESCRIPTIVES.
%
% If you specify multiple datasets they should contain the same channels, etc.
%
% The configuration can have the following parameters:
%   cfg.parameter     = field to be plotted on y-axis, for example 'avg', 'powspctrm' or 'cohspctrm' (default is automatic)
%   cfg.maskparameter = field in the first dataset to be used for marking significant data
%   cfg.maskstyle     = style used for masking of data, 'box', 'thickness' or 'saturation' (default = 'box')
%   cfg.maskfacealpha = mask transparency value between 0 and 1
%   cfg.xlim          = 'maxmin' or [xmin xmax] (default = 'maxmin')
%   cfg.ylim          = 'maxmin', 'maxabs', 'zeromax', 'minzero', or [ymin ymax] (default = 'maxmin')
%   cfg.gradscale     = number, scaling to apply to the MEG gradiometer channels prior to display
%   cfg.magscale      = number, scaling to apply to the MEG magnetometer channels prior to display
%   cfg.channel       = Nx1 cell-array with selection of channels (default = 'all'), see FT_CHANNELSELECTION for details
%   cfg.refchannel    = name of reference channel for visualising connectivity, can be 'gui'
%   cfg.baseline      = 'yes', 'no' or [time1 time2] (default = 'no'), see FT_TIMELOCKBASELINE or FT_FREQBASELINE
%   cfg.trials        = 'all' or a selection given as a 1xN vector (default = 'all')
%   cfg.axes          = string, 'yes' or 'no' whether to draw x- and y-axes for each graph (default = 'yes')
%   cfg.box           = string, 'yes' or 'no' whether to draw a box around each graph (default = 'no')
%   cfg.showlabels    = 'yes' or 'no' (default = 'no')
%   cfg.showoutline   = 'yes' or 'no' (default = 'no')
%   cfg.showscale     = 'yes' or 'no' (default = 'yes')
%   cfg.showcomment   = 'yes' or 'no' (default = 'yes')
%   cfg.comment       = string of text (default = date + limits)
%                       Add 'comment' to graph (according to COMNT in the layout)
%   cfg.limittext     = add user-defined text instead of cfg.comment, (default = cfg.comment)
%   cfg.fontsize      = font size of comment and labels (default = 8)
%   cfg.interactive   = 'yes' or 'no', make the plot interactive (default = 'yes')
%                       In a interactive plot you can select areas and produce a new
%                       interactive plot when a selected area is clicked. Multiple areas
%                       can be selected by holding down the SHIFT key.
%   cfg.renderer      = 'painters', 'zbuffer', ' opengl' or 'none' (default = [])
%   cfg.colorgroups   = 'sequential', 'allblack', 'labelcharN' (N = Nth character in label), 'chantype' or a vector
%                       with the length of the number of channels defining the groups (default = 'sequential')
%   cfg.linestyle     = linestyle/marker type, see options of the PLOT function (default = '-')
%                       can be a single style for all datasets, or a cell-array containing one style for each dataset
%   cfg.linewidth     = linewidth in points (default = 0.5)
%   cfg.linecolor     = color(s) used for plotting the dataset(s) (default = 'brgkywrgbkywrgbkywrgbkyw')
%                       alternatively, colors can be specified as Nx3 matrix of RGB values
%   cfg.directionality = '', 'inflow' or 'outflow' specifies for connectivity measures whether the
%                       inflow into a node, or the outflow from a node is plotted. The (default) behavior
%                       of this option depends on the dimord of the input data (see below).
%   cfg.layout        = specify the channel layout for plotting using one of the supported ways (see below).

% The following options for the scaling of the EEG, EOG, ECG, EMG, MEG and NIRS channels
% is optional and can be used to bring the absolute numbers of the different
% channel types in the same range (e.g. fT and uV). The channel types are determined
% from the input data using FT_CHANNELSELECTION.
%   cfg.eegscale      = number, scaling to apply to the EEG channels prior to display
%   cfg.eogscale      = number, scaling to apply to the EOG channels prior to display
%   cfg.ecgscale      = number, scaling to apply to the ECG channels prior to display
%   cfg.emgscale      = number, scaling to apply to the EMG channels prior to display
%   cfg.megscale      = number, scaling to apply to the MEG channels prior to display
%   cfg.gradscale     = number, scaling to apply to the MEG gradiometer channels prior to display (in addition to the cfg.megscale factor)
%   cfg.magscale      = number, scaling to apply to the MEG magnetometer channels prior to display (in addition to the cfg.megscale factor)
%   cfg.nirsscale     = number, scaling to apply to the NIRS channels prior to display
%   cfg.mychanscale   = number, scaling to apply to the channels specified in cfg.mychan
%   cfg.mychan        = Nx1 cell-array with selection of channels
%   cfg.chanscale     = Nx1 vector with scaling factors, one per channel specified in cfg.channel
%
% For the plotting of directional connectivity data the cfg.directionality option
% determines what is plotted. The default value and the supported functionality
% depend on the dimord of the input data. If the input data is of dimord
% 'chan_chan_XXX', the value of directionality determines whether, given the
% reference channel(s), the columns (inflow), or rows (outflow) are selected for
% plotting. In this situation the default is 'inflow'. Note that for undirected
% measures, inflow and outflow should give the same output. If the input data is of
% dimord 'chancmb_XXX', the value of directionality determines whether the rows in
% data.labelcmb are selected. With 'inflow' the rows are selected if the
% refchannel(s) occur in the right column, with 'outflow' the rows are selected if
% the refchannel(s) occur in the left column of the labelcmb-field. Default in this
% case is '', which means that all rows are selected in which the refchannel(s)
% occur. This is to robustly support linearly indexed undirected connectivity
% metrics. In the situation where undirected connectivity measures are linearly
% indexed, specifying 'inflow' or 'outflow' can result in unexpected behavior.
%
% The layout defines how the channels are arranged and what the size of each
% subplot is. You can specify the layout in a variety of ways:
%  - you can provide a pre-computed layout structure (see prepare_layout)
%  - you can give the name of an ascii layout file with extension *.lay
%  - you can give the name of an electrode file
%  - you can give an electrode definition, i.e. "elec" structure
%  - you can give a gradiometer definition, i.e. "grad" structure
% If you do not specify any of these and the data structure contains an
% electrode or gradiometer structure, that will be used for creating a
% layout. If you want to have more fine-grained control over the layout
% of the subplots, you should create your own layout file.
%
% To facilitate data-handling and distributed computing you can use
%   cfg.inputfile   =  ...
% If you specify this option the input data will be read from a *.mat
% file on disk. This mat files should contain only a single variable named 'data',
% corresponding to the input structure. For this particular function, the
% data should be provided as a cell-array.
%
% See also FT_MULTIPLOTTFR, FT_SINGLEPLOTER, FT_SINGLEPLOTTFR, FT_TOPOPLOTER,
% FT_TOPOPLOTTFR, FT_PREPARE_LAYOUT

% Undocumented local options:
% cfg.layoutname
% cfg.preproc
% cfg.orient = landscape/portrait

% Copyright (C) 2003-2006, Ole Jensen
% Copyright (C) 2007-2011, Roemer van der Meij & Jan-Mathijs Schoffelen
% Copyright (C) 2012-2017, F.C. Donders Centre

%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% 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$

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% DEVELOPERS NOTE: This code is organized in a similar fashion for multiplot/singleplot/topoplot
% and for ER/TFR and should remain consistent over those 6 functions.
% Section 1: general cfg handling that is independent from the data
% Section 2: data handling, this also includes converting bivariate (chan_chan and chancmb) into univariate data
% Section 3: select the data to be plotted and determine min/max range
% Section 4: do the actual plotting
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Section 1: general cfg handling that is independent from the data

% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin   = nargin;
ft_nargout  = nargout;

% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar varargin
ft_preamble provenance varargin
ft_preamble trackconfig

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

Ndata = length(varargin);
for i=1:Ndata
  % check if the input data is valid for this function
  varargin{i} = ft_checkdata(varargin{i}, 'datatype', {'timelock', 'freq'});
end

% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'renamed', {'cohrefchannel', 'refchannel'});
cfg = ft_checkconfig(cfg, 'renamed', {'hlim', 'xlim'});
cfg = ft_checkconfig(cfg, 'renamed', {'matrixside',  'directionality'});
cfg = ft_checkconfig(cfg, 'renamed', {'vlim', 'ylim'});
cfg = ft_checkconfig(cfg, 'renamed', {'zparam', 'parameter'});
cfg = ft_checkconfig(cfg, 'renamed', {'graphcolor', 'linecolor'});
cfg = ft_checkconfig(cfg, 'renamedval', {'directionality', 'feedback', 'inflow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'directionality', 'feedforward', 'outflow'});
cfg = ft_checkconfig(cfg, 'renamedval', {'zlim', 'absmax', 'maxabs'});
cfg = ft_checkconfig(cfg, 'unused',  {'cohtargetchannel'});
% cfg = ft_checkconfig(cfg, 'deprecated', {'xparam'});

% set the defaults
cfg.baseline       = ft_getopt(cfg, 'baseline', 'no');
cfg.trials         = ft_getopt(cfg, 'trials', 'all', 1);
cfg.xlim           = ft_getopt(cfg, 'xlim', 'maxmin');
cfg.ylim           = ft_getopt(cfg, 'ylim', 'maxmin');
cfg.comment        = ft_getopt(cfg, 'comment', date);
cfg.limittext      = ft_getopt(cfg, 'limittext', 'default');
cfg.axes           = ft_getopt(cfg, 'axes', 'yes');
cfg.showlabels     = ft_getopt(cfg, 'showlabels', 'no');
cfg.showoutline    = ft_getopt(cfg, 'showoutline', 'no');
cfg.showscale      = ft_getopt(cfg, 'showscale',   'yes');
cfg.showcomment    = ft_getopt(cfg, 'showcomment', 'yes');
cfg.box            = ft_getopt(cfg, 'box', 'no');
cfg.fontsize       = ft_getopt(cfg, 'fontsize', 8);
cfg.fontweight     = ft_getopt(cfg, 'fontweight');
cfg.interactive    = ft_getopt(cfg, 'interactive', 'yes');
cfg.renderer       = ft_getopt(cfg, 'renderer'); % let MATLAB decide on the default
cfg.orient         = ft_getopt(cfg, 'orient', 'landscape');
cfg.maskparameter  = ft_getopt(cfg, 'maskparameter');
cfg.colorgroups    = ft_getopt(cfg, 'colorgroups', 'condition');
cfg.linecolor      = ft_getopt(cfg, 'linecolor', 'brgkywrgbkywrgbkywrgbkyw');
cfg.linestyle      = ft_getopt(cfg, 'linestyle', '-');
cfg.linewidth      = ft_getopt(cfg, 'linewidth', 0.5);
cfg.maskstyle      = ft_getopt(cfg, 'maskstyle', 'box');
cfg.maskfacealpha  = ft_getopt(cfg, 'maskfacealpha', 1);
cfg.channel        = ft_getopt(cfg, 'channel', 'all');
cfg.directionality = ft_getopt(cfg, 'directionality', '');
cfg.figurename     = ft_getopt(cfg, 'figurename');
cfg.preproc        = ft_getopt(cfg, 'preproc');
cfg.tolerance      = ft_getopt(cfg, 'tolerance', 1e-5);
cfg.frequency      = ft_getopt(cfg, 'frequency', 'all'); % needed for frequency selection with TFR data
cfg.latency        = ft_getopt(cfg, 'latency', 'all'); % needed for latency selection with TFR data, FIXME, probably not used

% check for linestyle being a cell-array
if ischar(cfg.linestyle)
  cfg.linestyle = repmat({cfg.linestyle}, 1, Ndata);
end
% check it's length, and lengthen it if does not have enough styles in it
if (length(cfg.linestyle) > 1) && (length(cfg.linestyle) < Ndata )
  ft_error('either specify cfg.linestyle as a cell-array with one cell for each dataset, or only specify one linestyle')
elseif (length(cfg.linestyle) == 1)
  cfg.linestyle = repmat(cfg.linestyle, 1, Ndata);
end

% this is needed for the figure title and correct labeling of linecolor later on
if isfield(cfg, 'dataname') && ~isempty(cfg.dataname)
  dataname = cfg.dataname;
elseif isfield(cfg, 'inputfile') && ~isempty(cfg.inputfile)
  dataname = cfg.inputfile;
elseif nargin>1
  dataname = arrayfun(@inputname, 2:nargin, 'UniformOutput', false);
else
  dataname = {};
end

%% Section 2: data handling, this also includes converting bivariate (chan_chan and chancmb) into univariate data

for i=1:Ndata
  dtype{i}   = ft_datatype(varargin{i});
  hastime(i) = isfield(varargin{i}, 'time');
  hasfreq(i) = isfield(varargin{i}, 'freq');
end

% check if the input has consistent datatypes
if ~all(strcmp(dtype, dtype{1})) || ~all(hastime==hastime(1)) || ~all(hasfreq==hasfreq(1))
  ft_error('different datatypes are not allowed as input');
end
dtype   = dtype{1};
hastime = hastime(1);
hasfreq = hasfreq(1);

% Set x/y/parameter defaults according to datatype and dimord
switch dtype
  case 'timelock'
    xparam = 'time';
    if isfield(varargin{1}, 'trial')
      cfg.parameter = ft_getopt(cfg, 'parameter', 'trial');
    elseif isfield(varargin{1}, 'individual')
      cfg.parameter = ft_getopt(cfg, 'parameter', 'individual');
    elseif isfield(varargin{1}, 'avg')
      cfg.parameter = ft_getopt(cfg, 'parameter', 'avg');
    end
  case 'freq'
    if hastime && hasfreq
      xparam = 'time';  % further down the code will compute the average over selected frequencies
    else
      xparam = 'freq';
    end
    cfg.parameter = ft_getopt(cfg, 'parameter', 'powspctrm');
  case 'comp'
    % not supported
  otherwise
    % not supported
end

% check whether rpt/subj is present and remove if necessary
dimord = getdimord(varargin{1}, cfg.parameter);
dimtok = tokenize(dimord, '_');
hasrpt = any(ismember(dimtok, {'rpt' 'subj'}));

if ~hasrpt
  assert(isequal(cfg.trials, 'all') || isequal(cfg.trials, 1), 'incorrect specification of cfg.trials for data without repetitions');
else
  assert(~isempty(cfg.trials), 'empty specification of cfg.trials for data with repetitions');
end

% parse cfg.channel
if isfield(cfg, 'channel') && isfield(varargin{1}, 'label')
  cfg.channel = ft_channelselection(cfg.channel, varargin{1}.label);
elseif isfield(cfg, 'channel') && isfield(varargin{1}, 'labelcmb')
  cfg.channel = ft_channelselection(cfg.channel, unique(varargin{1}.labelcmb(:)));
end

% apply baseline correction
if ~strcmp(cfg.baseline, 'no')
  tmpcfg = keepfields(cfg, {'baseline', 'baselinetype', 'baselinewindow', 'demean', 'parameter', 'channel'});
  for i=1:Ndata
    % keep mask-parameter if it is set
    if ~isempty(cfg.maskparameter)
      tempmask = varargin{i}.(cfg.maskparameter);
    end
    if strcmp(dtype, 'timelock') && strcmp(xparam, 'time')
      varargin{i} = ft_timelockbaseline(tmpcfg, varargin{i});
    elseif strcmp(dtype, 'freq') && strcmp(xparam, 'time')
      varargin{i} = ft_freqbaseline(tmpcfg, varargin{i});
    elseif strcmp(dtype, 'freq') && strcmp(xparam, 'freq')
      ft_error('Baseline correction is not supported for spectra without a time dimension');
    else
      ft_warning('Baseline correction not applied, please set xparam');
    end
    % put mask-parameter back if it is set
    if ~isempty(cfg.maskparameter)
      varargin{i}.(cfg.maskparameter) = tempmask;
    end
  end
end

% channels SHOULD be selected here, as no interactive action produces a new multiplot
tmpcfg = keepfields(cfg, {'channel', 'showcallinfo', 'trials'});
if hasrpt
  tmpcfg.avgoverrpt = 'yes';
else
  tmpcfg.avgoverrpt = 'no';
end
if hastime && hasfreq
  tmpcfg.avgoverfreq = 'yes';       % average over the selected frequencies
  tmpcfg.frequency = cfg.frequency; % not to be confused with cfg.xlim or cfg.ylim
  tmpcfg.keepfreqdim = 'no';
else
  tmpcfg.avgoverfreq = 'no';
end
tmpvar = varargin{1};
[varargin{:}] = ft_selectdata(tmpcfg, varargin{:});
% restore the provenance information
[cfg, varargin{:}] = rollback_provenance(cfg, varargin{:});

if isfield(tmpvar, cfg.maskparameter) && ~isfield(varargin{1}, cfg.maskparameter)
  % the mask parameter is not present after ft_selectdata, because it is
  % not included in all input arguments. Make the same selection and copy
  % it over
  tmpvar = ft_selectdata(tmpcfg, tmpvar);
  varargin{1}.(cfg.maskparameter) = tmpvar.(cfg.maskparameter);
end

clear tmpvar tmpcfg dimord dimtok hastime hasfreq hasrpt

% ensure that the preproc specific options are located in the cfg.preproc
% substructure, but also ensure that the field 'refchannel' remains at the
% highest level in the structure. This is a little hack by JM because the field
% refchannel can relate to connectivity or to an EEg reference.

if isfield(cfg, 'refchannel'), refchannelincfg = cfg.refchannel; cfg = rmfield(cfg, 'refchannel'); end
cfg = ft_checkconfig(cfg, 'createsubcfg',  {'preproc'});
if exist('refchannelincfg', 'var'), cfg.refchannel  = refchannelincfg; end

if ~isempty(cfg.preproc)
  % preprocess the data, i.e. apply filtering, baselinecorrection, etc.
  fprintf('applying preprocessing options\n');
  if ~isfield(cfg.preproc, 'feedback')
    cfg.preproc.feedback = cfg.interactive;
  end
  for i=1:Ndata
    varargin{i} = ft_preprocessing(cfg.preproc, varargin{i});
  end
end

% handle the bivariate case
dimord = getdimord(varargin{1}, cfg.parameter);
if startsWith(dimord, 'chan_chan_') || startsWith(dimord, 'chancmb_')
  % convert the bivariate data to univariate and call this plotting function with univariate input
  cfg.originalfunction = 'ft_multiplotER';
  cfg.trials = 'all'; % trial selection has been taken care off
  bivariate_common(cfg, varargin{:});
  return
end

% apply channel-type specific scaling
fn = fieldnames(cfg);
tmpcfg = keepfields(cfg, fn(endsWith(fn, 'scale') | startsWith(fn, 'mychan') | strcmp(fn, 'channel') | strcmp(fn, 'parameter')));
for i=1:Ndata
  varargin{i} = chanscale_common(tmpcfg, varargin{i});
end

%% Section 3: select the data to be plotted and determine min/max range

% Read or create the layout that will be used for plotting
tmpcfg = keepfields(cfg, {'layout', 'rows', 'columns', 'commentpos', 'scalepos', 'elec', 'grad', 'opto', 'showcallinfo'});
cfg.layout = ft_prepare_layout(tmpcfg, varargin{1});

% Take the subselection of channels that is contained in the layout, this is the same in all datasets
[selchan, sellay] = match_str(varargin{1}.label, cfg.layout.label);

% Get physical min/max range of x, i.e. time or frequency
if strcmp(cfg.xlim, 'maxmin')
  % Find maxmin throughout all varargins:
  xmin = [];
  xmax = [];
  for i=1:Ndata
    xmin = nanmin([xmin varargin{i}.(xparam)]);
    xmax = nanmax([xmax varargin{i}.(xparam)]);
  end
else
  xmin = cfg.xlim(1);
  xmax = cfg.xlim(2);
end

% Get the index of the nearest bin, this is the same in all datasets
xminindx = nearest(varargin{1}.(xparam), xmin);
xmaxindx = nearest(varargin{1}.(xparam), xmax);
xmin = varargin{1}.(xparam)(xminindx);
xmax = varargin{1}.(xparam)(xmaxindx);
selx = xminindx:xmaxindx;
xval = varargin{1}.(xparam)(selx);

% Get physical y-axis range, i.e. of the parameter to be plotted
if ~isnumeric(cfg.ylim)
  % Find maxmin throughout all varargins
  ymin = [];
  ymax = [];
  for i=1:Ndata
    % Select the channels in the data that match with the layout and that are selected for plotting
    dat = varargin{i}.(cfg.parameter)(selchan,selx);
    ymin = min([ymin min(min(min(dat)))]);
    ymax = max([ymax max(max(max(dat)))]);
  end
  if strcmp(cfg.ylim, 'maxabs') % handle maxabs, make y-axis center on 0
    ymax = max([abs(ymax) abs(ymin)]);
    ymin = -ymax;
  elseif strcmp(cfg.ylim, 'zeromax')
    ymin = 0;
  elseif strcmp(cfg.ylim, 'minzero')
    ymax = 0;
  end
else
  ymin = cfg.ylim(1);
  ymax = cfg.ylim(2);
end

% Gather the data from all input data structures
datamatrix = zeros(Ndata, length(selchan), length(selx));
for i=1:Ndata
  datamatrix(i,:,:) = varargin{i}.(cfg.parameter)(selchan, selx);
end

if ~isempty(cfg.maskparameter)
  % one value for each channel-time point
  maskmatrix = varargin{1}.(cfg.maskparameter)(selchan, selx);
else
  % create an Nx0 matrix
  maskmatrix = zeros(length(selchan), 0);
end

chanX      = cfg.layout.pos(sellay, 1);
chanY      = cfg.layout.pos(sellay, 2);
chanWidth  = cfg.layout.width(sellay);
chanHeight = cfg.layout.height(sellay);
chanLabel  = cfg.layout.label(sellay);

%% Section 4: do the actual plotting

% determine the coloring of channels/conditions
linecolor = linecolor_common(cfg, varargin{:});

cla
hold on

if ischar(linecolor)
  set(gca, 'ColorOrder', char2rgb(linecolor))
elseif isnumeric(linecolor)
  set(gca, 'ColorOrder', linecolor)
end

% Plot the data
for m=1:length(selchan)
  mask = maskmatrix(m, :);
  if strcmp(cfg.maskstyle, 'difference')
    % combine the conditions in a single plot, highlight the difference
    yval = squeeze(datamatrix(:,m,:));
    % Clip out of bounds y values:
    yval(yval > ymax) = ymax;
    yval(yval < ymin) = ymin;
    ft_plot_vector(xval, yval, 'width', chanWidth(m), 'height', chanHeight(m), 'hpos', chanX(m), 'vpos', chanY(m), 'hlim', [xmin xmax], 'vlim', [ymin ymax], 'color', linecolor, 'style', cfg.linestyle{1}, 'linewidth', cfg.linewidth, 'axis', cfg.axes, 'highlight', mask, 'highlightstyle', cfg.maskstyle, 'facealpha', cfg.maskfacealpha);
  else
    % loop over the conditions, plot them on top of each other
    for i=1:Ndata
      yval = squeeze(datamatrix(i,m,:));
      % clip out of bounds y values:
      yval(yval > ymax) = ymax;
      yval(yval < ymin) = ymin;
      % select the color for the channel/condition
      if strcmp(cfg.colorgroups, 'condition')
        color = linecolor(i,:);
      else
        color = linecolor(m,:);
      end
      ft_plot_vector(xval, yval, 'width', chanWidth(m), 'height', chanHeight(m), 'hpos', chanX(m), 'vpos', chanY(m), 'hlim', [xmin xmax], 'vlim', [ymin ymax], 'color', color, 'style', cfg.linestyle{i}, 'linewidth', cfg.linewidth, 'axis', cfg.axes, 'highlight', mask, 'highlightstyle', cfg.maskstyle, 'facealpha', cfg.maskfacealpha);
    end
  end
end % for number of channels

% plot the layout, labels and outline
ft_plot_layout(cfg.layout, 'box', istrue(cfg.box), 'label', istrue(cfg.showlabels), 'outline', istrue(cfg.showoutline), 'point', 'no', 'mask', 'no', 'fontsize', cfg.fontsize, 'labelyoffset', 1.4*median(cfg.layout.height/2), 'labelalignh', 'center', 'chanindx', find(~ismember(cfg.layout.label, {'COMNT', 'SCALE'})) );

% write comment
if istrue(cfg.showcomment)
  % Add the colors of the different conditions/datasets to the comment
  colorLabels = [];
  if Ndata > 1 && strcmp(cfg.colorgroups, 'condition')
    for i=1:Ndata
      if ischar(linecolor)
        colorLabels = [colorLabels '\n' dataname{i} '='         linecolor(i)     ];
      elseif isnumeric(linecolor)
        colorLabels = [colorLabels '\n' dataname{i} '=' num2str(linecolor(i, :)) ];
      end
    end
  end
  cfg.comment = [cfg.comment colorLabels];

  k = find(strcmp('COMNT', cfg.layout.label));
  if ~isempty(k)
    limittext = cfg.limittext;
    if ~strcmp(limittext, 'default')
      comment = limittext;
    else
      comment = cfg.comment;
      comment = sprintf('%0s\nxlim=[%.3g %.3g]', comment, xmin, xmax);
      comment = sprintf('%0s\nylim=[%.3g %.3g]', comment, ymin, ymax);
    end
    ft_plot_text(cfg.layout.pos(k, 1), cfg.layout.pos(k, 2), sprintf(comment), 'FontSize', cfg.fontsize, 'FontWeight', cfg.fontweight);
    % plot an invisible box, the text itself is not sufficient to get the automatic scaling of the figures axes to include COMNT
    xy(1) = cfg.layout.pos(k, 1) - cfg.layout.width(k, 1)/2;
    xy(2) = cfg.layout.pos(k, 1) + cfg.layout.width(k, 1)/2;
    xy(3) = cfg.layout.pos(k, 2) - cfg.layout.height(k, 1)/2;
    xy(4) = cfg.layout.pos(k, 2) + cfg.layout.height(k, 1)/2;
    ft_plot_box(xy, 'edgecolor', 'none');
  end
end

% Plot scales
if istrue(cfg.showscale)
  l = find(strcmp(cfg.layout.label, 'SCALE'));
  if ~isempty(l)
    x = cfg.layout.pos(l,1);
    y = cfg.layout.pos(l,2);
    plotScales([xmin xmax], [ymin ymax], x, y, chanWidth(1), chanHeight(1), cfg)
  end
end

axis tight
axis off
hold off

% Make the axis a little wider when boxes are shown
if strcmp(cfg.box, 'yes')
  abc = axis;
  axis(abc + [-1 +1 -1 +1]*mean(abs(abc))/10)
end

% Set orientation for printing if specified
if ~isempty(cfg.orient)
  orient(gcf, cfg.orient);
end

% set the figure window title
if ~isempty(dataname)
  set(gcf, 'Name', sprintf('%d: %s: %s', double(gcf), mfilename, join_str(', ', dataname)));
else
  set(gcf, 'Name', sprintf('%d: %s', double(gcf), mfilename));
end
set(gcf, 'NumberTitle', 'off');

% Set renderer if specified
if ~isempty(cfg.renderer)
  set(gcf, 'renderer', cfg.renderer)
end

% Make the figure interactive
if strcmp(cfg.interactive, 'yes')
  % add the cfg/data/channel information to the figure under identifier linked to this axis
  ident                 = ['axh' num2str(round(sum(clock.*1e6)))]; % unique identifier for this axis
  set(gca, 'tag', ident);
  info                  = guidata(gcf);
  info.(ident).x        = cfg.layout.pos(:, 1);
  info.(ident).y        = cfg.layout.pos(:, 2);
  info.(ident).label    = cfg.layout.label;
  info.(ident).dataname = dataname;
  info.(ident).cfg      = cfg;
  info.(ident).varargin = varargin;
  guidata(gcf, info);

  set(gcf, 'WindowButtonUpFcn',  {@ft_select_channel, 'multiple', true, 'callback', {@select_singleplotER}, 'event', 'WindowButtonUpFcn'});
  set(gcf, 'WindowButtonDownFcn', {@ft_select_channel, 'multiple', true, 'callback', {@select_singleplotER}, 'event', 'WindowButtonDownFcn'});
  set(gcf, 'WindowButtonMotionFcn', {@ft_select_channel, 'multiple', true, 'callback', {@select_singleplotER}, 'event', 'WindowButtonMotionFcn'});
end

% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble trackconfig
ft_postamble previous varargin
ft_postamble provenance
ft_postamble savefig

% add a menu to the figure, but only if the current figure does not have subplots
menu_fieldtrip(gcf, cfg, false);

if ~ft_nargout
  % don't return anything
  clear cfg
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function plotScales(hlim, vlim, hpos, vpos, width, height, cfg)

% the placement of all elements is identical
placement = {'hpos', hpos, 'vpos', vpos, 'width', width, 'height', height, 'hlim', hlim, 'vlim', vlim};

ft_plot_box([hlim vlim], placement{:}, 'edgecolor', 'k');

if hlim(1)<=0 && hlim(2)>=0
  ft_plot_line([0 0], vlim, placement{:}, 'color', 'k');
  ft_plot_text(0, vlim(1), '0  ', placement{:}, 'rotation', 90, 'HorizontalAlignment', 'right', 'VerticalAlignment', 'middle', 'FontSize', cfg.fontsize);
end

if vlim(1)<=0 && vlim(2)>=0
  ft_plot_line(hlim, [0 0], placement{:}, 'color', 'k');
  ft_plot_text(hlim(1), 0, '0  ', placement{:}, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'middle', 'FontSize', cfg.fontsize);
end

ft_plot_text(hlim(1), vlim(1), [num2str(hlim(1), 3) ' '], placement{:}, 'rotation', 90, 'HorizontalAlignment', 'right', 'VerticalAlignment', 'top',    'FontSize', cfg.fontsize);
ft_plot_text(hlim(2), vlim(1), [num2str(hlim(2), 3) ' '], placement{:}, 'rotation', 90, 'HorizontalAlignment', 'right', 'VerticalAlignment', 'bottom', 'FontSize', cfg.fontsize);
ft_plot_text(hlim(1), vlim(1), [num2str(vlim(1), 3) ' '], placement{:}, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'bottom', 'FontSize', cfg.fontsize);
ft_plot_text(hlim(1), vlim(2), [num2str(vlim(2), 3) ' '], placement{:}, 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'top',    'FontSize', cfg.fontsize);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION which is called after selecting channels in case of cfg.interactive='yes'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function select_singleplotER(label, varargin)
% fetch cfg/data based on axis indentifier given as tag
ident       = get(gca,'tag');
info        = guidata(gcf);
cfg         = info.(ident).cfg;
datvarargin = info.(ident).varargin;
if ~isempty(label)
  cfg = removefields(cfg, 'inputfile');   % the reading has already been done and varargin contains the data
  cfg.baseline = 'no';                    % make sure the next function does not apply a baseline correction again
  cfg.channel = label;
  cfg.dataname = info.(ident).dataname;   % put data name in here, this cannot be resolved by other means
  cfg.trials = 'all';                     % trial selection has already been taken care of
  fprintf('selected cfg.channel = {%s}\n', join_str(', ', cfg.channel));
  % ensure that the new figure appears at the same position
  f = figure('position', get(gcf, 'Position'));
  ft_singleplotER(cfg, datvarargin{:});
end


% importevent() - Import experimental events from data file or Matlab
%                 array into a structure.
%
% Usage: >> eventstruct = importevent( event, oldevent, srate);
%        >> eventstruct = importevent( event, oldevent, srate, 'key1', 'value1', ...);
%
% Input:
%   event     - [ 'filename'|array ] Filename of a text file, or name of
%               Matlab array in the global workspace containing an
%               array of events in the folowing format: The first column of
%               the cell array is the type of the event, the second the latency.
%               The others are user-defined. The function can read
%               either numeric or text entries in ascii files.
%   oldevent  - Old event structure. Used for aligning new events with old
%               ones. Enter [] is no such structure exist.
%   srate     - Sampling rate of the underlying data. Event latencies are
%               expressed in terms of latency in sample point with respect
%               to the data.
%
% Optional file or array input:
%  'fields'   - [Cell array] List of the name of each user-defined column,
%               optionally followed by a description. Ex: { 'type', 'latency' }
%  'skipline' - [Interger] Number of header rows to skip in the text file
%  'timeunit' - [ latency unit rel. to seconds ]. Default unit is 1 = seconds.
%               NaN indicates that the latencies are given in time points.
%  'delim'    - [string] String of delimiting characters in the input file.
%               Default is tab|space.
%
% Optional oldevent input:
%  'append'   - ['yes'|'no'] 'yes' = Append events to the current events in
%               the EEG dataset {default}: 'no' = Erase the previous events.
%  'indices'  - {integer vector] Vector indicating the indices of the events to
%               modify.
%  'align'    - [num] Align the first event latency to the latency of existing
%               event number (num), and check latency consistency.
%               A value of 0 indicates that the first events of the pre-defined
%               events and imported events will be aligned. A positive value (num)
%               aligns the first event to the num-th pre-existing event.
%               A negative value can also be used; then event number (-num)
%               is aligned to the first pre-existing event.  Default is 0.
%               (NaN-> no alignment).
%  'optimmeas' - ['median'|'mean'] Uses either the median of the mean
%               distance to align events. Default is 'mean'. Median is
%               preferable if events are missing in the event file.
%  'optimalign' - ['on'|'off'] Optimize the sampling rate of the new events so
%               they best align with old events. Default is 'on'.
%
% Outputs:
%   eventstruct - Event structure containing imported events
%
% Example: >> eventstruct = importevent( 'event_values.txt', [], 256, ...
%         'fields', {'type', 'latency','condition' }, ...
%         'append', 'no', 'align', 0, 'timeunit', 1E-3 );
%
%         This loads the ascii file 'event_values.txt' containing 3 columns
%         (event_type, latency, and condition). Latencies in the file are
%         in ms (1E-3). The first event latency is re-aligned with the
%         beginning of the dataset ('align', 0). Any previous events
%         are erased ('append', 'no').
%
% Author: Arnaud Delorme & Scott Makeig, CNL / Salk Institute, 2004
%
% See also: pop_importevent()

% Copyright (C) Arnaud Delorme, CNL / Salk Institute, 2004, 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

%% REVISION HISTORY
%% function definition, input check
function event = importevent(event, oldevent, srate, varargin)

if nargin < 1
   help importevent;
   return;
end;

I = [];

% remove the event field
% ----------------------
if ~isempty(oldevent), allfields = fieldnames(oldevent);
else                   allfields = {}; end;

g = finputcheck( varargin, { 'fields'    'cell'     []         {};
                         'skipline'  'integer'  [0 Inf]    0;
                         'indices'   'integer'  [1 Inf]    [];
                         'append'    'string'   {'yes';'no';'''yes''';'''no''' }         'yes';
                         'timeunit'  'real'     []         1;
                         'event'     { 'cell';'real';'string' }     []    [];
                         'align'     'integer'  []         NaN;
                         'optimalign' 'string'  { 'on';'off' }         'on';
                         'optimmeas'  'string'  { 'median';'mean' }         'mean';
                         'delim'     {'integer';'string'}   []         char([9 32 44])}, 'importevent');
if isstr(g), error(g); end;
if ~isempty(g.indices), g.append = 'yes'; end;
g.delim = char(g.delim);

% call from pop_importevent
% -------------------------
if ~isempty(g.event)
    event = g.event;
    if ~isstr(event)
        if size(event,2) > size(event,1), event = event'; end;
        if iscell(event)
            eventStr = cellfun(@ischar, event);
            eventNum = cellfun(@isnumeric, event);
            if all(eventStr(1,:)) && ~all(eventStr(:,1))
                event = event';
            end;
            if all(eventNum(1,:)) && ~all(eventNum(:,1))
                event = event';
            end;
        end;
    end;
end;
g.event = event;

% determine latency for old event alignment
% -----------------------------------------
tmpalign    = g.align;
g.align     = [];
g.align.val = tmpalign;
if ~isnan(g.align.val)
    if isempty(oldevent)
        error('Setevent: no pre-existing event, cannot perform alignment');
    end;
    if ~isfield(oldevent, 'latency')
        error('Setevent: pre-existing events do not have a latency field for re-alignment');
    end;
    switch g.append
        case {'yes' '''yes'''}, disp('Setevent warning: cannot align and append events at the same time; disabling event alignment');
    end;
    if g.align.val < 0
        g.align.event = oldevent(1).latency;
    else
        g.align.event = oldevent(g.align.val+1).latency;
    end
    g.align.nbevent = length(oldevent);
    g.oldevents = oldevent;
    g.align.txt = sprintf([ 'Check alignment between pre-existing (old) and loaded event' ...
                          ' latencies:\nOld event latencies (10 first): %s ...\n' ], ...
                          int2str([ oldevent(1:min(10, length(oldevent))).latency ]));
else
    g.oldevents = [];
end;

tmpfields = fieldnames(g);
event = oldevent;
%% scan all the fields of g
% ------------------------
for curfield = tmpfields'
    if ~isempty(event), allfields = fieldnames(event);
    else                    allfields = {}; end;
    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''' 'no' } % ''no'' for backward compatibility
                      if isstr(g.event) && ~exist(g.event), g.event = evalin('caller', g.event); end;
                      event = load_file_or_array( g.event, g.skipline, g.delim );
                      allfields = g.fields(1:min(length(g.fields), size(event,2)));
                      event = eeg_eventformat(event, 'struct', allfields);
					  % generate ori fields
					  % -------------------
                      if ~isnan(g.timeunit)
                          for index = 1:length(event)
                              event(index).init_index = index;
                              event(index).init_time  = event(index).latency*g.timeunit;
                          end;
                      end;
                      event = recomputelatency( event, 1:length(event), srate, ...
                                                    g.timeunit, g.align, g.oldevents, g.optimalign, g.optimmeas);
                case { '''yes''' 'yes' }
                      % match existing fields
                      % ---------------------
                      if isstr(g.event) && ~exist(g.event), g.event = evalin('caller', g.event); end;
                      tmparray = load_file_or_array( g.event, g.skipline, g.delim );
                      if isempty(g.indices) g.indices = [1:size(tmparray,1)] + length(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)
                          event = setstruct( event, g.fields{eventfield}, g.indices, { tmparray{:,eventfield} });
                      end;
					  % generate ori fields
					  % -------------------
                      offset = length(event)-size(tmparray,1);
                      for index = 1:size(tmparray,1)
                          event(index+offset).init_index = index;
                          event(index+offset).init_time  = event(index+offset).latency*g.timeunit;
                      end;
                      event = recomputelatency( event, g.indices, srate, g.timeunit, ...
                                                    g.align, g.oldevents, g.optimalign, g.optimmeas);
            end;
      end;
end;

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

%% remove the events wit out-of-bound latencies
% --------------------------------------------
if isfield(event, 'latency')
    try
        res = cellfun('isempty', { event.latency });
        res = find(res);
        if ~isempty(res)
            fprintf( 'importevent warning: %d/%d event(s) have invalid latencies and were removed\n', ...
                     length(res), length(event));
            event( res ) = [];
        end;
    end;
end;

%% interpret the variable name
% ---------------------------
function array = load_file_or_array( varname, skipline, delim );
    if isstr(varname) & exist(varname) == 2  % mean that it is a filename
                                             % --------------------------
        array = loadtxt( varname, 'skipline', skipline, 'delim', delim, 'blankcell', 'off' );

    else
         if ~iscell(varname)
             array = mattocell(varname);
         else
             array = varname;
         end;
    end;
return;

%% update latency values
% ---------------------
function event = recomputelatency( event, indices, srate, timeunit, align, oldevents, optimalign, optimmeas)

    % update time unit
    % ----------------
    if ~isfield(event, 'latency'),
        if isfield(event, 'duration')
            error('A duration field cannot be defined if a latency field has not been defined');
        end;
        return;
    end;
    if ~isnan(timeunit)
        for index = indices
            event(index).latency  = event(index).latency*srate*timeunit;
            if isfield(event, 'duration')
                event(index).duration = event(index).duration*srate*timeunit;
            end;
        end;
    end;

    % alignment with old events
    % -------------------------
    if ~isnan( align.val )
        if align.val >= 0, alignlatency = event(1).latency;
        else               alignlatency = event(-align.val+1).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 events');
            disp('                  that were read, so their latencies may have been wrongly re-aligned');
        end;
        fprintf(align.txt);
        fprintf('New event latencies (10 first): %s ...\n', int2str(round([ event(1:min(10, length(event))).latency ])));
    end;
    if strcmpi(optimalign, 'on') & ~isempty(oldevents)
        newlat = [ event.latency     ];
        oldlat = [ oldevents.latency ];

        newlat = repmat(newlat, [length(oldlat) 1]);
        oldlat = repmat(oldlat', [1 size(newlat,2)]);
        if align.val >= 0
            newlat = newlat-newlat(1);
            oldlat = oldlat-oldlat(1+align.val);
        else
            newlat = newlat-newlat(1-align.val);
            oldlat = oldlat-oldlat(1);
        end;

        try
            newfactor = fminsearch('eventalign',1,[],newlat, oldlat, optimmeas);
        catch
            newfactor = fminsearch('eventalign',1,[],[],newlat, oldlat, optimmeas); % Octave
        end;
        fprintf('Best sampling rate ratio found is %1.7f. Below latencies after adjustment\n', newfactor);
        if newfactor > 1.01 | newfactor < 0.99
            disp('Difference is more than 1%, something is wrong; ignoring ratio');
            newfactor = 1;
        else
            difference1 = eventalign( 1        , newlat, oldlat, optimmeas);
            difference2 = eventalign( newfactor, newlat, oldlat, optimmeas);
            fprintf('The average difference before correction was %f sample points\n', difference1);
            fprintf('The average difference after correction is %f sample points\n', difference2);
        end;

        %diffarray = abs(newfactor*newlat-oldlat)';
        %[allmins poss] = min(diffarray);
        %figure; hist(allmins);
    else
        newfactor = 1;
    end;
    if ~isnan( align.val ) & newfactor ~= 1
        if align.val >= 0
            latfirstevent = event(1).latency;
        else
            latfirstevent = event(-align.val+1).latency;
        end;
        for index = setdiff_bc(indices, 1)
            event(index).latency = round(event(index).latency-latfirstevent)*newfactor+latfirstevent;
        end;
        if ~isempty(oldevents)
            fprintf('Old event latencies (10 first): %s ...\n', int2str(round([ oldevents(1:min(10, length(oldevents))).latency ])));
            fprintf('New event latencies (10 first): %s ...\n', int2str(round([ event(1:min(10, length(event))).latency ])));
        end;
    else
        % must add one (because first sample point has latency 0
        % ------------------------------------------------------
        if ~isnan(timeunit)
            for index = indices
                event(index).latency = round((event(index).latency+1)*1000*newfactor)/1000;
            end;
        end;
    end;

%% 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
    if ~isempty(values)
        for index = 1:length(indices)
            var = setfield(var, {indices(index)}, fieldname, values{index});
        end
    else
        for index = 1:length(indices)
            var = setfield(var, {indices(index)}, fieldname, '');
        end
    end
return;


function map = mrvColorMaps(name, nC, varargin)
%
% map = mrvColorMaps(name, [nColors=256]);
%
% Color maps used in mrVista.
%
% list = mrvColorMaps without input arguments
% returns a list of the currently-defined
% color maps.
%
% map = mrvColorMaps([name or num]) returns a 3x256
% color map corresponding to the name, or its index
% number in the list of all colormaps (useful for
% popup menus etc).
%
% map = mrvColorMaps('user') prompts for a user-defined
% colormap. This can be specified in one of two ways:
%   (1) Enter the # of colors to use. A prompt pops up
%       with a GUI to edit manually each of the colors.
%       You can also invoke the matlab CMAPEDITOR on this
%       for further options.
%
%   (2) Save a matlab file which contains the variable
%       'cmap'. At the dialog, leave the # of colors empty.
%       A second dialog will appear prompting for this file.
%
% map = mrvColorMaps([name or num], nC) returns a
% 3xnColors preset color map.
%
% For the case 'redgreenblue', you can specify an optional 3rd argument,
% gamma, which specifies the sharpness of the color transitions. (gamma > 1
% is sharper, gamma==1 is linear, gamma < 1 is more gradual).
%
% mrvColorMaps('demo') shows a set of all current colormaps in a figure.
%
% ras 07/05.
% ras 10/05: added support for external color maps.
% ras 05/08: added 'short HSV' color map, support for MATLAB expression
if nargin==0
	% output a list of names
	map = {'hot' 'cool' 'jet' 'hsv' 'hsvshort' 'gray' 'autumn' 'winter'...
		'red' 'green' 'blue' 'yellow' 'cyan' 'magenta' ...
		'red binary' 'green binary' 'blue binary' ...
		'redgreenblue' 'redyellowblue' 'greenbluered' ...
		'redgreenblueyellow' 'redmagentablue' ...
		'blueredyellow' 'greenred' ...
		'revjet' 'bluered' 'coolhot' 'user'};
	return
end

if ~exist('nC','var') || isempty(nC), nC = 256; end

if isnumeric(name)
	% convert from index # into name
	list =  mrvColorMaps;
	name = list{name};
end

map = zeros(nC,3);
switch lower(name)
	case 'hot', map = hot(nC);
	case 'cool', map = cool(nC);
	case 'jet', map = jet(nC);
	case 'hsv', map = hsv(nC);
	case 'hsvshort', map = circshift( hsv(round(nC*1.4)), 10 ); map = map(1:nC,:);
	case 'gray', map = gray(nC);
	case 'autumn', map = autumn(nC);
	case 'winter', map = winter(nC);
	case 'red binary', map(:,1) = 1;
	case 'green binary', map(:,2) = 1;
	case 'blue binary', map(:,3) = 1;
	case 'red', map(:,1) = linspace(0, 1, nC);
	case 'green', map(:,2) = linspace(0, 1, nC);
	case 'blue',  map(:,3) = linspace(0, 1, nC);
	case 'yellow', map = mrvColorMaps('red',nC) + mrvColorMaps('green',nC);
	case 'cyan', map = mrvColorMaps('blue',nC) + mrvColorMaps('green',nC);
	case 'magenta', map = mrvColorMaps('red',nC) + mrvColorMaps('blue',nC);

	case {'redgreen', 'greenred'},
		map(:,1) = linspace(0.2,1,nC);
		map(:,2) = linspace(1,0.2,nC);

	case {'redgreenblue', 'rgb'},
		% do some gamma adjust so the color trabsitions are more prominent
		% gamma coefficient (<1 = sharp, 1=linear, >1 = gradual)
		if ~isempty(varargin)
			gamma = varargin{1};
		else
			gamma = 2.8;
		end

		% make ramps for each color
		r = mrvColorMaps('red', nC) .^ gamma;
		g = [mrvColorMaps('green', ceil(nC/2)) .^ gamma] .* 0.7;
		g = [g; flipud(g)]; g = g(1:nC,:);
		b = flipud( mrvColorMaps('blue', nC) ) .^ gamma;
		map = r + g + b;
		map = map(1:nC,:);

	case {'redyellowblue', 'ryb'}
		% complement to rgb color map, for representing the ipsilateral
		% visual field.

		% do some gamma adjust so the color trabsitions are more prominent
		% gamma coefficient (<1 = sharp, 1=linear, >1 = gradual)
		if ~isempty(varargin)
			gamma = varargin{1};
		else
			gamma = 2.8;
		end

		% make ramps for each color
		r = mrvColorMaps('red', nC) .^ gamma;
		y = [mrvColorMaps('yellow', ceil(nC/2)) .^ gamma] .* 0.7;
		y = [y; flipud(y)]; y = y(1:nC,:);
		b = flipud( mrvColorMaps('blue', nC) ) .^ gamma;
		map = r + y + b;
		map = map(1:nC,:);

	case {'pinwheel' 'rgb_alt' 'redgreenblueyellow' 'rgby'}
		% an alternate version of the RGB color map above: still trying to
		% keep (red, green, blue) mapping to (down, horizontal, up) in
		% representing polar angle, but with an accommodation for
		% representing activations at ipsilateral polar angles as well.
		n = ceil(nC/4); m = nC - 2*n;
		map = [ mrvColorMaps('blue', n); ...
			flipud(mrvColorMaps('hsvshort', m)); ...
			flipud(mrvColorMaps('red', n)) ];
% 		n = ceil(nC/2); m = nC - n;
% 		map = [ mrvColorMaps('rgb', n); ...
% 				flipud(mrvColorMaps('ryb', m)) ];

	case {'greenbluered', 'gbr'},
		% do some gamma adjust so the color trabsitions are more prominent
		if ~isempty(varargin)
			gamma = varargin{1};
		else
			gamma = 2.8;  % gamma coefficient (<1 = sharp, 1=linear, >1 = gradual)
		end

		g = flipud( mrvColorMaps('green', nC) ) .^ gamma;
		b = mrvColorMaps('blue', ceil(nC/2)) .^ gamma;
		b = [b; flipud(b)]; b = b(1:nC,:);
		r = mrvColorMaps('red', nC) .^ gamma;    % make ramps for each color

		I = floor(nC/3):ceil(2*nC/3);

		map = r + g + b;

		map = map(1:nC,:);

	case {'redmagentablue', 'rmb', 'redblue', 'rb'},
		map(:,1) = linspace(1, 0, nC);
		map(:,2) = zeros(nC, 1);
		map(:,3) = linspace(0, 1, nC);

	case {'blueredyellow' 'bry' 'rby' 'redblueyellow'}
		r = linspace(0,1,nC/2)';
		z = zeros(nC/2,1);
		o = ones(nC/2,1);
		map = [[r;o] [z;r] flipud([z;r])];

	case {'revjet' 'reversejet'}
		map = 1 - jet(nC);

	case {'bluered' 'corr'}
		% blue-red colormap which works well for correlation coefficients
		blue = flipud( mrvColorMaps('blue', ceil(nC/2)) );
		red = mrvColorMaps('red', nC - ceil(nC/2));
		map = [blue; red];

	case {'coolhot' 'blueredmagenta' 'corr2'}
		% modified version of the above correlation colormap:
		% saturates to magenta at both ends
		nQ = ceil(nC / 4);  % one quarter of the cmap

		mag1 = [ zeros(1, nQ)' linspace(1, 0, nQ)' ones(1, nQ)'];
		blue = flipud( mrvColorMaps('blue', nQ) );
		red = mrvColorMaps('red', nC - 3*nQ);
		mag2 = [ones(1, nQ)' linspace(0, 1, nQ)' zeros(1, nQ)'];

		map = [mag1; blue; red; mag2];

	case 'user', map = mrvUserColormap;

	case 'demo', % show a demo of the available color maps
		map = mrvColorMaps;
		figure('Name', 'Color Maps in mrvColorMaps');
		for i = 1:27
			subplot(7, 4, i);
			image(ind2rgb(1:256, mrvColorMaps(i)));
			title(map{i});
			axis off;
		end
		return

	case 'expression' % let user input a MATLAB expression
		msg = ['Enter a MATLAB expression for the color map. ' ...
			'The color map should be a matrix of 3 x ncolors, ' ...
			'with values from 0 - 255. ' ...
			'You can directly enter the expression, or assign ' ...
			'it to a variable named "map". ' ...
			'E.g.:    [gray(128); hot(128)]; ' ...
			'or:      tmp = hsv(256);  map = tmp(1:100,:); '];
		def = sprintf('gray(%i); ', nC);
		resp = inputdlg({msg}, mfilename, 4, {def});
		if ~isempty(strfind(resp{1}, '=')) && ...
			~isempty(strfind(resp{1}, 'map'))
			% presume that the expression is assigning a value to the
			% 'map' variable:
			eval(resp{1})
		else
			% the map assignment is implied
			map = eval(resp{1});
		end

	otherwise
		warning('Invalid name specified. Returning empty color map');
end

% bounds check
map(map>1) = 1; map(map<0) = 0;

return
% /-------------------------------------------------------------------/ %

% /-------------------------------------------------------------------/ %
function map = mrvUserColormap;
% Use the colormap editor to get a colormap
%
% map = mrvUserColormap;
%
% ras 10/2005.
q='Enter # of colors in new colormap (leave empty to load from file)';
resp = inputdlg(q, 'mrvColormaps', 1, {'256'});
if isempty(resp{1})
	% load from file
	p = 'Select a .mat file with the colormap as a ''cmap'' variable.';
	[f pth] = uigetfile('*.mat',p);
	try
		load(fullfile(pth,f),'cmap');
		map = cmap;
	catch
		msg='Can''t find file w/ cmap variable, returning empty cmap.';
		warning(msg); map=[]; return
	end

else
	nC = str2num(resp{1});
	cbar = cbarEdit( cbarDefault(hot(nC)) );
	map = cbar.cmap;

end

return

% old cmaps
%     case 'selectivity'   % for AdaptNSelect experiment (4 colors), gum
%         colors = {[1 0 0] [1 1 0] [0 1 1] [0 0 1]};
%         colPerCond = nC / length(colors);
%         map = [];
%         for i = 1:length(colors)
%             for j = 1:colPerCond
%                 col = colors{i};
%                 w = 0.3 + 0.5*(j-1) / colPerCond; % weight of color
%                 map(end+1,:) = (w*col+ ((colPerCond-j+1) / (colPerCond))*[.7 .7 .7]);
%             end
%         end
%
%         % shift so that, when this is mapped to an integer range,
%         % integer values (like 1.0) will map to the max of the previous
%         % range, not the first of the next range -- also add black:
%         map = [0 0 0; map(1:nC-1,:)];