function varargout = dirwalk(topPath, visitor, recursionLimit, varargin)
%DIRWALK Generate the file names in a directory tree by walking the tree
%
% Description:
%   Function DIRWALK generates the file names in a directory tree by walking the tree
%   top-down. For each directory in the tree rooted at directory topPath.
%   For each directory of tree you can call "Visitor Function" for files processing.
%
%
% Using:
%   [pathNames, dirNames, fileNames] = dirwalk(topPath)
%   dirwalk(topPath, visitor)
%   dirwalk(topPath, visitor, varargin)
%   varargout = dirwalk(topPath, visitor, varargin)
%   [visitorOutput1, visitorOutput2, ..., visitorOutputN] = dirwalk(topPath, visitor)
%   [...] = dirwalk(topPath, visitor, visitorInput1, visitorInput2, ..., visitorInputN)
%   [...] = dirwalk(topPath, visitor, varargin)
%c
%
% Input:
%   topPath -- (Required) Top path name (Root path name)
%
%   visitor -- (Optional) Function handle. The function will be called
%                         when visiting each directory of tree.
%
%   Signatures of visitor function:
%   	visitor(pathName, DirListing)
%       visitor(pathName, DirListing, in1, in2, ..., inN)
%       visitor(pathName, DirListing, varargin)
%       varargout = visitor(...)
%      	[out1, out2, ..., outN] = visitor(...)
%
%   Input arguments:
%       pathName   -- (Required) Path to visited directory. String.
%                                (passed within DIRWALK)
%
%       DirListing -- (Required) Visited directory listing.
%                                Array of structs output of function DIR.
%                                (passed within DIRWALK)
%
%       varargin   -- (Optional) Other input arguments. (Passed outside)
%
%
% Output:
%   dirPaths  -- (Default visitor output) Visited path names. Cell array of strings.
%   dirNames  -- (Default visitor output) Directory names in visited paths (without '.' and '..'). Cell array of cell arrays.
%   fileNames -- (Default visitor output) File names in visited paths. Cell array of cell arrays.
%   visitorOutputs -- Visitor function outputs
%
%
% Examples:
%   topPath = fullfile(matlabroot, 'toolbox', 'matlab', 'demos');
%
%   [pathNames, dirNames, fileNames] = dirwalk(topPath);
%
%   dirwalk(topPath, @(y,x) disp(strcat(y, filesep, {x.name}')))
%
%
% See Also DIR, LS
%

% -------------------------------------------------------------------------
%   Version   : 1.1
%   Author    : Evgeny Pr aka iroln <esp.home@gmail.com>
%   Created   : 10.10.10
%   Updated   : 03.07.11
%
%   Copyright : Evgeny Prilepin (c) 2010-2011
% -------------------------------------------------------------------------

error(nargchk(1, Inf, nargin));

if (nargin < 2)
    error(nargoutchk(0, 3, nargout));
    recursionLimit = Inf;
    visitor = @default_visitor;
    visitorNumOutputs = 3;
    isUseDefaultVisitor = true;
elseif nargin < 3
    recursionLimit = Inf;
else
    visitorNumOutputs = nargout;
    isUseDefaultVisitor = false;
end

validateattributes(topPath, {'char'}, {'row'}, ...
    mfilename('fullpath'), '"Top Path Name"', 1)

if ~isempty(visitor)
    validateattributes(visitor, {'function_handle'}, {'scalar'}, ...
        mfilename('fullpath'), '"Visitor Function Handle"', 2)
end

varargout = dir_tree_helper(topPath, ...
    visitor, visitorNumOutputs, isUseDefaultVisitor, recursionLimit, varargin{:});
%--------------------------------------------------------------------------

%==========================================================================
function visitorOutputs = dir_tree_helper(topPath, visitorHandle, ...
    visitorNumOutputs, isUseDefaultVisitor, recursionLimit, varargin)
%DIR_TREE_HELPER Helper function for get directory tree

% Allocate memory for tree listing
preallocDirItems = 100000;
outputs = cell(preallocDirItems, visitorNumOutputs);
counter = 1;
depth = 0;

% Get tree listing
[counter, outputs] = dir_tree_listing(topPath, counter, depth, outputs, ...
    visitorHandle, visitorNumOutputs, isUseDefaultVisitor, recursionLimit, varargin{:});

% Remove extra
if (counter < preallocDirItems)
    outputs(counter+1:end, :) = [];
end

% Construct visitor function outputs
visitorOutputs = cell(1, visitorNumOutputs);
for i = 1:visitorNumOutputs
    visitorOutputs(i) = {outputs(:,i)};
end
%--------------------------------------------------------------------------

%==========================================================================
function [counter, outputs] = dir_tree_listing(topPath, counter, depth, outputs, ...
    visitorHandle, visitorNumOutputs, isUseDefaultVisitor, recursionLimit, varargin)
%DIR_TREE_LISTING Generate dir tree listing

% Get listing of current directory
Listing = dir(topPath);

% remove .* hidden directories here
for i =1:length(Listing)
    if strcmp(Listing(i).name(1),'.') & length(Listing(i).name)>2
       bad(i) = 1;
    else
        bad(i) = 0;
    end
end
if ~isempty(Listing)
Listing(find(bad)) = [];
end
% Call Visitor function
cwd = pwd;
cd(topPath)
disp(varargin)
visitorOutputs = visitor_call_helper(topPath, Listing, ...
    visitorHandle, visitorNumOutputs, varargin{:});
cd(cwd);

if isUseDefaultVisitor
    dirNames = visitorOutputs{2};
else
    dirNames = get_dir_file_names(Listing);
end

if (visitorNumOutputs > 0)
    outputs(counter,:) = visitorOutputs;
end

% Recursive walking directories in current directory
for i = 1:length(dirNames)
    nextRootPath = fullfile(topPath, dirNames{i});
    if depth + 1 <= recursionLimit
        [counter, outputs] = dir_tree_listing(nextRootPath, counter+1, depth+1, outputs, ...
            visitorHandle, visitorNumOutputs, isUseDefaultVisitor, recursionLimit, varargin{:});
    end
end

%--------------------------------------------------------------------------

%==========================================================================
function visitorOutputs = visitor_call_helper(topPath, Listing, ...
    visitor, numOutputs, varargin)
%VISITOR_CALL_HELPER Helper function for call visitor function

if (numOutputs == 0)
    % Visitor function without output arguments
    visitorOutputs = {};
%     visitor(topPath, Listing, varargin{:});

    visitor();
%     visitorCalling = sprintf('[] = visitor(topPath, Listing, varargin{:});');
%     eval(visitorCalling);
else
    % Construct eval command for call visitor function with any number output arguments
    outputNumbers = num2cell(1:numOutputs);

    %FIXME: It is not recommended, but otherwise doesn't work
    outputArgs = deblank(sprintf('outputs{%d} ', outputNumbers{:}));
    visitorCalling = sprintf('[%s] = visitor(topPath, Listing, varargin{:});', outputArgs);
    eval(visitorCalling);

    visitorOutputs = outputs;
end
%--------------------------------------------------------------------------

%==========================================================================
function [pathName, dirNames, fileNames] = default_visitor(rootPath, Listing)
%DEFAULT_VISITOR Default Visitor function
%
% Default visitor function return 3 output arguments:
%   pathNames -- visited paths names
%   dirNames  -- Directories names in visited directories
%   fileNames -- Files names in visited directories
%

pathName = rootPath;
[dirNames, fileNames] = get_dir_file_names(Listing);
%--------------------------------------------------------------------------

%==========================================================================
function [dirNames, fileNames] = get_dir_file_names(Listing)
%GET_DIR_FILE_NAMES

names = {Listing.name}';
isDirs = [Listing.isdir];
dirNames = names(isDirs);
fileNames = names(~isDirs);

% Exclude special directories '.' and '..'
inds = ~strcmp(dirNames, '.') & ~strcmp(dirNames, '..');
dirNames = dirNames(inds);
%--------------------------------------------------------------------------


function sDest = struct_copy_fields(sDest, sSrc, override)
% STRUCT_COPY_FIELDS: Copy the fields from sSrc structure to sDest structure

% @=============================================================================
% 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: Sylvain Baillet, March 2002

if (nargin < 3) || isempty(override)
    override = 1;
end

% No fields to add
if isempty(sSrc)
    return
% No fields in destination structure
elseif isempty(sDest)
    sDest = sSrc;
% Fields in both structures
else
    namesSrc = fieldnames(sSrc);
    for i = 1:length(namesSrc)
        if override || ~isfield(sDest, namesSrc{i})
            sDest.(namesSrc{i}) = sSrc.(namesSrc{i});
        end
    end
end


classdef CoreStack < handle
    % CoreStack. A class for storing values on a stack
    %
    %
    %     Licence
    %     -------
    %     Part of CoreMat. https://github.com/tomdoel/coremat
    %     Author: Tom Doel, 2013.  www.tomdoel.com
    %     Distributed under the MIT licence. Please see website for details.
    %

    properties (Access = private)
        Stack
    end

    methods
        function obj = CoreStack(stack_items)
            if nargin > 0
                obj.Stack = CoreContainerUtilities.ConvertToSet(stack_items);
            else
                obj.Stack = cell.empty;
            end
        end

        function Push(obj, stack_items)
            % Places one or more items on the stack. stack_items can be a single
            % object, a set or an array of objects
            obj.Stack = [obj.Stack CoreContainerUtilities.ConvertToSet(stack_items)];
        end

        function item = Pop(obj)
            % Retrieves the item from the top of the stack
            item = obj.Stack{end};
            obj.Stack(end) = [];
        end

        function is_empty = IsEmpty(obj)
            % Returns true if there are no items in the stack
            is_empty = isempty(obj.Stack);
        end

        function all_values = GetAndClear(obj)
            % Clears the stack and returns any remaining values in a set
            all_values = obj.Stack;
            obj.Stack = [];
        end

        function field_values = GetField(obj, field_name)
            % Returns the values of the field field_name from every item in the
            % stack
            field_values = CoreContainerUtilities.GetFieldValuesFromSet(obj.Stack, field_name);
        end
    end
end


% Function to get all files only under specific directory
% (including subdirectories)
%
% ref: stackoverflow
%      2652630/how-to-get-all-files-under-a-specific-directory-in-matlab

function fileList = getAllFiles(dirName,fileExtension)

  dirData = dir([dirName '/' fileExtension]);      %# Get the data for the current directory
  dirIndex = [dirData.isdir];  %# Find the index for directories
  fileList = {dirData(~dirIndex).name}';  %'# Get a list of the files
  if ~isempty(fileList)
    fileList = cellfun(@(x) fullfile(dirName,x),...  %# Prepend path to files
                       fileList,'UniformOutput',false);
  end
  subDirs = {dirData(dirIndex).name};  %# Get a list of the subdirectories
  validIndex = ~ismember(subDirs,{'.','..'});  %# Find index of subdirectories
                                               %#   that are not '.' or '..'
  for iDir = find(validIndex)                  %# Loop over valid subdirectories
    nextDir = fullfile(dirName,subDirs{iDir});    %# Get the subdirectory path
    fileList = [fileList; getAllFiles(nextDir,fileExtension)];  %# Recursively call getAllFiles
  end

end


% FIGURE Create figure window.
%    FIGURE, by itself, creates a new figure window, and returns
%    its handle.
%
%    FIGURE(H) makes H the current figure, forces it to become visible,
%    and raises it above all other figures on the screen.  If Figure H
%    does not exist, and H is an integer, a new figure is created with
%    handle H.
%
%    GCF returns the handle to the current figure.
%
%    Execute GET(H) to see a list of figure properties and
%    their current values. Execute SET(H) to see a list of figure
%    properties and their possible values.
%
%    See also SUBPLOT, AXES, GCF, CLF.
%
%    Reference page in Doc Center
%       doc figure
%
%


neuron.A(:, IDs_last) = neuron_bk.A(:, IDs_last);
neuron.C(IDs_last,:) = neuron_bk.C(IDs_last,:);
neuron.C_raw(IDs_last,:) = neuron_bk.C_raw(IDs_last,:);
ind_del(IDs_last) = false;

axes(ax_merged); cla;
axes(ax_merged_trace); cla;

IDs = IDs_last;


% pop_studydesign() - create a STUDY design structure.
%
% Usage:
%   >> [STUDY, ALLEEG] = pop_studydesign(STUDY, ALLEEG, key1, val1, ...);
%
% Inputs:
%   STUDY      - EEGLAB STUDY set
%   ALLEEG     - vector of the EEG datasets included in the STUDY structure
%
% Optional inputs:
%
% Authors: Arnaud Delorme, April 2010

% Copyright (C) Arnaud Delorme & Scott Makeig, SCCN/INC/UCSD, October 11, 2004, smakeig@ucsd.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

function [STUDY allcom] = pop_studydesign(STUDY, ALLEEG, designind, varargin);

allcom = '';
if nargin < 2
    help pop_studydesign;
    return;
end;

if nargin < 3 && ~isstr(STUDY)

    %% create GUI
    [ usrdat.factors usrdat.factorvals usrdat.factsubj] = std_getindvar(STUDY, 'both', 1);

    usrdat.factors     = { 'None' usrdat.factors{:} };
    usrdat.factorvals  = { {}     usrdat.factorvals{:} };
    usrdat.factsubj    = { {}     usrdat.factsubj{:} };
    usrdat.subjects    = STUDY.subject;
    usrdat.datasetinfo = STUDY.datasetinfo;
    usrdat.design      = STUDY.design;
    usrdat.filepath    = STUDY.filepath;
    for ind = 1:length(usrdat.design)
        usrdat.design(ind).deletepreviousfiles = 0;
    end;

    % build menu
    popupselectsubj = { 'Select all subjects' };
    for ind1 = 1:length(usrdat.factors)
        if ~isempty(usrdat.factsubj{ind1})
            if any(cellfun(@length, usrdat.factsubj{ind1}) ~= length(usrdat.subjects))
                for ind2 = 1:length(usrdat.factorvals{ind1})
                    if ~iscell(usrdat.factorvals{ind1}{ind2}) % not a combined value
                        tmpval = encodevals(usrdat.factorvals{ind1}(ind2));
                        popupselectsubj{end+1} = [ num2str(usrdat.factors{ind1}) ' - ' tmpval{1} ];
                    end;
                end;
            end;
        end;
    end;

    cb_rename = 'pop_studydesign(''rename'', gcbf);';
    cb_add    = 'pop_studydesign(''add'', gcbf);';
    cb_del    = 'pop_studydesign(''del'', gcbf);';
    cb_listboxfact1 = 'pop_studydesign(''selectfact'', gcf, 0);';
    cb_listboxfact2 = 'pop_studydesign(''selectfact'', gcf, 1);';
    cb_selectsubj   = 'pop_studydesign(''selectsubj'', gcbf);';
    cb_combinevals1 = 'pop_studydesign(''combinevals'', gcbf, 0);';
    cb_combinevals2 = 'pop_studydesign(''combinevals'', gcbf, 1);';
    cb_lbval        = 'pop_studydesign(''updatedesign'', gcbf);';
    cb_selectdesign = 'pop_studydesign(''selectdesign'', gcbf);';
    cb_selectdata   = 'pop_studydesign(''selectdatatrials'', gcbf);';
    cb_selectfolder = 'pop_studydesign(''selectfolder'', gcbf);';
    cb_setfolder    = 'pop_studydesign(''updatedesign'', gcbf);';
    uilist = { { 'style' 'text'       'string' 'Select STUDY design' 'fontweight' 'bold' } ...
               { 'style' 'listbox'    'string' { usrdat.design.name } 'tag' 'listboxdesign' 'callback' cb_selectdesign 'value' STUDY.currentdesign } ...
               { 'style' 'pushbutton' 'string' 'Add design'    'callback' cb_add } ...
               { 'style' 'pushbutton' 'string' 'Rename design' 'callback' cb_rename } ...
               { 'style' 'pushbutton' 'string' 'Delete design' 'callback' cb_del } ...
               { 'style' 'text'       'string' 'Subjects' 'fontweight' 'bold' } ...
               { 'style' 'text'       'string' 'Independent variable 1   ' 'fontweight' 'bold' } ...
               { 'style' 'text'       'string' 'Independent variable 2   ' 'fontweight' 'bold' } ...
               { 'style' 'listbox'    'string' usrdat.subjects 'tag' 'lbsubj' 'min' 0 'max' 2 'value' 1 'callback' cb_lbval } ...
               { 'style' 'listbox'    'string' usrdat.factors  'tag' 'lbfact0' 'callback' cb_listboxfact1 'value' 2 } ...
               { 'style' 'listbox'    'string' usrdat.factors  'tag' 'lbfact1' 'callback' cb_listboxfact2 'value' 1 } ...
               { 'style' 'text'       'string' 'Ind. var. 1 values ' } ...
               { 'style' 'text'       'string' 'Ind. var. 2 values' } ...
               { 'style' 'listbox'    'string' '' 'tag' 'lbval0' 'min' 0 'max' 2 'callback' cb_lbval } ...
               { 'style' 'listbox'    'string' '' 'tag' 'lbval1' 'min' 0 'max' 2 'callback' cb_lbval } ...
               { 'style' 'popupmenu'  'string' popupselectsubj 'tag' 'popupselect' 'callback' cb_selectsubj } ...
               { 'style' 'pushbutton' 'string' 'Combine selected values' 'tag' 'combine1' 'callback' cb_combinevals1 } ...
               { 'style' 'pushbutton' 'string' 'Combine selected values' 'tag' 'combine2' 'callback' cb_combinevals2 } ...
               { 'style' 'popupmenu'  'string' 'Paired statistics|Unpaired statistics' 'tag' 'lbpair0' 'callback' cb_lbval } ...
               { 'style' 'popupmenu'  'string' 'Paired statistics|Unpaired statistics' 'tag' 'lbpair1' 'callback' cb_lbval } ...
               { 'style' 'pushbutton' 'string' 'Use only specific datasets/trials' 'callback' cb_selectdata } ...
               { 'style' 'edit'       'string' '' 'tag' 'edit_selectdattrials' 'callback' cb_lbval } ...
               { 'style' 'text'       'string' 'Store pre-computed files in folder' } ...
               { 'style' 'edit'       'string' '' 'tag' 'edit_storedir' 'callback' cb_setfolder } ...
               { 'style' 'pushbutton' 'string' '...' 'callback' cb_selectfolder } ...
               { 'style' 'checkbox'   'string' 'Delete all pre-computed datafiles associated with this specific STUDY design' 'tag' 'chk_del' 'callback' cb_lbval } ...
               { 'style' 'checkbox'   'string' 'Save the STUDY' 'tag' 'chk_save' 'value' 1 } };
%               { 'style' 'checkbox'  'string' 'Paired statistics' 'tag' 'lbpair0' 'callback' cb_lbval } ...
%               { 'style' 'checkbox'  'string' 'Paired statistics' 'tag' 'lbpair1' 'callback' cb_lbval } ...

    geometry = { {3 18 [1 1] [2 1] } ...
                 {3 18 [1 2] [2 3] } ...
                 {3 18 [3 2] [1 1] } ...
                 {3 18 [3 3] [1 1] } ...
                 {3 18 [3 4] [1 1] } ...
                 {3 18 [1 5] [1 1] } ...
                 {3 18 [2 5] [1 1] } ...
                 {3 18 [3 5] [1 1] } ...
                 {3 18 [1 6] [1 8] } ...
                 {3 18 [2 6] [0.98 3] } ...
                 {3 18 [3 6] [0.98 3] } ...
                 {3 18 [2 9] [1 1] } ...
                 {3 18 [3 9] [1 1] } ...
                 {3 18 [2 10] [0.98 3] } ...
                 {3 18 [3 10] [0.98 3] } ...
                 {3 18 [1 14] [1 1] } ...
                 {3 18 [2 13] [1 1] } ...
                 {3 18 [3 13] [1 1] } ...
                 {3 18 [2 14] [1 1] } ...
                 {3 18 [3 14] [1 1] } ...
                 {3 18 [1    15.5] [1.3 1] } ...
                 {3 18 [2.25 15.5] [1.7 1] } ...
                 {3 18 [1    16.5] [1.3 1] } ...
                 {3 18 [2.25 16.5] [1.2 1] } ...
                 {3 18 [3.45 16.5] [0.55 1] } ...
                 {3 18 [1 17.5] [3 1] } ...
                 {3 18 [1 19] [3 1] } ...
                 };

    for i = 1:length(geometry), geometry{i}{3} = geometry{i}{3}-1; end;
    streval = [ 'pop_studydesign(''selectdesign'', gcf);' ];
    [tmp usrdat tmp2 result] = inputgui('uilist', uilist, 'title', 'Edit STUDY design -- pop_studydesign()', 'helpbut', 'Web help', 'helpcom',  'web(''http://sccn.ucsd.edu/wiki/Chapter_03:_Working_with_STUDY_designs'', ''-browser'')', 'geom', geometry, 'userdata', usrdat, 'eval', streval);
    if isempty(tmp), return; end;

    % call std_makedesign
    % -------------------
    des    = usrdat.design;
    allcom = '';
    if length(des) < length(STUDY.design)
        for index = length(des)+1:length(STUDY.design)
            fprintf('Deleting STUDY design %d\n', index);
            com    = 'STUDY.design(index).name = '';'; eval(com);
            allcom = [ allcom 10 com ];
        end;
    end;
    for index = 1:length(des)
        tmpdes  = rmfield(des(index), 'deletepreviousfiles');
        rmfiles = fastif(des(index).deletepreviousfiles, 'limited', 'off');
        if index > length(STUDY.design) || ~isequal(STUDY.design(index), tmpdes) || strcmpi(rmfiles, 'on')
            fprintf('Updating/creating STUDY design %d\n', index);

            % test if file exist and issue warning
            if length(STUDY.design) >= index && isfield('cell', STUDY.design) && ~isempty(STUDY.design(index).cell) && ...
                ~isempty(dir([ STUDY.design(index).cell(1).filebase '.*' ])) &&  strcmpi(rmfiles, 'off')
                if ~isequal(tmpdes.variable(1).label, STUDY.design(index).variable(1).label) || ...
                     ~isequal(tmpdes.variable(2).label, STUDY.design(index).variable(2).label) || ...
                      ~isequal(tmpdes.include, STUDY.design(index).include) || ...
                       ~isequal(tmpdes.variable(1).value, STUDY.design(index).variable(1).value) || ...
                        ~isequal(tmpdes.cases.value, STUDY.design(index).cases.value) || ...
                         ~isequal(tmpdes.variable(2).value, STUDY.design(index).variable(2).value)
                    res = questdlg2(strvcat([ 'Precomputed data files exist for design ' int2str(index) '.' ], ' ', ...
                                       'Modifying this design without deleting the associated files', ...
                                       'might mean that they will stay on disk and will be unusable'), ...
                                       'STUDY design warning', 'Abort', 'Continue', 'Continue');
                    if strcmpi(res, 'Abort'), return; end;
                end;
            end;
            [STUDY com] = std_makedesign(STUDY, ALLEEG, index, tmpdes, 'delfiles', rmfiles);
            allcom = [ allcom 10 com ];
        else
            fprintf('STUDY design %d not modified\n', index);
        end;
    end;
    if result.listboxdesign ~= STUDY.currentdesign
        fprintf('Selecting STUDY design %d\n', result.listboxdesign);
        com = sprintf('STUDY = std_selectdesign(STUDY, ALLEEG, %d);', result.listboxdesign); eval(com);
        allcom = [ allcom 10 com ];
    end;
    if result.chk_save == 1
        fprintf('Resaving STUDY\n');
        [STUDY ALLEEG com] = pop_savestudy(STUDY, ALLEEG, 'savemode', 'resave');
        allcom = [ allcom 10 com ];
    end;
    if ~isempty(allcom), allcom(1) = []; end;

elseif isstr(STUDY)
    com = STUDY;
    fig = ALLEEG;
    usrdat = get(fig, 'userdata');
    datinfo  = usrdat.datasetinfo;
    des      = usrdat.design;
    filepath = usrdat.filepath;

    switch com
        % summary of callbacks
        % case 'add', Add new study design
        % case 'del', Delete study design
        % case 'rename', Rename study design
        %
        % case 'selectdesign', select a specific design
        % case 'updatedesign', update the study information (whenever the
        %                      user click on a button
        %
        % case 'selectfact', select a specific ind. var. (update value listboxes)
        % case 'combinevals', combine values in value listboxes
        % case 'selectsubj', select specific subjects
        %
        % case 'selectdatatrials', new GUI to select specific dataset and trials
        % case 'selectdatatrialssel', % select in the GUI above
        % case 'selectdatatrialsadd', % add new selection in the GUI above

        case 'add', % Add new study design
            inde  = find( cellfun(@isempty,{ des.name }));
            if isempty(inde), inde = length(des)+1; end;
            des(inde(1)) = des(1);
            des(inde(1)).name = sprintf('Design %d', inde(1));
            set(findobj(fig, 'tag', 'listboxdesign'), 'string', { des.name }, 'value', inde(1));
            usrdat.design = des;
        	set(fig, 'userdata', usrdat);
            pop_studydesign( 'selectstudy', fig);
            return;

        case 'del', % Delete study design
            val = get(findobj(fig, 'tag', 'listboxdesign'), 'value');
            if val == 1
                warndlg2('The first STUDY design cannot be removed, only modified');
                return;
            end;
            des(val).name = '';
            set(findobj(fig, 'tag', 'listboxdesign'), 'value', 1, 'string', { des.name } );
            usrdat.design = des;
        	set(fig, 'userdata', usrdat);
            pop_studydesign( 'selectstudy', fig);
            return;

        case 'rename', % Rename study design
            val        = get(findobj(fig, 'tag', 'listboxdesign'), 'value');
            strs       = get(findobj(fig, 'tag', 'listboxdesign'), 'string');
            result     = inputdlg2( { 'Study design name:                                                                    ' }, ...
                                      'Rename Study Design', 1,  { strs{val} }, 'pop_studydesign');
            if isempty(result), return; end;
            des(val).name  = result{1};
            set(findobj(fig, 'tag', 'listboxdesign'), 'string', { des.name } );

        case 'selectdesign', % select a specific design
            val  = get(findobj(fig, 'tag', 'listboxdesign'), 'value');
            if isempty(des(val).name)
                set(findobj(fig, 'tag', 'lbfact0'), 'string', '', 'value', 1);
                set(findobj(fig, 'tag', 'lbfact1'), 'string', '', 'value', 1);
                set(findobj(fig, 'tag', 'lbval0') , 'string', '', 'value', 1);
                set(findobj(fig, 'tag', 'lbval1') , 'string', '', 'value', 1);
                set(findobj(fig, 'tag', 'lbpair0'), 'value', 1);
                set(findobj(fig, 'tag', 'lbpair1'), 'value', 1);
                set(findobj(fig, 'tag', 'lbsubj') , 'value' , 1, 'string', '');
                set(findobj(fig, 'tag', 'chk_del'), 'value', des(val).deletepreviousfiles );
                set(findobj(fig, 'tag', 'edit_selectdattrials'), 'string', '' );
                % do not change file path
                return;
            end;
            val1 = strmatch(des(val).variable(1).label, usrdat.factors, 'exact'); if isempty(val1), val1 = 1; end;
            val2 = strmatch(des(val).variable(2).label, usrdat.factors, 'exact'); if isempty(val2), val2 = 1; end;
            set(findobj(fig, 'tag', 'lbfact0'), 'string', usrdat.factors, 'value', val1);
            set(findobj(fig, 'tag', 'lbfact1'), 'string', usrdat.factors, 'value', val2);
            valfact1 = strmatchmult(des(val).variable(1).value, usrdat.factorvals{val1});
            valfact2 = strmatchmult(des(val).variable(2).value, usrdat.factorvals{val2});
            if isempty(valfact1), listboxtop1 = 1; else listboxtop1 = valfact1(1); end;
            if isempty(valfact2), listboxtop2 = 1; else listboxtop2 = valfact2(1); end;
            set(findobj(fig, 'tag', 'lbval0'), 'string', encodevals(usrdat.factorvals{val1}), 'value', valfact1, 'listboxtop', listboxtop1);
            set(findobj(fig, 'tag', 'lbval1'), 'string', encodevals(usrdat.factorvals{val2}), 'value', valfact2, 'listboxtop', listboxtop2);
            valsubj = strmatchmult(des(val).cases.value, usrdat.subjects);
            set(findobj(fig, 'tag', 'lbsubj'), 'string', usrdat.subjects, 'value', valsubj);
            if isempty(des(val).include), str = ''; else str = vararg2str(des(val).include); end;
            set(findobj(fig, 'tag', 'chk_del'), 'value', des(val).deletepreviousfiles );
            set(findobj(fig, 'tag', 'edit_selectdattrials'), 'string', str );
            set(findobj(fig, 'tag', 'popupselect'), 'value', 1 );
            set(findobj(fig, 'tag', 'lbpair0'), 'value', fastif(isequal(des(val).variable(1).pairing,'on'),1,2));
            set(findobj(fig, 'tag', 'lbpair1'), 'value', fastif(isequal(des(val).variable(2).pairing,'on'),1,2));
            if ~isfield(des, 'filepath') || isempty(des(val).filepath), des(val).filepath = ''; end;
            set(findobj(fig, 'tag', 'edit_storedir'), 'string', des(val).filepath);

        case 'updatedesign', % update the study information (whenever the user click on a button)
            val    = get(findobj(fig, 'tag', 'listboxdesign'), 'value');
            val1   = get(findobj(fig, 'tag', 'lbfact0'), 'value');
            val2   = get(findobj(fig, 'tag', 'lbfact1'), 'value');
            valf1  = get(findobj(fig, 'tag', 'lbval0'),  'value');
            valf2  = get(findobj(fig, 'tag', 'lbval1'),  'value');
            valp1  = get(findobj(fig, 'tag', 'lbpair0'), 'value');
            valp2  = get(findobj(fig, 'tag', 'lbpair1'), 'value');
            vals   = get(findobj(fig, 'tag', 'lbsubj'),  'value');
            valchk = get(findobj(fig, 'tag', 'chk_del'), 'value');
            filep  = get(findobj(fig, 'tag', 'edit_storedir'), 'string');
            strs   = get(findobj(fig, 'tag', 'edit_selectdattrials'),  'string');
            valpaired = { 'on' 'off' };

            if ~strcmpi(des(val).variable(1).label, usrdat.factors{val1})
                des(val).variable(1).label = usrdat.factors{val1};
                des(val).variable(1).value = usrdat.factorvals{val1}(valf1);
            end;
            if ~strcmpi(des(val).variable(2).label, usrdat.factors{val2})
                des(val).variable(2).label = usrdat.factors{val2};
                des(val).variable(2).value = usrdat.factorvals{val2}(valf2);
            end;
            if ~isequal(mysort(des(val).variable(1).value), mysort(usrdat.factorvals{val1}(valf1)))
                des(val).variable(1).value = usrdat.factorvals{val1}(valf1);
            end;
            if ~isequal(mysort(des(val).variable(2).value), mysort(usrdat.factorvals{val2}(valf2)))
                des(val).variable(2).value = usrdat.factorvals{val2}(valf2);
            end;
            if ~isequal(mysort(des(val).cases.value), mysort(usrdat.subjects(vals)))
                des(val).cases.value = usrdat.subjects(vals);
            end;
            if ~isequal(des(val).variable(1).pairing, valpaired{valp1})
                des(val).variable(1).pairing = valpaired{valp1};
            end;
            if ~isequal(des(val).variable(2).pairing, valpaired{valp2})
                des(val).variable(2).pairing = valpaired{valp2};
            end;
            if ~isequal(des(val).deletepreviousfiles, valchk)
                des(val).deletepreviousfiles = 1;
            end;
            if ~isfield(des, 'filepath') || ~isequal(des(val).filepath, filep)
                des(val).filepath = filep;
            end;
            if ~isequal(des(val).include, strs)
                try,
                    des(val).include = eval( [ '{' strs '}' ]);
                catch,
                    disp('Error while decoding list of parameters');
                    des(val).include = {};
                    set(findobj(fig, 'tag', 'edit_selectdattrials'),  'string', '');
                end;
            end;

        case 'selectfact', % select a specific ind. var. (update value listboxes)
            factval = designind;
            val   = get(findobj(fig, 'tag', 'listboxdesign'), 'value');
            val1  = get(findobj(fig, 'tag', [ 'lbfact' num2str(factval) ]), 'value');
            val2  = get(findobj(fig, 'tag', [ 'lbfact' num2str(~factval) ]), 'value');
%             if val1 == val2 && val1 ~= 1
%                 warndlg2('Cannot select twice the same independent variable');
%                 val1 = 1;
%                 set(findobj(fig, 'tag', [ 'lbfact' num2str(factval) ]), 'value', val1);
%             end;
            valfact = [1:length(usrdat.factorvals{val1})];
            set(findobj(fig, 'tag', ['lbval' num2str(factval) ]), 'string', encodevals(usrdat.factorvals{val1}), 'value', valfact, 'listboxtop', 1);
            pop_studydesign('updatedesign', fig);
            return;

        case 'combinevals', %  combine values in value listboxes
            factval = designind;
            val1    = get(findobj(fig, 'tag', [ 'lbfact' num2str(factval) ]), 'value');
            vals    = get(findobj(fig, 'tag', [ 'lbval'  num2str(factval) ]), 'value');
            strs    = get(findobj(fig, 'tag', [ 'lbval'  num2str(factval) ]), 'string');
            if length(vals) == 1
                warndlg2('You need to select several values to combine them');
                return;
            end;
            if ~iscell(usrdat.factorvals{val1})
                warndlg2('Cannot combine values from numerical variables');
                return;
            end;
            % combine values for string and integers
            if isstr(usrdat.factorvals{val1}{1}) || iscell(usrdat.factorvals{val1}{1})
                tmpcell = {};
                for indCell = vals(:)'
                    if iscell(usrdat.factorvals{val1}{indCell})
                        tmpcell = { tmpcell{:} usrdat.factorvals{val1}{indCell}{:} };
                    else
                        tmpcell = { tmpcell{:} usrdat.factorvals{val1}{indCell} };
                    end;
                end;
                usrdat.factorvals{val1}{end+1} = unique_bc(tmpcell);
            else
                usrdat.factorvals{val1}{end+1} = unique_bc([ usrdat.factorvals{val1}{vals} ]);
            end;
            set(findobj(fig, 'tag', ['lbval' num2str(factval) ]), 'string', encodevals(usrdat.factorvals{val1}));

        case 'selectsubj', % select specific subjects
            val = get(findobj(fig, 'tag', 'popupselect'), 'value');
            str = get(findobj(fig, 'tag', 'popupselect'), 'string');
            str = str{val};
            if val == 1,
                subjbox = get(findobj(fig, 'tag', 'lbsubj'), 'string');
                set(findobj(fig, 'tag', 'lbsubj'), 'value', [1:length(subjbox)]);
                return;
            end;
            indunders = findstr( ' - ', str);
            factor    = str(1:indunders-1);
            factorval = str(indunders+3:end);

            % select subjects
            eval( [ 'allsetvals = { datinfo.' factor '};' ]);
            indset = strmatch(factorval, allsetvals, 'exact');
            subjects = unique_bc( { datinfo(indset).subject } );

            % change the subject listbox
            val     = get(findobj(fig, 'tag', 'popupselect'), 'value');
            subjbox = get(findobj(fig, 'tag', 'lbsubj'), 'string');
            indsubj = [];
            for ind = 1:length(subjects);
                indsubj(ind) = strmatch(subjects{ind}, subjbox, 'exact');
            end;
            set(findobj(fig, 'tag', 'lbsubj'), 'value', indsubj);
            set(findobj(fig, 'tag', 'popupselect'), 'value', 1);
            pop_studydesign('updatedesign', fig);
            return;
            %set(findobj(get(gcbf, ''userdata''), ''tag'', ''edit_selectdattrials''

        case 'selectdatatrials', % select specific dataset and trials
            cb_sel = 'pop_studydesign(''selectdatatrialssel'',gcbf);';
            cb_add = 'pop_studydesign(''selectdatatrialsadd'',gcbf);';
            uilist = { { 'style' 'text'    'string' strvcat('Press ''Add'' to add data', 'selection. Multiple variables', 'are combined using AND.') } ...
                       { 'style' 'text'    'string' 'Select data based on variable', 'fontweight' 'bold' } ...
                       { 'style' 'listbox' 'string' usrdat.factors  'tag' 'lbfact2' 'callback' cb_sel 'value' 1 } ...
                       { 'style' 'text'    'string' 'Select data based on value(s)' 'fontweight' 'bold' } ...
                       { 'style' 'listbox' 'string' encodevals(usrdat.factorvals{1}) 'tag' 'lbval2' 'min' 0 'max' 2} };
            cb_renamehelp = [ 'set(findobj(gcf, ''tag'', ''help''), ''string'', ''Add'');' ...
                              'set(findobj(gcf, ''tag'', ''cancel''), ''string'', ''Erase'', ''callback'', ''set(findobj(''''tag'''', ''''edit_selectdattrials''''), ''''string'''', '''''''');'');' ...
                              'set(findobj(gcf, ''tag'', ''ok''), ''string'', ''Close'');' ];
            usrdat.fig = fig;
            inputgui('uilist', uilist, 'geometry', { [1] [1] [1] [1] [1] }, 'geomvert', [2 1 2.5 1 2.5], ...
                'helpcom', cb_add, 'userdata', usrdat, 'eval', cb_renamehelp);
            pop_studydesign('updatedesign', fig);
            return;

        case 'selectdatatrialssel', % select in the GUI above
            val1  = get(findobj(fig, 'tag', 'lbfact2'), 'value');
            valfact = [1:length(usrdat.factorvals{val1})];
            tmpval = get(findobj(fig, 'tag', 'lbval2'), 'value');
            if max(tmpval) > max(valfact)
                set(findobj(fig, 'tag', 'lbval2'), 'value', valfact, 'string', encodevals(usrdat.factorvals{val1}));
            else
                set(findobj(fig, 'tag', 'lbval2'), 'string', encodevals(usrdat.factorvals{val1}), 'value', valfact);
            end;
            return;

        case 'selectfolder',
            res = uigetdir;
            if ~isempty(findstr(filepath, res)) && findstr(filepath, res) == 1 && ~isequal(filepath, res)
                res = res(length(filepath)+2:end);
            end;
            if res(1) == 0, return; end;
            set(findobj(fig, 'tag', 'edit_storedir'), 'string', res);
            pop_studydesign('updatedesign', fig);
            return;

        case 'selectdatatrialsadd', % Add button in the GUI above
            val1    = get(findobj(fig, 'tag', 'lbfact2'), 'value');
            val2    = get(findobj(fig, 'tag', 'lbval2') , 'value');
            objedit = findobj(usrdat.fig, 'tag', 'edit_selectdattrials');
            str     = get(objedit, 'string');
            if ~isempty(str), str = [ str ',' ]; end;
            set(objedit, 'string', [ str vararg2str( { usrdat.factors{val1} usrdat.factorvals{val1}(val2) }) ]);
            return;

    end;
    usrdat.design = des;
	set(fig, 'userdata', usrdat);
end;

function res = strmatchmult(a, b);
    if isempty(b), res = []; return; end;
    res = zeros(1,length(a));
    for index = 1:length(a)
        tmpi = std_indvarmatch(a{index}, b);
        res(index) = tmpi(1); % in case there is a duplicate
    end;
    %[tmp ind] = mysetdiff(b, a);
    %res = setdiff_bc([1:length(b)], ind);

function cellarray = mysort(cellarray)
    return; % was crashing for combinations of selection
            % also there is no reason the order should be different
    if ~isempty(cellarray) && isstr(cellarray{1})
        cellarray = sort(cellarray);
    end;

function [cellout inds ] = mysetdiff(cell1, cell2);
    if (~isempty(cell1) && isstr(cell1{1})) || (~isempty(cell2) && isstr(cell2{1}))
         [ cellout inds ] = setdiff_bc(cell1, cell2);
    else [ cellout inds ] = setdiff_bc([ cell1{:} ], [ cell2{:} ]);
         cellout = mattocell(cellout);
    end;

% encode string an numerical values for list boxes
function cellout = encodevals(cellin)
    if isempty(cellin)
        cellout = {};
    elseif ~iscell(cellin)
        cellout = { num2str(cellin) };
    elseif ischar(cellin{1}) || iscell(cellin{1})
        for index = 1:length(cellin)
            if isstr(cellin{index})
                cellout{index} = cellin{index};
            else
                cellout{index} =  cellin{index}{1};
                for indcell = 2:length(cellin{index})
                    cellout{index} = [ cellout{index} ' & ' cellin{index}{indcell} ];
                end;
            end;
        end;
    else
        for index = 1:length(cellin)
            if length(cellin{index}) == 1
                cellout{index} = num2str(cellin{index});
            else
                cellout{index} =  num2str(cellin{index}(1));
                for indcell = 2:length(cellin{index})
                    cellout{index} = [ cellout{index} ' & ' num2str(cellin{index}(indcell)) ];
                end;
            end;
        end;
    end;


function fmdplot(obj, tabgrouptag)
    % control the FMD plot in the main window

    Tags.xs = 'fmd xs contextmenu';

    % the fmd object is actually stored in the axes into which it plots
    myTab = findOrCreateTab(obj.fig, obj.fig, tabgrouptag, 'FMD');
    ax=findobj(myTab,'Type','axes');
    if isempty(ax)
        ax=axes(myTab);
        ylabel(ax,'Cum # events > M(x)');
        xlabel(ax,'Magnitude');
        if ~isempty(obj.catalog)
            bdiffobj=bdiff2(obj.getCurrentCatalog,'ax',ax,'InteractiveMode',false);
            bAnalysisWin = AnalysisBvalues(ax,bdiffobj);
            ax.UserData = bAnalysisWin;
            % ax.UserData=bdiffobj; %stash this, but keep it with the ZMapMainWindow.
            uimenu(ax.UIContextMenu,'Label','Cut catalog at Mc','MenuSelectedFcn', @(s,v)crop_to_mc(s, v, bdiffobj) );
        end

    elseif isempty(obj.catalog)
        cla(ax);
        ylabel(ax,'Cum # events > M(x)');
        xlabel(ax,'Magnitude');
    else
        % axes exists, and the catalog is not empty

        bAnalysisWin = ax.UserData;
        if isempty(bAnalysisWin)
            bdiffobj=bdiff2(obj.getCurrentCatalog,'ax',ax,'InteractiveMode',false);
            bAnalysisWin = AnalysisBvalues(ax,bdiffobj);
            ax.UserData = bAnalysisWin;
            % ax.UserData=bdiffobj;
        else
            bAnalysisWin.bobj.RawCatalog = obj.catalog;
            bAnalysisWin.bobj.Calculate();
            bAnalysisWin.bobj.updatePlot();
        end

        if isempty(ax.UIContextMenu)
            c = uicontextmenu(obj.fig);
            ax.UIContextMenu = c;
        end
        addLegendToggleContextMenuItem(ax.UIContextMenu,'bottom','above');
    end

    analy_win = ax.UserData;

    %% plot & synchronize cross sections

    plotted_xs = get(ax.Children,'Tag');
    if ~isempty(plotted_xs)
        plotted_xs = plotted_xs(startsWith(plotted_xs, 'Xsection fmd'));
    end
    if ~isempty(plotted_xs)
        existing_xs = obj.XSectionTitles;
        plotted_xs2 = extractAfter(plotted_xs,'Xsection fmd ');

        toDelIdx = ~startsWith(plotted_xs2,existing_xs); % all tags
        todel = string(plotted_xs(toDelIdx));
        todel = todel(~endsWith(todel,'line') & ~endsWith(todel,'Mc'));

        % delete cross sections that shouldn't exist
        %todel = plotted_xs(~startsWith(plotted_xs,existing_xs));
        %todel = plotted_xs(~ismember(plotted_xs,existing_xs));
        analy_win.remove_series(todel);
    end

    %% if necessary, add context menu to figure
    cxs=findobj(obj.fig,'Tag',Tags.xs);
    if isempty(cxs)
        cxs=uicontextmenu(obj.fig,'tag',Tags.xs);
        uimenu(cxs,'Label','Open in new window','MenuSelectedFcn',@cb_xstimeplot);
    end

    obj.plot_xsections(@xsplotter, 'Xsection fmd');
    clear_empty_legend_entries(obj.fig);

    %%
    function h = xsplotter(xs, xscat)
        xsProps.LineWidth   = 1.5;
        xsProps.DisplayName = xs.Name;
        xsProps.Color       = xs.Color;
        xsProps.Marker = 'x';
        mytag               = ['Xsection fmd ' xs.Name];
        h = analy_win.add_series(xscat, mytag, xsProps);

    end

    function cb_xstimeplot(~,~)
        % CB_XSTIMEPLOT shows the TIMEPLOT for the currently selected cross-section
        myName = get(gco,'DisplayName');
        idx = strcmp(obj.XSectionTitles,myName);
        ZG=ZmapGlobal.Data;
        ZG.newt2=obj.catalog.subset(obj.CrossSections(idx).inside(obj.catalog));
        ZG.newt2.Name=sprintf('Events within %g km of %s',obj.CrossSections(idx).name);
    end

    function crop_to_mc(~,~, bdiffobj)
        % should this crop the raw catalog?
        zdlg = ZmapDialog();
        zdlg.AddEdit('mc', "Cut Magnitude [Mc:" + bdiffobj.Result.Mc_value + "]", bdiffobj.Result.Mc_value,...
            'Choose magnitude to cut the catalog');
        [res,okpressed] = zdlg.Create('Name', 'Choose Cut Magnitude');
        if okpressed
            obj.rawcatalog = obj.rawcatalog.subset(res.mc <= obj.rawcatalog.Magnitude);
            obj.CatalogManager.RawCatalog = obj.rawcatalog;
            obj.replot_all;
        end
    end
end


% mex CFLAGS="-O3 -march=nocona -Wall" -largeArrayDims mexSOSVMLearn.cpp LaRank.cpp Config.cpp
mex CFLAGS="-O3 -march=nocona -Wall" mexSOSVMLearn.cpp LaRank.cpp Config.cpp


function [selection, value] = seldellistdlg(varargin)
%LISTDLG  List selection dialog box.
%   [SELECTION,OK] = LISTDLG('ListString',S) creates a modal dialog box
%   which allows you to select a string or multiple strings from a list.
%   SELECTION is a vector of indices of the selected strings (length 1 in
%   the single selection mode).  This will be [] when OK is 0.  OK is 1 if
%   you push the OK button, or 0 if you push the Cancel button or close the
%   figure.
%
%   Double-clicking on an item or pressing <CR> when multiple items are
%   selected has the same effect as clicking the OK button.  Pressing <CR>
%   is the same as clicking the OK button. Pressing <ESC> is the same as
%   clicking the Cancel button.
%
%   Inputs are in parameter,value pairs:
%
%   Parameter       Description
%   'ListString'    cell array of strings for the list box.
%   'SelectionMode' string; can be 'single' or 'multiple'; defaults to
%                   'multiple'.
%   'ListSize'      [width height] of listbox in pixels; defaults
%                   to [160 300].
%   'InitialValue'  vector of indices of which items of the list box
%                   are initially selected; defaults to the first item.
%   'Name'          String for the figure's title; defaults to ''.
%   'PromptString'  string matrix or cell array of strings which appears
%                   as text above the list box; defaults to {}.
%   'OKString'      string for the OK button; defaults to 'OK'.
%   'CancelString'  string for the Cancel button; defaults to 'Cancel'.
%
%   A 'Select all' button is provided in the multiple selection case.
%
%   Example:
%     d = dir;
%     str = {d.name};
%     [s,v] = listdlg('PromptString','Select a file:',...
%                     'SelectionMode','single',...
%                     'ListString',str)
 %
%  See also DIALOG, ERRORDLG, HELPDLG, INPUTDLG,
%    MSGBOX, QUESTDLG, WARNDLG.

%   Copyright 1984-2005 The MathWorks, Inc.
%   $Revision: 1.1 $  $Date: 2008/05/22 22:20:02 $

%   'uh'            uicontrol button height, in pixels; default = 22.
%   'fus'           frame/uicontrol spacing, in pixels; default = 8.
%   'ffs'           frame/figure spacing, in pixels; default = 8.

% simple test:
%
% d = dir; [s,v] = listdlg('PromptString','Select a file:','ListString',{d.name});
%
error(nargchk(1,inf,nargin))

figname = '';
smode = 2;   % (multiple)
promptstring = {};
liststring = [];
listsize = [160 300];
initialvalue = [];
okstring = 'OK';
cancelstring = 'Cancel';
fus = 8;
ffs = 8;
uh = 22;

if mod(length(varargin),2) ~= 0
    % input args have not com in pairs, woe is me
    error('MATLAB:listdlg:InvalidArgument', 'Arguments to LISTDLG must come param/value in pairs.')
end
for i=1:2:length(varargin)
    switch lower(varargin{i})
     case 'name'
      figname = varargin{i+1};
     case 'promptstring'
      promptstring = varargin{i+1};
     case 'selectionmode'
      switch lower(varargin{i+1})
       case 'single'
        smode = 1;
       case 'multiple'
        smode = 2;
      end
     case 'listsize'
      listsize = varargin{i+1};
     case 'liststring'
      liststring = varargin{i+1};
     case 'initialvalue'
      initialvalue = varargin{i+1};
     case 'uh'
      uh = varargin{i+1};
     case 'fus'
      fus = varargin{i+1};
     case 'ffs'
      ffs = varargin{i+1};
     case 'okstring'
      okstring = varargin{i+1};
     case 'cancelstring'
      cancelstring = varargin{i+1};
     otherwise
      error('MATLAB:listdlg:UnknownParameter', ['Unknown parameter name passed to LISTDLG.  Name was ' varargin{i}])
    end
end

if ischar(promptstring)
    promptstring = cellstr(promptstring);
end

if isempty(initialvalue)
    initialvalue = 1;
end

if isempty(liststring)
    error('MATLAB:listdlg:NeedParameter', 'ListString parameter is required.')
end

ex = get(0,'defaultuicontrolfontsize')*1.7;  % height extent per line of uicontrol text (approx)

fp = get(0,'defaultfigureposition');
w = 2*(fus+ffs)+listsize(1);
h = 2*ffs+6*fus+ex*length(promptstring)+listsize(2)+uh+(smode==2)*(fus+uh);
fp = [fp(1) fp(2)+fp(4)-h w h];  % keep upper left corner fixed

fig_props = { ...
    'name'                   figname ...
    'color'                  get(0,'defaultUicontrolBackgroundColor') ...
    'resize'                 'off' ...
    'numbertitle'            'off' ...
    'menubar'                'none' ...
    'windowstyle'            'modal' ...
    'visible'                'off' ...
    'createfcn'              ''    ...
    'position'               fp   ...
    'closerequestfcn'        'delete(gcbf)' ...
            };

liststring=cellstr(liststring);

fig = figure(fig_props{:});

if length(promptstring)>0
    prompt_text = uicontrol('style','text','string',promptstring,...
        'horizontalalignment','left',...
        'position',[ffs+fus fp(4)-(ffs+fus+ex*length(promptstring)) ...
        listsize(1) ex*length(promptstring)]); %#ok
end

btn_wid = (fp(3)-2*(ffs+fus)-fus)/2;

listbox = uicontrol('style','listbox',...
                    'position',[ffs+fus ffs+uh+4*fus+(smode==2)*(fus+uh) listsize],...
                    'string',liststring,...
                    'backgroundcolor','w',...
                    'max',smode,...
                    'tag','listbox',...
                    'value',initialvalue, ...
                    'callback', {@doListboxClick});

ok_btn = uicontrol('style','pushbutton',...
                   'string',okstring,...
                   'position',[ffs+fus ffs+fus btn_wid uh],...
                   'callback',{@doOK,listbox});

cancel_btn = uicontrol('style','pushbutton',...
                       'string',cancelstring,...
                       'position',[ffs+2*fus+btn_wid ffs+fus btn_wid uh],...
                       'callback',{@doCancel,listbox});

if smode == 2
    selectall_btn = uicontrol('style','pushbutton',...
                              'string','Select all',...
                              'position',[ffs+fus 4*fus+ffs+uh listsize(1) uh],...
                              'tag','selectall_btn',...
                              'callback',{@doSelectAll, listbox});

    if length(initialvalue) == length(liststring)
        set(selectall_btn,'enable','off')
    end
    set(listbox,'callback',{@doListboxClick, selectall_btn})
end

set([fig, ok_btn, cancel_btn, listbox], 'keypressfcn', {@doKeypress, listbox});

set(fig,'position',getdialoglocation(fp, get(fig,'Units')));
% Make ok_btn the default button.
%setdefaultbutton(fig, ok_btn);

% make sure we are on screen
movegui(fig)
set(fig, 'visible','on'); drawnow;

try
    % Give default focus to the listbox *after* the figure is made visible
    uicontrol(listbox);
    uiwait(fig);
catch
    if ishandle(fig)
        delete(fig)
    end
end

if isappdata(0,'ListDialogAppData__')
    ad = getappdata(0,'ListDialogAppData__');
    selection = ad.selection;
    value = ad.value;
    rmappdata(0,'ListDialogAppData__')
else
    % figure was deleted
    selection = [];
    value = 0;
end

%% figure, OK and Cancel KeyPressFcn
function doKeypress(src, evd, listbox) %#ok
switch evd.Key
 case 'escape'
  doCancel([],[],listbox);
end

%% OK callback
function doOK(ok_btn, evd, listbox) %#ok
if (~isappdata(0, 'ListDialogAppData__'))
    ad.value = 1;
    ad.selection = get(listbox,'value');
    setappdata(0,'ListDialogAppData__',ad);
    delete(gcbf);
end

%% Cancel callback
function doCancel(cancel_btn, evd, listbox) %#ok
ad.value = 0;
ad.selection = get(listbox,'value');
setappdata(0,'ListDialogAppData__',ad)
delete(gcbf);

%% SelectAll callback
function doSelectAll(selectall_btn, evd, listbox) %#ok
set(selectall_btn,'enable','off')
set(listbox,'value',1:length(get(listbox,'string')));

%% Listbox callback
function doListboxClick(listbox, evd, selectall_btn) %#ok
% if this is a doubleclick, doOK
if strcmp(get(gcbf,'SelectionType'),'open')
    %doOK([],[],listbox);
elseif nargin == 3
    if length(get(listbox,'string'))==length(get(listbox,'value'))
        set(selectall_btn,'enable','off')
    else
        set(selectall_btn,'enable','on')
    end
end


function header = text2header(requestdata,config)
request_text=char(requestdata);
request_lines = regexp(request_text, '\r\n+', 'split');
request_words = regexp(request_lines, '\s+', 'split');
for i=1:length(request_lines)
    line=request_lines{i};
    if(isempty(line)), break; end
    type=request_words{i}{1};
    switch(lower(type))
        case 'get'
            header.Get.Filename=request_words{i}{2};
            header.Get.Protocol=request_words{i}{3};
        case 'post'
            header.Post.Filename=request_words{i}{2};
            header.Post.Protocol=request_words{i}{3};
        case 'host:'
            header.Host=rmvp(line(7:end));
        case 'user-agent:'
            header.UserAgent=rmvp(line(13:end));
        case 'accept:'
            header.Accept=rmvp(line(9:end));
        case 'accept-language:'
            header.AcceptLanguage=rmvp(line(18:end));
        case 'accept-encoding:'
            header.AcceptEncoding=rmvp(line(18:end));
        case 'accept-charset:'
            header.AcceptCharset=rmvp(line(17:end));
        case 'keep-alive:'
            header.KeepAlive=rmvp(line(13:end));
        case 'connection:'
            header.Connection=rmvp(line(13:end));
        case 'content-length:'
            header.ContentLength=rmvp(line(17:end));
        case 'content-type:'
            %lines=rmvp(line(15:end));
            switch rmvp(request_words{i}{2})
                case {'application/x-www-form-urlencoded','application/x-www-form-urlencoded;'}
                    header.ContentType.Type='application/x-www-form-urlencoded';
                    header.ContentType.Boundary='&';
                case {'multipart/form-data','multipart/form-data;'}
                    header.ContentType.Type='multipart/form-data';
                    str=request_words{i}{3};
                    header.ContentType.Boundary=str(10:end);
                otherwise
                    disp('unknown')
            end
        otherwise
    end
end
header.Content=struct;
if(isfield(header,'ContentLength'))
    cl=str2double(header.ContentLength);
    str=request_text(end-cl+1:end);
    data=requestdata(end-cl+1:end);
    if(~isfield(header,'ContentType'))
        header.ContentType.Type=''; header.ContentType.Boundary='&';
    end
    switch (header.ContentType.Type)
        case {'application/x-www-form-urlencoded',''}
            str=rmvp(str);
            words = regexp(str, '&', 'split');
            for i=1:length(words)
                words2 = regexp(words{i}, '=', 'split');
                header.Content.(words2{1})=words2{2};
            end
        case 'multipart/form-data'
            pos=strfind(str,header.ContentType.Boundary);
            while((pos(1)>1)&&(str(pos(1)-1)=='-'))
                header.ContentType.Boundary=['-' header.ContentType.Boundary];
                pos=strfind(str,header.ContentType.Boundary);
            end

            for i=1:(length(pos)-1)
                pstart=pos(i)+length(header.ContentType.Boundary);
                pend=pos(i+1)-3; % Remove "13 10" End-line characters
                subrequestdata=data(pstart:pend);
                subdata= multipart2struct(subrequestdata,config);
                header.Content.(subdata.Name).Filename=subdata.Filename;
                header.Content.(subdata.Name).ContentType=subdata.ContentType;
                header.Content.(subdata.Name).ContentData=subdata.ContentData;
            end
        otherwise
            disp('unknown')
    end
end


function s = convert(tree,uid)
% XMLTREE/CONVERT Converter an XML tree in a structure
%
% tree      - XMLTree object
% uid       - uid of the root of the subtree, if provided.
%             Default is root
% s         - converted structure
%__________________________________________________________________________
%
% Convert an XMLTree into a structure, when possible.
% When several identical tags are present, a cell array is used.
% The root tag is not saved in the structure.
% If provided, only the structure corresponding to the subtree defined
% by the uid UID is returned.
%__________________________________________________________________________
% Copyright (C) 2002-2015  http://www.artefact.tk/

% Guillaume Flandin
% $Id: convert.m 6480 2015-06-13 01:08:30Z guillaume $

% Exemple:
% tree = 'field1<c>field2</c>field3';
% toto = convert(xmltree(tree));
% <=> toto = struct('b',{{'field1', 'field3'}},'c','field2')

%error(nargchk(1,2,nargin));

% Get the root uid of the output structure
if nargin == 1
    % Get the root uid of the XML tree
    root_uid = root(tree);
else
    % Uid provided by user
    root_uid = uid;
end

% Initialize the output structure
% struct([]) should be used but this only works with Matlab 6
% So we create a field that we delete at the end
%s = struct(get(tree,root_uid,'name'),''); % struct([])
s = struct('deletedummy','');

%s = sub_convert(tree,s,root_uid,{get(tree,root_uid,'name')});
s = sub_convert(tree,s,root_uid,{});

s = rmfield(s,'deletedummy');

%==========================================================================
function s = sub_convert(tree,s,uid,arg)
    type = get(tree,uid,'type');
    switch type
        case 'element'
            child = children(tree,uid);
            l = {};
            ll = {};
            for i=1:length(child)
                if isfield(tree,child(i),'name')
                    ll = { ll{:}, get(tree,child(i),'name') };
                end
            end
            for i=1:length(child)
                if isfield(tree,child(i),'name')
                    name = get(tree,child(i),'name');
                    nboccur = sum(ismember(l,name));
                    nboccur2 = sum(ismember(ll,name));
                    l = { l{:}, name };
                    if nboccur || (nboccur2>1)
                        arg2 = { arg{:}, name, {nboccur+1} };
                    else
                        arg2 = { arg{:}, name};
                    end
                else
                    arg2 = arg;
                end
                s = sub_convert(tree,s,child(i),arg2);
            end
            if isempty(child)
                s = sub_setfield(s,arg{:},'');
            end
            %- saving attributes : does not work with b
            %- but ok with <c>b</c>
%             attrb = attributes(tree,'get',uid);     %-
%             if ~isempty(attrb)                      %-
%                 arg2 = {arg{:} 'attributes'};       %-
%                 s = sub_setfield(s,arg2{:},attrb);  %-
%             end                                     %-
        case 'chardata'
            s = sub_setfield(s,arg{:},get(tree,uid,'value'));
            %- convert strings into their numerical equivalent when possible
            %- e.g. string '3.14159' becomes double scalar 3.14159
%             v = get(tree,uid,'value');              %-
%             cv = str2num(v);                        %-
%             if isempty(cv)                          %-
%                 s = sub_setfield(s,arg{:},v);       %-
%             else                                    %-
%                 s = sub_setfield(s,arg{:},cv);      %-
%             end                                     %-
        case 'cdata'
            s = sub_setfield(s,arg{:},get(tree,uid,'value'));
        case 'pi'
            % Processing instructions are evaluated if possible
            app = get(tree,uid,'target');
            switch app
                case {'matlab',''}
                    s = sub_setfield(s,arg{:},eval(get(tree,uid,'value')));
                case 'unix'
                    s = sub_setfield(s,arg{:},unix(get(tree,uid,'value')));
                case 'dos'
                    s = sub_setfield(s,arg{:},dos(get(tree,uid,'value')));
                case 'system'
                    s = sub_setfield(s,arg{:},system(get(tree,uid,'value')));
                otherwise
                    try
                        s = sub_setfield(s,arg{:},feval(app,get(tree,uid,'value')));
                    catch
                        warning('[XMLTree] Unknown target application');
                    end
            end
        case 'comment'
            % Comments are forgotten
        otherwise
            warning(sprintf('Type %s unknown : not saved',get(tree,uid,'type')));
    end

%==========================================================================
function s = sub_setfield(s,varargin)
% Same as setfield but using '{}' rather than '()'
%if (isempty(varargin) | length(varargin) < 2)
%    error('Not enough input arguments.');
%end

subs = varargin(1:end-1);
for i = 1:length(varargin)-1
    if (isa(varargin{i}, 'cell'))
        types{i} = '{}';
    elseif ischar(varargin{i})
        types{i} = '.';
        subs{i} = varargin{i}; %strrep(varargin{i},' ',''); % deblank field name
    else
        error('Inputs must be either cell arrays or strings.');
    end
end

% Perform assignment
try
   s = builtin('subsasgn', s, struct('type',types,'subs',subs), varargin{end});
catch
   error(lasterr)
end


function handles = render_call_boxes(current_axes,handles,roi, fill_heigth)
%% This function draws rectangles in the focus view and page view

axis_xlim = get(current_axes,'Xlim');
axis_ylim = get(current_axes,'Ylim');

% Find calls within the current window
calls_in_page = find( (handles.data.calls.Box(:,1) >= axis_xlim(1) & handles.data.calls.Box(:,1) < axis_xlim(2)  ) ...
    | ( handles.data.calls.Box(:,1) + handles.data.calls.Box(:,3)  >= axis_xlim(1) & handles.data.calls.Box(:,1) + handles.data.calls.Box(:,3)  <= axis_xlim(2) )...
    | ( handles.data.calls.Box(:,1)<=  axis_xlim(1) & handles.data.calls.Box(:,1) + handles.data.calls.Box(:,3) >=  axis_xlim(2) )...
    );

boxes = handles.data.calls.Box(calls_in_page,:);

% Draw labels if there are any labels other than 'USV'
% if ~isempty(calls_in_page) any(handles.data.calls.Type ~= 'USV')
    LabelVisible = 'on'; % use 'hover' to only display labels on mouse over2
% else
%     LabelVisible = 'off';
% end

% Loop through all calls
for box_number = 1:length(calls_in_page)
    current_tag = num2str(calls_in_page(box_number));

    if fill_heigth
        boxes(box_number,2) = axis_ylim(1);
        boxes(box_number,4) = axis_ylim(2);
    end

    line_width = 0.5;
    box_color = 'r';
    line_style = '-';

    % Make the line thick if current call
    if handles.data.calls.Accept(calls_in_page(box_number))
        box_color = [0 1 0];
    end
    if calls_in_page(box_number) == handles.data.currentcall
        line_width = 2;
    end

    if roi

        % Only display a label if it isn't just "USV"
        if handles.data.calls.Type(calls_in_page(box_number)) == {'USV'}
            label = '';
        else
            label = char(handles.data.calls.Type(calls_in_page(box_number)));
        end

        % Add a new rectangle if there isn't a handle for one yet, or
        % update an existing one
        if box_number > length(handles.FocusWindowRectangles) % draw a new rectangle if we need more
            c = uicontextmenu;
            handles.FocusWindowRectangles{box_number} = drawrectangle(...
                'Position', boxes(box_number, :),...
                'Parent', current_axes,...
                'Color', box_color,...
                'FaceAlpha', 0,...
                'LineWidth', line_width,...
                'Tag', current_tag,...
                'uicontextmenu', c,...
                'label', label,...
                'LabelAlpha', .5,...
                'LabelVisible', LabelVisible);
            addlistener(handles.FocusWindowRectangles{box_number},'ROIClicked',@callBoxDeleteCallback);
            addlistener(handles.FocusWindowRectangles{box_number},'ROIMoved', @roiMovedCallback);
        else
            set(handles.FocusWindowRectangles{box_number},...
                'Position', boxes(box_number, :),...
                'Color', box_color,...
                'LineWidth', line_width,...
                'Tag', current_tag,...
                'Visible', true,...
                'label', label,...
                'LabelVisible', LabelVisible);
        end

    else
        % Add a new rectangle if there isn't a handle for one yet, or
        % update an existing one
        if box_number > length(handles.PageWindowRectangles)
            handles.PageWindowRectangles{box_number} = rectangle(current_axes,...
                'Position',  boxes(box_number, :),...
                'LineWidth', line_width,...
                'LineStyle', line_style,...
                'EdgeColor', box_color,...
                'PickableParts', 'none');
        else
            set(handles.PageWindowRectangles{box_number},...
                'Position',  boxes(box_number, :),...
                'EdgeColor', box_color,...
                'LineWidth', line_width,...
                'Visible', true)
        end

    end
end

% Make any extra boxes invisible
if roi
    for i = length(calls_in_page)+1:length(handles.FocusWindowRectangles)
        handles.FocusWindowRectangles{i}.Visible = false;
    end
else
    for i = length(calls_in_page)+1:length(handles.PageWindowRectangles)
        handles.PageWindowRectangles{i}.Visible = false;
    end
end

end


classdef TestLUCID
    %TestLUCID

    properties (Constant)
        im = fullfile(mexopencv.root(),'test','tsukuba.png');
    end

    methods (Static)
        function test_compute_img
            obj = cv.LUCID('LucidKernel',1, 'BlurKernel',2);
            typename = obj.typeid();
            ntype = obj.defaultNorm();

            img = imread(TestLUCID.im);
            kpts = cv.FAST(img, 'Threshold',20);
            [desc, kpts2] = obj.compute(img, kpts);
            validateattributes(kpts2, {'struct'}, {'vector'});
            assert(all(ismember(fieldnames(kpts), fieldnames(kpts2))));
            validateattributes(desc, {obj.descriptorType()}, ...
                {'size',[numel(kpts2) obj.descriptorSize()]});
        end

        function test_compute_imgset
            img = imread(TestLUCID.im);
            imgs = {img, img};
            kpts = cv.FAST(img, 'Threshold',20);
            kpts = {kpts, kpts};

            obj = cv.LUCID();
            [descs, kpts] = obj.compute(imgs, kpts);
            validateattributes(kpts, {'cell'}, {'vector'});
            validateattributes(descs, {'cell'}, {'vector', 'numel',numel(imgs)});
            cellfun(@(d,k) validateattributes(d, {obj.descriptorType()}, ...
                {'size',[numel(k) obj.descriptorSize()]}), descs, kpts);
        end

        function test_error_1
            try
                cv.LUCID('foobar');
                throw('UnitTest:Fail');
            catch e
                assert(strcmp(e.identifier,'mexopencv:error'));
            end
        end
    end

end


function EEG = eeg_formatamica(EEG)
if isfield(EEG.etc.amica,'LLt') && isfield(EEG.etc.amica,'LLt_smooth')
    if size(EEG.data,2) == size(EEG.etc.amica.LLt,2) && size(EEG.data,3) == size(EEG.etc.amica.LLt,3)
        if size(EEG.data,2) == size(EEG.etc.amica.LLt_smooth,2) && size(EEG.data,3) == size(EEG.etc.amica.LLt_smooth,3)

            EEG.data = [EEG.data; EEG.etc.amica.LLt; EEG.etc.amica.LLt_smooth];
            EEG.etc.amica.prob_added = 1;
            num_models = size(EEG.etc.amica.LLt,1);
            EEG.nbchan = EEG.nbchan + 2*num_models;
            if ~isempty(EEG.chanlocs)
                channames = {};
                for k=1:2*num_models
                    channames{end+1} = ['prob' num2str(k)]; end
                [EEG.chanlocs(end+1 : end+2*num_models).labels] = channames{:};
            end
        end
    end
end


function [outStruct, replacedFields] = bst_jsondecode(inString, forceBstVersion)
% BST_JSONDECODE: Decodes a JSON string as a Matlab structure
%
% USAGE: outStruct = bst_jsondecode(inString)
%        outStruct = bst_jsondecode(filename)
%
% Limitations:
%   - requires the "root type" to be an object: a list of key:value pairs
%     enclosed in curly brackets.
%   - only supports arrays of number or string.
%   - escaped special characters \r\n\t are decoded as unicode arrow symbols as
%     MATLAB does. Other escaped characters (other than \\ and \") are kept as is
%     and therefore would not be correctly re-encoded.

% @=============================================================================
% 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: Martin Cousineau, 2018-2019; Francois Tadel, 2018; Marc Lalancette, 2021

if nargin < 2 || isempty(forceBstVersion)
    forceBstVersion = 0;
end

% Json allows any string (unicode characters) as a key, unlike Matlab var/field
% names. So some must be renamed.
iReplaced = 0;
replacedFields = cell(0, 2);

% If the input is an existing filename: read it
if exist(inString, 'file')
    jsonFile = inString;
    % Open file
    fid = fopen(jsonFile, 'r');
    if (fid < 0)
        error(['Cannot open JSON file: ' jsonFile]);
    end
    % Read file
    inString = fread(fid, [1, Inf], '*char');
    % Close file
    fclose(fid);
    % Check that something was read
    if isempty(inString)
        error(['File is empty: ' jsonFile]);
    end
else
    jsonFile = [];
end

% If possible, call built-in function
if ~forceBstVersion && exist('jsondecode', 'builtin') == 5
    outStruct = jsondecode(inString);
    return;
end

inString = strtrim(inString);
outStruct = struct();

% States:
STATE.START           = 0;
STATE.START_FIELD     = 1;
%STATE.READ_NEXT_FIELD = 2; % unused
STATE.READ_FIELD      = 3;
STATE.END_FIELD       = 4;
STATE.START_VALUE     = 5; % can also be the start of a string in an existing list
%STATE.READ_NEXT_VALUE = 6; % unused
STATE.READ_VALUE      = 7;
STATE.END_VALUE       = 8;
STATE.START_LIST      = 9;
STATE.END_LIST        = 10;
STATE.END_SUBLEVEL    = 11;

% Supported value types:
% Note: only lists of numbers or strings are supported
VAL.NONE = 0; % to simplify resetting without having to check if empty.
VAL.CHAR = 1;
VAL.NUM  = 2;
VAL.BOOL = 3;
VAL.LIST_NUM = 4;
VAL.LIST_STR = 5;

state = STATE.START;
field = ''; % json key / matlab struct field
value = [];
token = ''; % json string (key, value, or element of array value)
path  = {};
valType = VAL.NONE;
escape = false;
lineNum  = 1;
lineChar = 0;

for iChar = 1:length(inString)
    c = inString(iChar);
    lineChar = lineChar + 1;
    err = 0;

    % Process character depending on state

    % Reading a string
    if state == STATE.READ_FIELD || ...
            (state == STATE.READ_VALUE && (valType == VAL.CHAR || valType == VAL.LIST_STR))
        if escape
            if c == '"' || c == '\'
                token(end + 1) = c; %#ok<*AGROW>
                % Copy Matlab's hack to decode/encode escaped special characters.
            elseif c == 'n' % newline / linefeed
                token(end + 1) = char(8629); % ↵
            elseif c == 'r' % carriage return
                token(end + 1) = char(8592); % ←
            elseif c == 't' % tab
                token(end + 1) = char(8594); % →
            else
                % Possibly other special character. Keep as is, but won't be properly re-encoded.
                token = [token '\' c];
            end
            escape = false;
        elseif c == '\'
            % Escape character for itself and double quote
            escape = true;
        elseif c == '"'
            if state == STATE.READ_FIELD
                state = STATE.END_FIELD;
            else
                state = STATE.END_VALUE;
            end
        else
            % Any other unicode character is valid in a json string.
            token(end + 1) = c;
        end

        % Ignore all white space outside strings.
    elseif c == char(10)
        % Increment line number if we have a line break
        lineNum = lineNum + 1;
        lineChar = 0;
    elseif isspace(c)
        continue;

    elseif state == STATE.START
        if c == '{'
            state = STATE.START_FIELD;
        elseif c == '"'
            state = STATE.READ_FIELD;
        else
            err = 1;
        end
    elseif state == STATE.START_FIELD
        %field = '';
        token = '';
        if c == '"'
            state = STATE.READ_FIELD;
        elseif c == '}'
            % This was an empty "sublevel"/object, i.e. {}.
            state = STATE.END_SUBLEVEL;
        else
            err = 1;
        end
    elseif state == STATE.END_FIELD
        % Validate field name is appropriate for matlab.  Need to do it now in
        % case there are sub-fields.
        if ~isvarname(token)
            iReplaced = iReplaced + 1;
            %         field = ['REPLACED_FIELD_', str2double(iReplaced)];
            % Rename like Matlab, though 2 keys could have the same modified name.
            field = matlab.lang.makeValidName(token);
            % Append _ if the field already exists.
            tempPath = 'outStruct';
            for iP = 1:length(path)
                tempPath = [tempPath '.' path{iP}];
            end
            if isfield(eval(tempPath), field)
                field = [field '_'];
            end
            replacedFields(iReplaced, :) = {field, token};
        else
            field = token;
        end
        if c ~= ':'
            err = 1;
        end
        valType = VAL.NONE;
        value = [];
        state = STATE.START_VALUE;
    elseif state == STATE.START_VALUE
        % value was emptied in END_FIELD since we might continue a list of strings.
        if c == '{'
            % Save sub-field. Needed to later check existance of renamed fields.
            outStruct = saveField(outStruct, path, field, [], valType);
            path{end + 1} = field;
            state = STATE.START_FIELD;
        elseif c == '['
            state = STATE.START_LIST;
        elseif c == '"'
            % Check if continuing an existing list of strings
            if valType ~= VAL.LIST_STR
                valType = VAL.CHAR;
            end
            token = '';
            state = STATE.READ_VALUE;
        elseif c >= '0' && c <= '9'
            valType = VAL.NUM;
            token = c;
            state = STATE.READ_VALUE;
            % Detect special values for boolean and null
        elseif findAhead({'true', 'false', 'null'}, inString(iChar:end))
            valType = VAL.BOOL;
            token = c;
            state = STATE.READ_VALUE;
        else
            err = 1;
        end
    elseif state == STATE.READ_VALUE % NUM, BOOL or LIST_NUM
        if valType == VAL.LIST_NUM && (c == ',' || c == ']')
            n = str2double(token);
            token = '';
            if ~isempty(n)
                value(end + 1) = n;
            else
                err = 1;
            end
            if c == ']'
                state = STATE.END_LIST;
            end
        elseif c == ',' || c == '}'
            value = token;
            % Save
            outStruct = saveField(outStruct, path, field, value, valType);
            if c == ','
                state = STATE.START_FIELD;
            elseif c == '}'
                state = STATE.END_SUBLEVEL;
            end
        else % numerical, boolean or null values validated later.
            token(end + 1) = c;
        end
    elseif state == STATE.END_VALUE && valType == VAL.LIST_STR
        value{end + 1} = token;
        if c == ','
            state = STATE.START_VALUE;
        elseif c == ']'
            state = STATE.END_LIST;
        else
            err = 1;
        end
    elseif (state == STATE.END_VALUE && valType == VAL.CHAR) || ...
            state == STATE.END_LIST
        if valType == VAL.CHAR
            value = token;
        end
        % Save
        outStruct = saveField(outStruct, path, field, value, valType);
        if c == ','
            state = STATE.START_FIELD;
        elseif c == '}'
            state = STATE.END_SUBLEVEL;
        else
            err = 1;
        end
    elseif state == STATE.END_SUBLEVEL
        if ~isempty(path) % else this should be the end of the file
            path = path(1:end-1);
        end
        if c == ','
            state = STATE.START_FIELD;
        elseif c == '}'
            state = STATE.END_SUBLEVEL;
        else
            err = 1;
        end
    elseif state == STATE.START_LIST
        if c == '"'
            valType = VAL.LIST_STR;
            token = '';
        else
            valType = VAL.LIST_NUM;
            token = c;
        end
        state = STATE.READ_VALUE;
    else
        err = 1;
    end

    % Error management
    if err
        errorMsg = sprintf(...
            ['JSON syntax error. Unexpected character ''%c'' ' ...
            'at line %d, column %d (character %d)'], ...
            c, lineNum, lineChar, iChar);
        if ~isempty(jsonFile)
            errorMsg = [errorMsg ' of file ' jsonFile];
        end
        error(errorMsg);
    end

end % character loop

% Warn if there were invalid field names.
if iReplaced > 0
    if nargout > 1
        warning('One or more field names were not valid for MATLAB and were replaced.');
    else
        warning('One or more field names were not valid for MATLAB and were replaced. To see the original names, use bst_jsondecode with a second output argument.');
    end
end

    function outStruct = saveField(outStruct, path, field, value, valType)
        sPath = 'outStruct';
        for iNode = 1:length(path)
            sPath = [sPath '.' path{iNode}];
        end

        if isempty(value)
            value = 'struct';
        elseif valType == VAL.CHAR
            % Due to eval later, replace single quotes by 2 single quotes.
            value = ['''' strrep(value, '''', '''''') ''''];
        elseif valType == VAL.LIST_NUM
            elems = value;
            value = '[';
            for iElem = 1:length(elems)
                if iElem > 1
                    value = [value ','];
                end
                value = [value num2str(elems(iElem))];
            end
            value = [value ']'];
        elseif valType == VAL.LIST_STR
            elems = value;
            value = '{';
            for iElem = 1:length(elems)
                if iElem > 1
                    value = [value ','];
                end
                value = [value '''' strrep(elems{iElem}, '''', '''''') ''''];
            end
            value = [value '}'];
        elseif valType == VAL.BOOL
            value = lower(value);
            if strcmp(value, 'null')
                value = '[]';
            elseif ~strcmp(value, 'true') && ~strcmp(value, 'false')
                error(['JSON syntax error. Invalid boolean string: ' value]);
            end
        elseif valType == VAL.NUM && isempty(str2double(value))
            error(['JSON syntax error. Invalid numerical string: ' value]);
        end

        try
            eval([sPath '.' field ' = ' value ';']);
        catch ME
            sPath %#ok<*NOPRT>
            field
            value
            rethrow(ME);
        end

    end
end

function found = findAhead(needles, haystack)
found = 0;
for iNeedle = 1:length(needles)
    needle = needles{iNeedle};
    if strncmpi(needle, haystack, length(needle))
        found = 1;
        return;
    end
end
end


%% (Internal) Display a message framed to a string
%
%     This function format a string for fprintf-like functions in order to
%     present a message string2display entitled with '-' characters below.
%
%   str_out = disp_string_title(style, string2display )
%
% Arguments:
%
%      + style: if numeric, could be the stream identifier for fprintf
%      function. If char, can be the "style" argument for cprintf function.
%
%      + string2display: The string to frame.
%
% Output:
%
%      + str_out: resulting string
%
% Example:
%
%
% See also disp_string_framed, disp_option_enumeration
%
% Author: Mariano Llamedo Soria llamedom@electron.frba.utn.edu.ar
% Version: 0.1 beta
% Last update: 14/5/2014
% Birthdate  : 21/4/2015
% Copyright 2008-2015
%
function str_out = disp_string_title(style, string2display )

    str_out = [];

    if( nargin == 1 || isempty(string2display) )
        string2display = style;
        style = 'text';
        fid = 1;
    end

    if( isempty(style) )
        style = 'text';
        fid = 1;
    elseif( isnumeric(style) )
        fid = style;
        style = 'text';
    else
        if( ischar(style) )
            fid = 1;
        else
            style = 'text';
            fid = 1;
        end
    end

    str_aux = sprintf(' %s ', string2display);
    str_aux1 = repmat('-', 1, length(str_aux));

    if( fid == 0)
        string2display = strrep(string2display, '\', '\\');
        str_out = sprintf('\n%s\n# %s #\n%s\n\n', str_aux1, string2display, str_aux1);
    elseif( fid == 1)
        fprintf(fid, '\n');
        cprintf(style, '%s', str_aux);
        fprintf(fid, '\n%s\n\n', str_aux1);
    else
        fprintf(fid, '\n%s\n%s\n\n', str_aux, str_aux1);
    end

end


classdef hdf5prop < handle
% HDF5PROP Class for transparent file data access.
% Matlab class to create and access HDF5 datasets transparently as Matlab
% variable. Data can be accessed and written with subscript referencing and
% assignment methods, just like a matlab variable, only size must be
% explicitly set or changed.
%
%   prop = hdf5prop(file, dataset)
%     Creates a 'hdf5prop' object for the given dataset in the given HDF5
%     file.
%
%   prop = hdf5prop(file, dataset, mode)
%     Specify the access mode,
%       mode = 'r' : readonly access (default)
%       mode = 'rw': readwrite access
%
%   prop = hdf5prop(file, dataset, parameter, value, ...)
%     Creates a 'hdf5prop' object for a new dataset with given parameters.
%
%     parameter
%       'size'        - size of the dataset. Necessary.
%       'chunk_size'  - size of the chunks of the dataset.
%       'compression' - 0-9  compression level.
%       'type'        - type of the dataset to create.  This can be a string
%                       such as 'double', in which case the datatype maps to
%                       'H5T_NATIVE_DOUBLE', or it can be a derived HDF5
%                       datatype.
%       'max_size'    - the maximum size of the dataset to create.
%       'fill'        - the fill value.

% copyleft 2011, Piers Titus van der Torren

  % TODO: replace all H5 calls to direct mex calls for speed
  properties( SetAccess = protected )
    file
    dataset
    mode = 'H5F_ACC_RDONLY'
    propsize
  end
  properties( SetAccess = protected, Transient = true )
    dataset_id
  end

  methods
    function self = hdf5prop(file,dataset,varargin)

      self.file = file;
      self.dataset = dataset;
      if nargin>2
        switch varargin{1}
          case {'H5F_ACC_RDWR','rw'}
            self.mode = 'H5F_ACC_RDWR';
            varargin = varargin(2:end);
          case {'H5F_ACC_RDONLY','r'}
            varargin = varargin(2:end);
          otherwise
        end
      end
      if ~exist(file, 'file')
        % create file
        file_id = H5F.create(file, 'H5F_ACC_EXCL', 'H5P_DEFAULT', 'H5P_DEFAULT');
        self.mode = 'H5F_ACC_RDWR';
      else
        file_id = H5F.open(self.file, self.mode, 'H5P_DEFAULT');
      end
      try
        self.dataset_id = H5D.open(file_id,self.dataset);
        if ~isempty(varargin)
          error('hdf5prop:exists', 'dataset already exists, even though creation parameters are given')
        end
      catch ex
        if strcmp(ex.identifier, 'hdf5prop:exists')
          rethrow(ex);
        end
        % create dataset

        if strcmp(self.mode, 'H5F_ACC_RDONLY')
          self.mode = 'H5F_ACC_RDWR';
          H5F.close(file_id);
          file_id = H5F.open(self.file, self.mode, 'H5P_DEFAULT');
        end

        self.dataset_id = h5datacreate(file_id, self.dataset, varargin{:});

      end

      space = H5D.get_space(self.dataset_id);
      [ans, dims] = H5S.get_simple_extent_dims(space);
      self.propsize = fliplr(dims);
    end

    function open( self )
      if ~isa(self.dataset_id, 'H5ML.id') || self.dataset_id.identifier < 0

        file_id=H5F.open(self.file,self.mode,'H5P_DEFAULT');
        self.dataset_id=H5D.open(file_id,self.dataset);

        % get size of dataset
        % TODO: size is not valid when file is changed elsewhere while open
        space = H5D.get_space(self.dataset_id);
        [ndims, dims] = H5S.get_simple_extent_dims(space);
        self.propsize = fliplr(dims);
      end
    end

    function close( self )
      % CLOSE Close file if open.
      if isa(self.dataset_id, 'H5ML.id')
        H5D.close(self.dataset_id);
      end
    end

    function set_extent( self, sz)
      % SET_EXTENT Allocate space
      %
      %   hdf5prop.set_extent(sz)
      %     Extend allocated space to size sz.
      self.open();
      H5D.extend(self.dataset_id, fliplr(sz));
      %H5D.set_extent(self.dataset_id, fliplr(sz));
      self.propsize = sz;
    end

    function set_mode( self, mode )
      self.close()
      switch mode
        case {'H5F_ACC_RDWR','rw'}
          self.mode = 'H5F_ACC_RDWR';
        case {'H5F_ACC_RDONLY','r'}
          self.mode = 'H5F_ACC_RDONLY';
        otherwise
          error('unknown mode')
      end
      self.open()
    end

    function lock( self )
      self.close()
      self.mode = 'H5F_ACC_RDONLY';
      self.open()
    end
  end

  methods( Hidden=true )

    function varargout = subsref(self, S)
      switch S(1).type
        case {'.', '{}'}
          [varargout{1:nargout}] = builtin('subsref',self,S);
        case '()'
          [varargout{1:nargout}] = self.get_data(S(1).subs{:});
%         case '{}'
%           error('{} not allowed')
      end
    end

    function self = subsasgn(self, S, B)
      switch S(1).type
        case {'.', '{}'}
          builtin('subsasgn',self,S,B);
        case '()'
          self.set_data(S(1).subs, B);
%         case '{}'
%           error('{} not allowed')
      end
    end

    function out = get_data(self, varargin)

      self.open()

      % return all data
      if nargin == 1 || all(strcmp(varargin,':'))
        out = H5D.read(self.dataset_id,'H5ML_DEFAULT','H5S_ALL','H5S_ALL','H5P_DEFAULT');
        return;
      end

      outsize = [];

      propsize = size(self);
      dim = numel(propsize);
      % fill singular dimensions if there's only 1 nonsingular dimension
      if dim > 1 && numel(varargin)==1 && sum(propsize ~= 1) <= 1
        a = varargin;
        varargin = repmat({':'},1,dim);
        if any(propsize ~= 1)
          varargin{propsize ~= 1} = a{1};
        end
        outsize = size(a{1});
        if sum(outsize~=1)==1
          outsize = [];
        end
      end
      assert(dim == numel(varargin),'linear or multidimensional boolean indexing is not yet supported, now use all %d dimensions',dim);

      % divide selection into hyperslabs
      start = nan(1,dim);
      count = nan(1,dim);
      stride = nan(1,dim);
      hyperslab = true;
      for n = 1:dim
        sel = varargin{dim+1-n};  % traverse reversed instead of fliplr
        if isa(sel,'char') && sel == ':'
          start(n) = 1;
          count(n) = propsize(dim+1-n);
          stride(n) = 1;
          idxc{n} = sel;
          selc{n} = sel;
        else
          %if isempty(sel)
          %  % if one dimension is empty the result is empty too
          %  out = [];
          %  return
          %end

          sel = sel(:);
          if isa(sel,'logical')
            sel = find(sel);
            idxc{n} = ':';
          elseif numel(sel) <= 1 || ( issorted(sel) && min(diff(sel))>0 )
            idxc{n} = ':';
          else
            [sel, ans, idxc{n}] = unique(sel);
          end

          selc{n} = sel;

          count(n) = numel(sel);

          dsel = diff(sel);
          if isempty(sel)
            start(n) = 1;
            stride(n) = 1;
          elseif numel(sel)==1
            start(n) = sel(1);
            stride(n) = 1;
          elseif all(dsel == dsel(1))
            start(n) = sel(1);
            stride(n) = dsel(1);
          else
            hyperslab = false;
          end
        end
      end

      % TODO: fix datatype
      if any(count==0)
        out = zeros(fliplr(count));
        return
      end

      space=H5D.get_space(self.dataset_id);
      if hyperslab
        %H5ML.hdf5lib2('H5Sselect_hyperslab',space_double,'H5S_SELECT_SET',start,stride,count,[]);
        H5S.select_hyperslab(space,'H5S_SELECT_SET',start-1,stride,count,[]);
      else
        s = find(isnan(start));
        selend = cellfun(@(x) x(end),selc(s));
        sel1 = cellfun(@(x) x(1),selc(s));
        if prod(count(s)) > .1 * prod(selend - sel1)
          % since hyperslab is way faster than select_elements, it is
          % faster to read a full hypeslab and do the selection in
          % matlab. The factor above should match the speed ratio.
          start(s) =  sel1;
          stride(s) = 1;
          count(s) = selend-sel1+1;
          for n=s
            idxc{n} = selc{n}(idxc{n}) - selc{n}(1) + 1;
          end
          H5S.select_hyperslab(space,'H5S_SELECT_SET',start-1,stride,count,[]);
        else
          sel = zeros(dim,prod(count));
          % ndgrid of selc{:}
          for n=1:dim,
            if ischar(selc{n})
              x = (1:count(n))';
            else
              x = selc{n}; % Extract and reshape as a vector.
            end
            s = count; s(n) = []; % Remove i-th dimension
            x = reshape(x(:,ones(1,prod(s))),[length(x) s]); % Expand x
            x = permute(x,[2:dim-n+1 1 dim-n+2:dim]); % Permute to i'th dimension
            sel(n,:) = x(:);
          end
          %warning('using H5S.select_elements, might be slow')
          H5S.select_elements(space,'H5S_SELECT_SET',sel-1);
        end
      end

      % reserve space for reading
      mem_space = H5S.create_simple(dim, count, []);

      % read the actual data
      out = H5D.read(self.dataset_id,'H5ML_DEFAULT',mem_space,space,'H5P_DEFAULT');

      % shuffle output to match selection
      out=out(idxc{end:-1:1});

      if ~isempty(outsize)
        out = reshape(out,outsize);
      end

      H5S.close(space);
      H5S.close(mem_space);
    end

    function set_data(self, argin, B)

      if ~strcmp(self.mode, 'H5F_ACC_RDWR')
        error('this hdf5prop is read only')
      end

      self.open()

      propsize = size(self);
      dim = numel(propsize);
      % fill singular dimensions if there's only 1 nonsingular dimension
      if dim > 1 && numel(argin)==1 && sum(propsize ~= 1) <= 1
        a = argin;
        argin = repmat({':'},1,dim);
        if any(propsize ~= 1)
          argin{propsize ~= 1} = a{1};
        end
      end
      assert(dim == numel(argin),'linear or multidimensional boolean indexing is not yet supported, now use all %d dimensions',dim);

      colondim = strcmp(argin,':');

      % get input indexing dimensions
      indim = 1:dim;
      for n=1:dim
        if colondim(n)
          indim(n) = propsize(n);
        elseif isa(argin{n},'logical')
          indim(n) = sum(argin{n}(:));
        else
          indim(n) = numel(argin{n});
        end
      end

      % check if squeezed dimensions match
      indim = max(cellfun(@numel,argin), colondim.*propsize);
      szB = size(B);
      assert(numel(B)==1 || all(indim(indim~=1)==szB(szB~=1)),'Subscripted assignment dimension mismatch.')

      if any(indim==0)
        % nothing to be done
        return;
      end

      % unsqueeze input
      if numel(B)~=1
        B = reshape(B,indim);
      end

      % write all data
      if all(colondim)
        if numel(B) == 1
          B = repmat(B,propsize);
          % H5D.fill ?
        end

        H5D.write(self.dataset_id,'H5ML_DEFAULT','H5S_ALL','H5S_ALL','H5P_DEFAULT',B);
        return;
      end

      % divide selection into hyperslabs
      start = zeros(1,dim);
      count = zeros(1,dim);
      stride = zeros(1,dim);
      hyperslab = true;
      for n = 1:dim
        sel = argin{dim+1-n};  % traverse reversed instead of fliplr
        if isa(sel,'char') && sel == ':'
          start(n) = 1;
          count(n) = propsize(dim+1-n);
          stride(n) = 1;
          idxc{n} = sel;
          selc{n} = sel;
        else
          sel = sel(:);
          if isa(sel,'logical')
            sel = find(sel);
            idxc{n} = ':';
          elseif numel(sel) == 1 || ( issorted(sel) && min(diff(sel))>0 )
            idxc{n} = ':';
          else
            % this line is different from get_data
            [sel, idxc{n}] = unique(sel);
          end

          selc{n} = sel;

          count(n) = numel(sel);
          if hyperslab
            dsel = diff(sel);
            if numel(sel)==1
              start(n) = sel(1);
              stride(n) = 1;
            elseif all(dsel == dsel(1))
              start(n) = sel(1);
              stride(n) = dsel(1);
            else
              hyperslab = false;
            end
          end
        end
      end

      space=H5D.get_space(self.dataset_id);
      if hyperslab
        %H5ML.HDF5lib2('H5Sselect_hyperslab',space_double,'H5S_SELECT_SET',start,stride,count,[]);
        H5S.select_hyperslab(space,'H5S_SELECT_SET',start-1,stride,count,[]);
      else
        sel = zeros(dim,prod(count));
        % ndgrid of selc{:}
        for n=1:dim,
          if ischar(selc{n})
            x = (1:count(n))';
          else
            x = selc{n}; % Extract and reshape as a vector.
          end
          s = count; s(n) = []; % Remove i-th dimension
          x = reshape(x(:,ones(1,prod(s))),[length(x) s]); % Expand x
          x = permute(x,[2:dim-n+1 1 dim-n+2:dim]); % Permute to i'th dimension
          sel(n,:) = x(:);
        end
        H5S.select_elements(space,'H5S_SELECT_SET',sel-1);
      end

      % reserve space for reading
      mem_space = H5S.create_simple(dim, count, []);

      % prepare input
      if prod(count) > 1
        if numel(B) == 1
          B = repmat(B,count);
        else
          % shuffle input to match selection
          B = B(idxc{end:-1:1});
        end
      end

      % write the actual data
      H5D.write(self.dataset_id,'H5ML_DEFAULT',mem_space,space,'H5P_DEFAULT',B);

      H5S.close(space);
      H5S.close(mem_space);
    end

    function varargout = size(self, dim)
      self.open()
      sz = self.propsize;
      if nargout > 1
        varargout = num2cell(sz);
      elseif nargin > 1
        varargout = {sz(dim)};
      else
        varargout = {sz};
      end
    end

    % overloading numel causes problems with builtin subsref
%     function out = numel(self)
%       out = 1;%prod(size(self));
%     end

    function out = length(self)
      out = max(size(self));
    end

    function out = end(self, dim, n_dim)
      out = size(self,dim);
    end

    function out = vertcat(varargin)
      error('array of hdf5prop objects is not allowed')
    end

    function out = horzcat(varargin)
      error('array of hdf5prop objects is not allowed')
    end

    function out = cat(varargin)
      error('array of hdf5prop objects is not allowed')
    end

    function out = double( self )
      out = double(self.get_data());
    end

    function out = eq( self, other )
      out = eq(self.get_data(), other());
    end

    function out = ne( self, other )
      out = ne(self.get_data(), other());
    end

    function out = le( self, other )
      out = le(self.get_data(), other());
    end

    function out = ge( self, other )
      out = qe(self.get_data(), other());
    end

    function out = lt( self, other )
      out = lt(self.get_data(), other());
    end

    function out = gt( self, other )
      out = gt(self.get_data(), other());
    end
  end
end