function [output] = ft_volumelookup(cfg, volume)

% FT_VOLUMELOOKUP can be used in to combine an anatomical or functional
% atlas with the source reconstruction results. You can use it for forward
% and reverse lookup.
%
% Given the region of interest (ROI) as anatomical or functional label, it
% looks up the locations and creates a mask (as a binary volume) based on
% the label. Given the ROI as point in the brain, it creates a sphere or
% box around that point. In these two case the function is to be used as:
%   mask = ft_volumelookup(cfg, volume)
%
% Given a binary volume that indicates a ROI or a point of interest (POI),
% it looks up the corresponding anatomical or functional labels from the
% atlas. In this case the function is to be used as:
%    labels = ft_volumelookup(cfg, volume)
%
% In both cases the input volume can be:
%   mri    is the output of FT_READ_MRI source is the output of FT_SOURCEANALYSIS
%   stat   is the output of FT_SOURCESTATISTICS
%
% The configuration options for a mask according to an atlas:
%   cfg.atlas               = string, filename of atlas to use, see FT_READ_ATLAS
%   cfg.roi                 = string or cell-array of strings, ROI from anatomical atlas
%
% The configuration options for a spherical/box mask around a POI:
%   cfg.roi                 = Nx3 vector, coordinates of the POI
%   cfg.sphere              = radius of each sphere in cm/mm dep on unit of input
%   cfg.box                 = Nx3 vector, size of each box in cm/mm dep on unit of input
%   cfg.round2nearestvoxel  = 'yes' or 'no' (default = 'no'), voxel closest to point of interest is calculated
%                             and box/sphere is centered around coordinates of that voxel
%
% The configuration options for labels from a mask:
%   cfg.atlas               = string, filename of atlas to use, see FT_READ_ATLAS
%   cfg.maskparameter       = string, field in volume to be looked up, data in field should be logical
%   cfg.minqueryrange       = number, should be odd and <= to maxqueryrange (default = 1)
%   cfg.maxqueryrange       = number, should be odd and >= to minqueryrange (default = 1)
%
% The configuration options for labels around POI:
%   cfg.output              = 'single' always outputs one label; if several POI are provided, they are considered together as describing a ROI (default)
%                             'multiple' outputs one label per POI (e.g., choose to get labels for different electrodes)
%   cfg.roi                 = Nx3 vector, coordinates of the POI
%   cfg.atlas               = string, filename of atlas to use, see FT_READ_ATLAS
%   cfg.minqueryrange       = number, should be odd and <= to maxqueryrange (default = 1)
%   cfg.maxqueryrange       = number, should be odd and >= to minqueryrange (default = 1)
%   cfg.querymethod         = 'sphere' searches voxels around the ROI in a sphere (default)
%                           = 'cube' searches voxels around the ROI in a cube
%   cfg.round2nearestvoxel  = 'yes' or 'no', voxel closest to POI is calculated (default = 'yes')
%
% The label output has a field "names", a field "count" and a field "usedqueryrange".
% To get a list of areas of the given mask you can do for instance:
%      [tmp ind] = sort(labels.count,1,'descend');
%      sel = find(tmp);
%      for j = 1:length(sel)
%        found_areas{j,1} = [num2str(labels.count(ind(j))) ': ' labels.name{ind(j)}];
%      end
% In the "found_areas" variable you can then see how many times which labels are
% found. Note that in the AFNI brick one location can have 2 labels.
%
% Dependent on the input coordinates and the coordinates of the atlas, the
% input MRI is transformed betweem MNI and Talairach-Tournoux coordinates
% See http://www.mrc-cbu.cam.ac.uk/Imaging/Common/mnispace.shtml for more details.
%
% See http://www.fieldtriptoolbox.org/template/atlas for a list of templates and
% atlasses that are included in the FieldTrip release.
%
% See also FT_READ_ATLAS, FT_SOURCEPLOT

% Copyright (C) 2008-2017, Robert Oostenveld, Ingrid Nieuwenhuis
% Copyright (C) 2013, Jan-Mathijs Schoffelen
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

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

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

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

% check if the input data is valid for this function
% additional checking of the input data is done further down
volume = ft_checkdata(volume, 'datatype', {'volume', 'source'}, 'feedback', 'yes', 'hasunit', 'yes', 'hascoordsys', 'yes');

% the handling of the default cfg options is done further down
cfg.minqueryrange      = ft_getopt(cfg,'minqueryrange', 1);
cfg.maxqueryrange      = ft_getopt(cfg,'maxqueryrange', 1);
cfg.output             = ft_getopt(cfg,'output', []); % in future, cfg.output could be extended to support both 'label' and 'mask'

% ensure that old and unsupported options are not being relied on by the end-user's script
% instead of specifying cfg.coordsys, the user should specify the coordsys in the data
cfg = ft_checkconfig(cfg, 'forbidden', {'units', 'coordsys', 'inputcoord', 'inputcoordsys', 'coordinates'});
cfg = ft_checkconfig(cfg, 'renamedval', {'output', 'label', 'single'});

roi2mask   = 0;
mask2label = 0;
roi2label  = 0;
if isfield(cfg, 'roi') && ~isempty(cfg.output)
  roi2label = 1;
elseif isfield(cfg, 'roi')
  roi2mask = 1;
elseif isfield(cfg, 'maskparameter')
  mask2label = 1;
else
  ft_error('you should either specify cfg.roi, or cfg.maskparameter')
end

if roi2mask
  % only for volume data
  volume = ft_checkdata(volume, 'datatype', 'volume');

  cfg.round2nearestvoxel = ft_getopt(cfg, 'round2nearestvoxel', 'no');

  isatlas = iscell(cfg.roi) || ischar(cfg.roi);
  ispoi   = isnumeric(cfg.roi);
  if isatlas+ispoi ~= 1
    ft_error('do not understand cfg.roi')
  end

  if isatlas
    ft_checkconfig(cfg, 'forbidden', {'sphere', 'box'}, 'required',  {'atlas'});
  elseif ispoi
    ft_checkconfig(cfg, 'forbidden', {'atlas'});
    if isempty(ft_getopt(cfg, 'sphere')) && isempty(ft_getopt(cfg, 'box'))
      ft_error('you should either specify cfg.sphere or cfg.box')
    end
  end

elseif mask2label || roi2label
  % convert to source representation, this is easier to work with
  volume = ft_checkdata(volume, 'datatype', 'source');
  ft_checkconfig(cfg, 'required', {'atlas'});

  if cfg.minqueryrange > cfg.maxqueryrange
    ft_error('maxqueryrange should be superior or equal to minqueryrange');
  end

  if rem(cfg.minqueryrange, 2) == 0 || rem(cfg.maxqueryrange, 2) == 0
    ft_error('incorrect query range, should be odd numbers');
  end

  cfg.round2nearestvoxel = ft_getopt(cfg, 'round2nearestvoxel', 'yes');
  cfg.querymethod        = ft_getopt(cfg, 'querymethod', 'sphere');
end

if roi2mask
  % determine location of each anatomical voxel in its own voxel coordinates
  dim = volume.dim;
  i = 1:dim(1);
  j = 1:dim(2);
  k = 1:dim(3);
  [I, J, K] = ndgrid(i, j, k);
  ijk = [I(:) J(:) K(:) ones(prod(dim),1)]';
  % determine location of each anatomical voxel in head coordinates
  xyz = volume.transform * ijk; % note that this is 4xN

  if isatlas
    if ischar(cfg.atlas)
      % assume it to represent a filename
      atlas = ft_read_atlas(cfg.atlas);
    else
      % assume cfg.atlas to be a struct
      atlas = cfg.atlas;
    end

    % determine which field(s) to use to look up the labels,
    % and whether these are boolean or indexed
    fn = fieldnames(atlas);
    isboolean = false(numel(fn),1);
    isindexed = false(numel(fn),1);
    for i=1:length(fn)
      if islogical(atlas.(fn{i})) && isequal(size(atlas.(fn{i})), atlas.dim)
        isboolean(i) = true;
      elseif isnumeric(atlas.(fn{i})) && isequal(size(atlas.(fn{i})), atlas.dim)
        isindexed(i) = true;
      end
    end
    if any(isindexed)
      % let the indexed prevail
      fn = fn(isindexed);
      isindexed = 1;
    elseif any(isboolean)
      % use the boolean
      fn = fn(isboolean);
      isindexed = 0;
    end

    if ischar(cfg.roi)
      cfg.roi = {cfg.roi};
    end

    if isindexed
      sel = zeros(0,2);
      for m = 1:length(fn)
        for i = 1:length(cfg.roi)
          tmp = find(strcmp(cfg.roi{i}, atlas.([fn{m},'label'])));
          sel = [sel; tmp m*ones(numel(tmp),1)];
        end
      end
      fprintf('found %d matching anatomical labels\n', size(sel,1));

      % this is to accommodate for multiple parcellations:
      % the brick refers to the parcellationname
      % the value refers to the value within the given parcellation
      brick = sel(:,2);
      value = sel(:,1);

      % convert between MNI head coordinates and TAL head coordinates
      % coordinates should be expressed compatible with the atlas
      if     strcmp(volume.coordsys, 'mni') && strcmp(atlas.coordsys, 'tal')
        xyz(1:3,:) = mni2tal(xyz(1:3,:));
      elseif strcmp(volume.coordsys, 'mni') && strcmp(atlas.coordsys, 'mni')
        % nothing to do
      elseif strcmp(volume.coordsys, 'tal') && strcmp(atlas.coordsys, 'tal')
        % nothing to do
      elseif strcmp(volume.coordsys, 'tal') && strcmp(atlas.coordsys, 'mni')
        xyz(1:3,:) = tal2mni(xyz(1:3,:));
      elseif ~strcmp(volume.coordsys, atlas.coordsys)
        ft_error('the mismatch between the coordinate system in the atlas and the coordinate system in the data cannot be resolved');
      end

      % determine location of each anatomical voxel in atlas voxel coordinates
      ijk = atlas.transform \ xyz;
      ijk = round(ijk(1:3,:))';

      inside_vol = ijk(:,1)>=1 & ijk(:,1)<=atlas.dim(1) & ...
        ijk(:,2)>=1 & ijk(:,2)<=atlas.dim(2) & ...
        ijk(:,3)>=1 & ijk(:,3)<=atlas.dim(3);
      inside_vol = find(inside_vol);

      % convert the selection inside the atlas volume into linear indices
      ind = sub2ind(atlas.dim, ijk(inside_vol,1), ijk(inside_vol,2), ijk(inside_vol,3));

      brick_val = cell(1,numel(brick));
      % search the bricks for the value of each voxel
      for i=1:numel(brick_val)
        brick_val{i} = zeros(prod(dim),1);
        brick_val{i}(inside_vol) = atlas.(fn{brick(i)})(ind);
      end

      mask = zeros(prod(dim),1);
      for i=1:numel(brick_val)
        %fprintf('constructing mask for %s\n', atlas.descr.name{sel(i)});
        mask = mask | (brick_val{i}==value(i));
      end
    else
      ft_error('support for atlases that have a probabilistic segmentationstyle is not supported yet');
      % NOTE: this may be very straightforward indeed: the mask is just the logical "or" of the specified ROIs.
    end

  elseif ispoi

    if istrue(cfg.round2nearestvoxel)
      for i=1:size(cfg.roi,1)
        cfg.roi(i,:) = poi2voi(cfg.roi(i,:), xyz);
      end
    end

    % sphere(s)
    if isfield(cfg, 'sphere')
      mask = zeros(1,prod(dim));
      for i=1:size(cfg.roi,1)
        dist = sqrt( (xyz(1,:) - cfg.roi(i,1)).^2 + (xyz(2,:) - cfg.roi(i,2)).^2 + (xyz(3,:) - cfg.roi(i,3)).^2 );
        mask = mask | (dist <= cfg.sphere(i));
      end
      % box(es)
    elseif isfield(cfg, 'box')
      mask = zeros(1, prod(dim));
      for i=1:size(cfg.roi,1)
        mask = mask | ...
          (xyz(1,:) <= (cfg.roi(i,1) + cfg.box(i,1)./2) & xyz(1,:) >= (cfg.roi(i,1) - cfg.box(i,1)./2)) & ...
          (xyz(2,:) <= (cfg.roi(i,2) + cfg.box(i,2)./2) & xyz(2,:) >= (cfg.roi(i,2) - cfg.box(i,2)./2)) & ...
          (xyz(3,:) <= (cfg.roi(i,3) + cfg.box(i,3)./2) & xyz(3,:) >= (cfg.roi(i,3) - cfg.box(i,3)./2));
      end
    end
  end

  mask = reshape(mask, dim);
  fprintf('%i voxels in mask, which is %.3f %% of total volume\n', sum(mask(:)), 100*mean(mask(:)));
  output = mask;

elseif mask2label || roi2label
  if ischar(cfg.atlas)
    % assume it to represent a filename
    atlas = ft_read_atlas(cfg.atlas);
  else
    % assume cfg.atlas to be a struct
    atlas = cfg.atlas;
  end

  % determine which field(s) to use to look up the labels,
  % and whether these are boolean or indexed
  fn = fieldnames(atlas);
  isboolean = false(numel(fn),1);
  isindexed = false(numel(fn),1);
  for i=1:length(fn)
    if islogical(atlas.(fn{i})) && isequal(size(atlas.(fn{i})), atlas.dim)
      isboolean(i) = true;
    elseif isnumeric(atlas.(fn{i})) && isequal(size(atlas.(fn{i})), atlas.dim)
      isindexed(i) = true;
    end
  end
  if any(isindexed)
    % let the indexed prevail
    fn = fn(isindexed);
    isindexed = 1;
  elseif any(isboolean)
    % use the boolean
    fn = fn(isboolean);
    isindexed = 0;
  end

  labels.name = cell(0,1);
  for k = 1:numel(fn)
    % ensure that they are concatenated as column
    tmp = atlas.([fn{k},'label']);
    labels.name = cat(1, labels.name(:), tmp(:));
  end
  labels.name{end+1} = 'no_label_found';
  labels.count = zeros(length(labels.name),1);
  for iLab = 1:length(labels.name)
    labels.usedqueryrange{iLab} = [];
  end

  if mask2label
    sel = find(volume.(cfg.maskparameter)(:));
  elseif roi2label
    if istrue(cfg.round2nearestvoxel)
      % determine location of each anatomical voxel in head coordinates
      xyz = [volume.pos ones(size(volume.pos,1),1)]'; % note that this is 4xN
      nSel = size(cfg.roi, 1);
      for i=1:nSel
        cfg.roi(i,:) = poi2voi(cfg.roi(i,:), xyz);
      end
    end % round2nearestvoxel
    sel = zeros(size(cfg.roi,1), 1);
    for i = 1:size(cfg.roi,1)
        sel(i) = find(volume.pos(:, 1) == cfg.roi(i, 1) & volume.pos(:, 2) == cfg.roi(i, 2) & volume.pos(:, 3) == cfg.roi(i, 3));
    end
  end
  if strcmp(cfg.output, 'multiple')
      labels = repmat(labels, length(sel), 1);
  end
  for iVox = 1:length(sel)
      label = {};
      for qr = cfg.minqueryrange:2:cfg.maxqueryrange
          if isempty(label)
              label = atlas_lookup(atlas, volume.pos(sel(iVox), :), 'coordsys', volume.coordsys, 'queryrange', qr, 'method', cfg.querymethod);
              usedQR = qr;
          end
      end

      if isempty(label)
          label = {'no_label_found'};
      elseif length(label) == 1
          label = {label};
      end

      if strcmp(cfg.output, 'multiple')
          iLabOut = iVox;
      else
          iLabOut = 1;
      end
      ind_lab = [];
      for iLab = 1:length(label)
          ind_lab = find(strcmp(label{iLab}, labels(iLabOut).name));
          labels(iLabOut).count(ind_lab) = labels(iLabOut).count(ind_lab)+1; % labels.count should give the number of times a label was found within a query range
      end

      %     labels.count(ind_lab) = labels.count(ind_lab) + (1/length(ind_lab));
      %     ^this gives each label a weight depending on the number of
      %     labels found within the query range. Using this method, all labels
      %     that were found will have the same number listed for labels.count,
      %     which defeats the point of the labels.count field as I understand it
      for iFoundLab = 1:length(ind_lab)
          if isempty(labels(iLabOut).usedqueryrange{ind_lab(iFoundLab)})
              labels(iLabOut).usedqueryrange{ind_lab(iFoundLab)} = usedQR;
          else
              labels(iLabOut).usedqueryrange{ind_lab(iFoundLab)} = [labels(iLabOut).usedqueryrange{ind_lab(iFoundLab)} usedQR];
          end
      end
  end %iVox

  output = labels;

end

% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble provenance

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION point of interest to voxel of interest
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function voi = poi2voi(poi, xyz)
xmin = min(abs(xyz(1,:) - poi(1))); xcl = round(abs(xyz(1,:) - poi(1))) == round(xmin);
ymin = min(abs(xyz(2,:) - poi(2))); ycl = round(abs(xyz(2,:) - poi(2))) == round(ymin);
zmin = min(abs(xyz(3,:) - poi(3))); zcl = round(abs(xyz(3,:) - poi(3))) == round(zmin);
xyzcls = xcl + ycl + zcl; ind_voi = xyzcls == 3;
if sum(ind_voi) > 1
  fprintf('%i voxels at same distance of poi, taking first voxel\n', sum(ind_voi))
  ind_voi_temp = find(ind_voi); ind_voi_temp = ind_voi_temp(1);
  ind_voi = zeros(size(ind_voi));
  ind_voi(ind_voi_temp) = 1;
  ind_voi = logical(ind_voi);
end
voi = xyz(1:3,ind_voi);
fprintf('coordinates of voi: %.1f  %.1f %.1f\n', voi(1), voi(2), voi(3));


function mv = mv_conditionsMenu(mv,hfig);
%
% mv = mv_conditionsMenu(mv,hfig);
%
% Add a menu for toggling on/off conditions
% in the multi voxel UI.
%
% ras, 04/05
% ras, 03/06: used to ignore baseline condition, now adds it.
if ieNotDefined('hfig')
    hfig = gcf;
end

if ieNotDefined('mv')
    mv = get(hfig,'UserData');
end

condNums = mv.trials.condNums;
condNames = mv.trials.condNames;
condColors = mv.trials.condColors;

% (callback will be as for the legend callback:
% umtoggle(gcbo);
% timeCourseUI;
mv.ui.condMenu = uimenu('ForegroundColor', 'k', 'Label', 'Conditions', 'Separator', 'on');
accelChars = '0123456789-=';
cbStr=['umtoggle(gcbo); multiVoxelUI;'];
for i = 1:length(condNames)
    if i < length(accelChars)
        accel = accelChars(i);
    else
        accel = '';
    end
    if(isempty(mv.trials.condNames{i}))
        mv.trials.condNames{i}=num2str(i);
    end

    hc(i) = uimenu(mv.ui.condMenu,'Label',condNames{i},...
                     'Separator','off',...
                     'Checked','on',...
                     'Accelerator',accel,...
                     'Tag',num2str(condNums(i)),...
                     'UserData',condColors{i},'Callback',cbStr);
end

set(hc(1), 'Checked', 'off'); % unselect baseline condition

mv.ui.condMenuHandles = hc;

set(hfig,'UserData',mv);

return


function gprs = printGPRForRxns(model,rxnIDs, printLevel)
% Print the GPRs (in textual format) for the given RxnIDs
% USAGE:
%
%    gprs = printGPRForRxns(model,rxnID)
%
% INPUTS:
%    model:             The model to retrieve the GPR rules from
%    rxnIDs:            The reaction IDs to obtain the GPR rules for
%
% OPTIONAL INPUTS:
%
%    printLevel:        Whether to print out the GPRs. If printLevel is 0,
%                       the function will only return the Strings in a cell
%                       array.
%
% OUTPUTS:
%
%    gprs:              The textual GPR Rules
%
% Authors:
%
%    Thomas Pfau Dec 2017

if ~isnumeric(rxnIDs)
    rxnPos = findRxnIDs(model,rxnIDs);
else
    %We got a list of positions
    rxnPos = rxnIDs;
    if any(rxnPos > numel(model.rxns))
        error('Some indices provided are larger than the number of reactions.');
    end
end

if any(rxnPos == 0)
    error('The following reaction IDs are not part of this model:\n%s\n',strjoin(rxnIDs(rxnPos==0),'; '));
end

if ~exist('printLevel','var')
    printLevel = 1;
end

if isfield(model,'grRules')
    gprs = model.grRules(rxnPos);
else
    if isfield(model,'rules') && isfield(model,'genes')
        gprs = strrep(model.rules(rxnPos),'|','or');
        gprs = strrep(gprs,'&','and');
        gprs = regexprep(gprs,'x\(([0-9]+)\)','${model.genes{str2num($1)}}');
    else
        gprs = repmat({''},size(rxnPos));
    end
end
rxnNames = model.rxns(rxnPos);
maxRxnLength = num2str(max(cellfun(@length,rxnNames))+2);

if printLevel > 0
    for i = 1:numel(rxnPos)
        fprintf(['%-' maxRxnLength 's:\t%s\n'],rxnNames{i},gprs{i});
    end
end


function newmodel = addMultipleReactions(model,rxnIDs,metList,Stoichiometries,varargin)
% Add multiple Reactions to the model. In contrast to addReaction, this
% function allows the addition of multiple reactions at once but requires
% all used metabolites to be already present in the model.
%
% USAGE:
%
%    newmodel = addMultipleReactions(model,rxnIDs,metList,Stoichiometries,varargin)
%
% INPUTS:
%    model:             The model to add the Metabolite batch to.
%    rxnIDs:            The IDs of the reactions that shall be added (cell Array).
%    metList:           A list of metabolites for which stoichiometric coefficients are given
%    Stoichiometries:   A Stoichiometric matrix of dimension
%                       numel(metList) x numel(rxnID).
% OPTIONAL INPUTS:
%    varargin:          fieldName, Value pairs. The given fields will be set
%                       according to the values. Only defined COBRA fields may be
%                       used. The following fields will be ignored (as they
%                       are dependent on the existing model structure)
%
%                        - `S`:  this is being resolved by the metList/Stoichiometries combination
%                        - `rxnGeneMat`: This depends on the original model structure, and is thus nor considered.
%
%                       You can provide GPR rules in different ways to
%                       this function:
%
%                        - By providing a grRules array in the varargin
%                        - By providing a genes field in the varargin and a
%                          corresponding rules field
%                        - By providing a rules field, that directly refers
%                          to the genes in the model.
%
%                       Examples of parameters are:
%
%                        - 'rxnNames',{'RuBisCo','Transketloase'}
%                        - 'rxnKEGGID,{'R00024','R01641'}
%                        - 'lb',[0,-1000]
%                        - or any field name associated with reactions (except
%                          for S and rxnGeneMat) as defined in the COBRA
%                          Model field definitions.
%                       It is also possible to add a printout to the
%                       function by using the 'printLevel', 1 argument.
%
%
% OUTPUTS:
%
%    newmodel:     The model structure with the additional reactions.
%
% EXAMPLE:
%
%    % To add the following reactions, with lower and upper bounds:
%    % ExA: A <-> ; ATob: A <-> B ; BToC: B <-> C
%    model = addMultipleReactions(model, {'ExA','ATob','BToC'}, {'A','b','c'}, [1 -1 0; 0,1,-1;0,0,1], 'lb', [-50,30,1], 'ub', [0,60,15])
%    % To add them with GPRs in text form:
%    % ExA: A <-> ; ATob: A <-> B ; BToC: B <-> C
%    model = addMultipleReactions(model, {'ExA','ATob','BToC'}, {'A','b','c'}, [1 -1 0; 0,1,-1;0,0,1], 'grRules', {'G1 or G2', 'G3 and G4',''})
%
%
%    % To add them with the same GPRs in logical format assuming that model.genes is {'G1';'G2';'G3';'G4'}:
%    % ExA: A <-> ; ATob: A <-> B ; BToC: B <-> C
%    model = addMultipleReactions(model, {'ExA','ATob','BToC'}, {'A','b','c'}, [1 -1 0; 0,1,-1;0,0,1], 'rules', {'x(1) | x(2)', 'x(3) and x(4)',''})
%    % To add them with the same GPRs in logical format without assuming anything for the model:
%    % ExA: A <-> ; ATob: A <-> B ; BToC: B <-> C
%    model = addMultipleReactions(model, {'ExA','ATob','BToC'}, {'A','b','c'}, [1 -1 0; 0,1,-1;0,0,1], 'rules', {'x(3) | x(2)', 'x(4) & x(1)',''}, 'genes', {'G4';'G2';'G1';'G3'})
%
%    % Print the above:
%    model = addMultipleReactions(model, {'ExA','ATob','BToC'}, {'A','b','c'}, [1 -1 0; 0,1,-1;0,0,1], 'printLevel', 1, 'rules', {'x(3) | x(2)', 'x(4) & x(1)',''}, 'genes', {'G4';'G2';'G1';'G3'})

[metPres,metPos] = ismember(metList,model.mets);
if any(metPos == 0)
    error('The following Metabolites are not part of the model:\n%s',strjoin(metList(metPos==0)));
end

% check rxnIDs
if ~iscell(rxnIDs)
    error('rxnIDs has to be a cell array of strings!')
end

%[bool,existing] = checkIDsForTypeExist(model,ids,basefield)
% Check whether the given IDs exist for the given type of base field. Base
% fields include rxns, mets, genes, comps, proteins, ctrs, evars. ctrs/mets
% as well as rxns/evars will be considered as a combined field.
% OUTPUT:
%    bool:                Boolean vector true if any of the IDs exist.
%    existing:            Unique set of pre-existing IDs for this base field.
[bool,existing] = checkIDsForTypeExist(model,rxnIDs,'rxns');

if bool
    if exist('varargin','var')
        warning('The following reaction IDs are already IDs of variables in the model:\n%s', strjoin(existing,'\n'));
    else
        %try to recover rather than fail ungracefully, passes
        %testBatchAddition and testMergeTwoModels!

        % C = setdiff(A,B) for vectors A and B, returns the values in A that
        % are not in B with no repetitions. C will be sorted.
        rxnIDs = setdiff(rxnIDs,existing);
        if isempty(rxnIDs)
            warning('All of the reaction IDs provided are already IDs of reactions in the model, so nothing to add.:\n%s', strjoin(existing,'\n'));
            newmodel = model;
            return
        else
            duplicateRxnIDBool = ismember(existing,rxnIDs);
            %pare down the stoichiometries
            Stoichiometries = Stoichiometries(:,duplicateRxnIDBool);
            warning('The following reaction IDs are already IDs of reactions in the model and will not be added:\n%s', strjoin(existing,'\n'));
        end
    end
end

if numel(unique(metList)) < numel(metList)
    error('Duplicate Metabolite ID detected.');
end

printLevel = 0;

nRxns = numel(model.rxns);

%We extract those fields which are either associated with the rxns field
%(2nd or 3rd column contains 'rxns' in the definitions), and we also look up which fields are in the model and
%associated with the rxns field (from the sizes and definitions)
fieldDefs = getDefinedFieldProperties();
fieldDefs = fieldDefs(cellfun(@(x) strcmp(x,'rxns'), fieldDefs(:,2)) | cellfun(@(x) strcmp(x,'rxns'), fieldDefs(:,3)));
modelRxnFields = getModelFieldsForType(model,'rxns');
model.rxns= [model.rxns;columnVector(rxnIDs)];

for field = 1:2:numel(varargin)
    cfield = varargin{field};
    values = varargin{field+1};
    %Anything thats not a model field or not a specialised field is
    %ignored.
    if strcmp(cfield,'printLevel')
        printLevel = varargin{field+1};
        continue;
    end
    if any(ismember({'rules','genes'},cfield))
        continue; %Don't throw a warning here.
    end
    if any(ismember({'S','rxnGeneMat'},cfield)) || (~any(ismember(fieldDefs(:,1),cfield)) && ~any(ismember(modelRxnFields,cfield)))
        warning('Field %s is excluded.',cfield);
        continue;
    end
    % if we reach this point we can transpose it savely
    values = columnVector(values);
    if any(size(values) ~= size(columnVector(rxnIDs)))
        if size(values,2) ~=1
            error('The supplied field %s has dimension %ix%i. But should be a %ix1 vector',cfield,size(values),numel(rxnIDs));
        else
            error('The supplied field %s has %i elements but it should have %i elements, one for each element in rxnIDs.',cfield,numel(values),numel(rxnIDs));
        end
    end
    if ~isfield(model,cfield)
        model = createEmptyFields(model,cfield);
        model.(cfield)((end-numel(varargin{field+1})+1):end) = columnVector(values);
    else
        model.(cfield) = [model.(cfield);columnVector(values)];
    end
end
newmodel = extendModelFieldsForType(model,'rxns','originalSize',nRxns);
%Now, we have extended the S matrix. So lets fill it.
newmodel.S(metPos,(nRxns+1):end) = Stoichiometries;

if isfield(model,'grRules') && ~any(ismember(varargin(1:2:end),'rules')) %There is a grRules field, and no Rules are provided.
    rulesToUpdate = ~cellfun(@isempty, model.grRules(nRxns+1:end));
    if any(rulesToUpdate)
        %We have non Empty grRules in the input. Parse them.
        newgrRules = nRxns + find(rulesToUpdate);
        for i = 1:numel(newgrRules)
            newmodel = changeGeneAssociation(newmodel,newmodel.rxns{newgrRules(i)},newmodel.grRules{newgrRules(i)});
        end
    end
end

if any(ismember(varargin(1:2:end),'rules'))
    %We have a rules field. First, reset any grRules
    if isfield(model,'grRules')
        newmodel.grRules(nRxns+1:end) = {''};
    end
    %Now, we have to check, if there is a gene list provided:
    if any(ismember(varargin(1:2:end),'genes'))
        %If genes are provided, we have to A: find the overlap, be
        %merge the genes and C adjust the rules.
        nGenes = length(newmodel.genes);
        genePos = 2 * find(ismember(varargin(1:2:end),'genes'));
        rulesPos = 2 * find(ismember(varargin(1:2:end),'rules'));
        genes = varargin{genePos};
        rules = varargin{rulesPos};
        if ~any(size(rules) == numel(rxnIDs)) %something must fit
            error('Size of field rules does not fit to the rxnList size');
        end
        %Find all genes (and their positions) which are already in the model.
        [genePres,genePos] = ismember(genes,newmodel.genes);
        additionalGenes = sum(~genePres);
        newmodel = addGenes(newmodel,genes(~genePres));
        genePos(~genePres) = nGenes+(1:additionalGenes);
        %now, replace the ids.
        genePos = cellfun(@(x) strcat('x(', num2str(x),')'), num2cell(genePos),'Uniformoutput',false);
        rules = regexprep(rules,'x\(([0-9]+)\)','${genePos{str2num($1)}}');
        newmodel.rules(nRxns+1:length(newmodel.rxns)) = rules;
    else
        rulesPos = 2 * find(ismember(varargin(1:2:end),'rules'));
        if ~isfield(newmodel,'rules') %If rules does not exist, create it.
            newmodel = generateRules(newmodel);
        end
        if ~any(size(varargin{rulesPos}) == numel(rxnIDs)) %something must fit
            error('Size of field rules does not fit to the rxnList size');
        end
        newmodel.rules((nRxns+1):end) = columnVector(varargin{rulesPos});
    end

    %Update the corresponding grRules fields and rxnGeneMat fields.
    %create the grRules
    rulesToUpdate = nRxns + find(~cellfun(@isempty, model.rules(nRxns+1:end)));
    if ~isempty(rulesToUpdate)
        if isfield(newmodel,'grRules')
            cGrRules = newmodel.grRules(nRxns+1:end);
            cGrRules = strrep(cGrRules,'|','or');
            cGrRules = strrep(cGrRules,'&','and');
            cGrRules = regexprep(cGrRules,'x\(([0-9]+)\)','${model.genes{str2num($1)}}');
            newmodel.grRules(nRxns+1:end) = cGrRules;
        end
        %Also update the rxnGeneMat, if present
        if isfield(model,'rxnGeneMat')
            nGenes = length(newmodel.genes);
            for i = 1:numel(rulesToUpdate)
                assoc = false(1,nGenes);
                %get all gene positions
                pos = regexp(newmodel.rules{rulesToUpdate(i)},'x\((?<pos>[0-9]+)\)','names');
                genePos = cellfun(@str2num, {pos.pos});
                assoc(genePos) = true;
                if ~issparse(newmodel.rxnGeneMat)
                    newmodel.rxnGeneMat=sparse(newmodel.rxnGeneMat);
                end
                newmodel.rxnGeneMat(rulesToUpdate(i),:) = sparse(assoc);
            end
        end
    end

end
if printLevel > 0
    fprintf('addMultipleReactions: Adding the following reactions to the model:\n');
    printRxnFormula(newmodel,rxnIDs);
end


function colormap_centered(hax)

if nargin < 1
  hax = gca();
end

for n=1:length(hax)
  cl = get(hax(n), 'clim');
  l = max(abs(cl));
  cl = [-l l];
  set(hax(n), 'clim', cl);
end


classdef LvdData < matlab.mixin.SetGet
    %LvdData Summary of this class goes here
    %   Detailed explanation goes here

    properties
        script LaunchVehicleScript
        launchVehicle LaunchVehicle
        stateLog LaunchVehicleStateLog
        initStateModel InitialStateModel
        optimizer LvdOptimization
        validation LaunchVehicleDataValidation
        settings LvdSettings
        notes char
        plugins LvdPluginSet
        pluginVars LvdPluginOptimVarSet
        viewSettings LaunchVehicleViewSettings
        groundObjs LaunchVehicleGroundObjectSet
        geometry LvdGeometry
        graphAnalysis LvdGraphicalAnalysis
        sensors LvdSensorSet
        sensorTgts LvdSensorTargetSet

        celBodyData
        ksptotVer char

        userData

        id(1,1) double
    end

    properties(Dependent)
        initialState LaunchVehicleStateLogEntry
    end

    methods(Access = private)
        function obj = LvdData()
            obj.stateLog = LaunchVehicleStateLog(obj);
            obj.validation = LaunchVehicleDataValidation(obj);
            obj.settings = LvdSettings();
            obj.plugins = LvdPluginSet(obj);
            obj.pluginVars = LvdPluginOptimVarSet(obj);
            obj.viewSettings = LaunchVehicleViewSettings(obj);
            obj.groundObjs = LaunchVehicleGroundObjectSet(obj);
            obj.geometry = LvdGeometry(obj);
            obj.graphAnalysis = LvdGraphicalAnalysis(obj);
            obj.sensors = LvdSensorSet(obj);
            obj.sensorTgts = LvdSensorTargetSet(obj);

            obj.id = rand();
        end
    end

    methods
        function initialState = get.initialState(obj)
            initialState = obj.initStateModel.getInitialStateLogEntry();
        end

        function tf = usesStage(obj, stage)
            tf = obj.script.usesStage(stage);
            tf = tf || obj.optimizer.usesStage(stage);
        end

        function tf = usesEngine(obj, engine)
            tf = obj.script.usesEngine(engine);
            tf = tf || obj.optimizer.usesEngine(engine);
        end

        function tf = usesTank(obj, tank)
            tf = obj.script.usesTank(tank);
            tf = tf || obj.optimizer.usesTank(tank);
        end

        function tf = usesEngineToTankConn(obj, engineToTank)
            tf = obj.script.usesEngineToTankConn(engineToTank);
            tf = tf || obj.optimizer.usesEngineToTankConn(engineToTank);
        end

        function tf = usesStopwatch(obj, stopwatch)
            tf = obj.script.usesStopwatch(stopwatch);
            tf = tf || obj.optimizer.constraints.usesStopwatch(stopwatch);
        end

        function tf = usesExtremum(obj, extremum)
            tf = obj.script.usesExtremum(extremum);
            tf = tf || obj.optimizer.usesExtremum(extremum);
        end

        function tf = usesTankToTankConn(obj, tankToTank)
            tf = obj.script.usesTankToTankConn(tankToTank);
        end

        function tf = usesGroundObj(obj, grdObj)
            tf = obj.optimizer.usesGroundObj(grdObj) || ...
                 obj.geometry.usesGroundObj(obj);
        end

        function tf = usesCalculusCalc(obj, calculusCalc)
            tf = obj.script.usesCalculusCalc(calculusCalc);

            tf = tf || obj.optimizer.usesCalculusCalc(calculusCalc);
        end

        function tf = usesPwrSink(obj, powerSink)
            tf = obj.script.usesPwrSink(powerSink);
%             tf = tf || obj.optimizer.usesPwrStorage(powerSink);
        end

        function tf = usesPwrSrc(obj, powerSrc)
            tf = obj.script.usesPwrSrc(powerSrc);
%             tf = tf || obj.optimizer.usesPwrStorage(powerSrc);
        end

        function tf = usesPwrStorage(obj, powerStorage)
            tf = obj.script.usesPwrStorage(powerStorage);
%             tf = tf || obj.optimizer.usesPwrStorage(powerStorage);
        end

        function tf = usesGeometricPoint(obj, point)
            tf = obj.optimizer.usesGeometricPoint(point);
            tf = tf || obj.geometry.usesGeometricPoint(point);
            tf = tf || obj.sensors.usesGeometricPoint(point);
            tf = tf || obj.sensorTgts.usesGeometricPoint(point);
        end

        function tf = usesGeometricVector(obj, vector)
            tf = obj.optimizer.usesGeometricVector(vector);
            tf = tf || obj.geometry.usesGeometricVector(vector);
            tf = tf || obj.sensors.usesGeometricVector(vector);
            tf = tf || obj.sensorTgts.usesGeometricVector(vector);
        end

        function tf = usesGeometricCoordSys(obj, coordSys)
            tf = obj.optimizer.usesGeometricCoordSys(coordSys);
            tf = tf || obj.geometry.usesGeometricCoordSys(coordSys);
            tf = tf || obj.sensors.usesGeometricCoordSys(coordSys);
            tf = tf || obj.sensorTgts.usesGeometricCoordSys(coordSys);
        end

        function tf = usesGeometricRefFrame(obj, refFrame)
            tf = obj.optimizer.usesGeometricRefFrame(refFrame);
            tf = tf || obj.geometry.usesGeometricRefFrame(refFrame);
            tf = tf || obj.graphAnalysis.usesGeometricRefFrame(refFrame);
            tf = tf || obj.sensors.usesGeometricRefFrame(refFrame);
            tf = tf || obj.sensorTgts.usesGeometricRefFrame(refFrame);
        end

        function tf = usesGeometricAngle(obj, angle)
            tf = obj.optimizer.usesGeometricAngle(angle);
            tf = tf || obj.geometry.usesGeometricAngle(angle);
            tf = tf || obj.sensors.usesGeometricAngle(angle);
            tf = tf || obj.sensorTgts.usesGeometricAngle(angle);
        end

        function tf = usesGeometricPlane(obj, plane)
            tf = obj.optimizer.usesGeometricPlane(plane);
            tf = tf || obj.geometry.usesGeometricPlane(plane);
            tf = tf || obj.sensors.usesGeometricPlane(plane);
            tf = tf || obj.sensorTgts.usesGeometricPlane(plane);
        end

        function tf = usesPlugin(obj, plugin)
            tf = obj.optimizer.usesPlugin(plugin);
        end

        function tf = usesSensor(obj, sensor)
            tf = obj.script.usesSensor(sensor);
        end

        function baseFrame = getBaseFrame(obj)
            topLevelBody = obj.celBodyData.getTopLevelBody();
            baseFrame = topLevelBody.getBodyCenteredInertialFrame();
        end

        function createContinuityConstraints(obj, evt, selEvt, refBodyInfo, useTime, timeScale, usePos, posScale, useVel, velScale, useAtt, attScale, useTankMass, tankMassScale)
            arguments
                obj(1,1) LvdData
                evt(1,1) LaunchVehicleEvent
                selEvt(1,1) LaunchVehicleEvent
                refBodyInfo = [];

                useTime(1,1) logical = true;
                timeScale(1,1) double = 1.0;

                usePos(1,1) logical = true;
                posScale(1,1) double = 1.0;

                useVel(1,1) logical = true;
                velScale(1,1) double = 1.0;

                useAtt(1,1) logical = false;
                attScale(1,1) double = 1.0;

                useTankMass(1,1) logical = false;
                tankMassScale(1,1) double = 1.0;
            end

            eventNum = evt.getEventNum();
            selEvtNum = selEvt.getEventNum();

            if(eventNum > selEvtNum)
                refEvt = evt;
                constrEvt = selEvt;
            else
                refEvt = selEvt;
                constrEvt = evt;
            end

            if(isempty(refBodyInfo))
                stateLogEntry = obj.stateLog.getLastStateLogForEvent(refEvt);
                refBodyInfo = stateLogEntry.centralBody;
            end
            frame = refBodyInfo.getBodyCenteredInertialFrame();

            cs = AbstractConstraint.empty(1,0);

            %time
            if(useTime)
                c = GenericMAConstraint('Universal Time', refEvt, 0, 0, [], [], refBodyInfo);
                c.setScaleFactor(timeScale);
                cs(end+1) = c;
            end

            %position
            if(usePos)
                c = GenericMAConstraint('Position Vector (X)', refEvt, 0, 0, [], [], refBodyInfo);
                c.setScaleFactor(posScale);
                cs(end+1) = c;

                c = GenericMAConstraint('Position Vector (Y)', refEvt, 0, 0, [], [], refBodyInfo);
                c.setScaleFactor(posScale);
                cs(end+1) = c;

                c = GenericMAConstraint('Position Vector (Z)', refEvt, 0, 0, [], [], refBodyInfo);
                c.setScaleFactor(posScale);
                cs(end+1) = c;
            end

            %velocity
            if(useVel)
                c = GenericMAConstraint('Velocity Vector (X)', refEvt, 0, 0, [], [], refBodyInfo);
                c.setScaleFactor(velScale);
                cs(end+1) = c;

                c = GenericMAConstraint('Velocity Vector (Y)', refEvt, 0, 0, [], [], refBodyInfo);
                c.setScaleFactor(velScale);
                cs(end+1) = c;

                c = GenericMAConstraint('Velocity Vector (Z)', refEvt, 0, 0, [], [], refBodyInfo);
                c.setScaleFactor(velScale);
                cs(end+1) = c;
            end

            %attitude
            if(useAtt)
                c = RollAngleConstraint(refEvt, 0, 0);
                c.setScaleFactor(attScale);
                cs(end+1) = c;

                c = PitchAngleConstraint(refEvt, 0, 0);
                c.setScaleFactor(attScale);
                cs(end+1) = c;

                c = YawAngleConstraint(refEvt, 0, 0);
                c.setScaleFactor(attScale);
                cs(end+1) = c;
            end

            %tank masses
            if(useTankMass)
                [~,tanks] = obj.launchVehicle.getTanksListBoxStr();

                for(i=1:length(tanks))
                    c = TankMassConstraint(tanks(i), refEvt, 0, 0);
                    c.setScaleFactor(tankMassScale);
                    cs(end+1) = c; %#ok<AGROW>
                end
            end

            for(i=1:length(cs))
                c = cs(i);
                c.evalType = ConstraintEvalTypeEnum.StateComparison;
                c.stateCompType = ConstraintStateComparisonTypeEnum.Equals;
                c.stateCompEvent = constrEvt;
                c.frame = frame;

                obj.optimizer.constraints.addConstraint(c);
            end
        end
    end

    methods(Static)
        function lvdData = getEmptyLvdData()
            lvdData = LvdData();
            lvdData.ksptotVer = getKSPTOTVersionNumStr();
        end

        function lvdData = getDefaultLvdData(celBodyData)
            lvdData = LvdData();

            lvdData.celBodyData = celBodyData;

            simDriver = LaunchVehicleSimulationDriver(lvdData);

            %Set Up Initial Launch Vehicle
            lv = LaunchVehicle.createDefaultLaunchVehicle(lvdData);
            lvdData.launchVehicle = lv;

            %Set Up Initial State
            initBody = LvdData.getDefaultInitialBodyInfo(celBodyData);

            initStateModel = InitialStateModel.getDefaultInitialStateLogModelForLaunchVehicle(lvdData.launchVehicle, initBody);
            lvdData.initStateModel = initStateModel;

            %Set Up Mission Script
            script = LaunchVehicleScript(lvdData, simDriver);
            script.addEvent(LaunchVehicleEvent.getDefaultEvent(script));

            lvdData.script = script;

            %State Log
            lvdData.stateLog = LaunchVehicleStateLog(lvdData);

            %Optimization
            lvdOptim = LvdOptimization(lvdData);

            %Objective Function
%             lvdOptim.objFcn = NoOptimizationObjectiveFcn(lvdOptim, lvdData);
            lvdOptim.objFcn = CompositeObjectiveFcn(GenericObjectiveFcn.empty(1,0), ...
                                                    ObjFcnDirectionTypeEnum.Minimize, ...
                                                    ObjFcnCompositeMethodEnum.Sum, ...
                                                    lvdData.optimizer, lvdData);

            lvdData.optimizer = lvdOptim;

            %set up view profile
            lvdData.viewSettings.selViewProfile.frame = lvdData.initialState.centralBody.getBodyCenteredInertialFrame();

            %set up ground objects
            grndObj = LaunchVehicleGroundObject.getDefaultObj(celBodyData);
            lvdData.groundObjs.addGroundObj(grndObj);
            grndObj.name = 'KSC';
            grndObj.desc = "Kerbin Space Center";

            bfFrame = grndObj.centralBodyInfo.getBodyFixedFrame();

            durToNextWayPt = 3600;
            elemSet = GeographicElementSet(grndObj.initialTime + durToNextWayPt, deg2rad(-0.1025), deg2rad(285.42472), 0.06841, 0, 0, 0, bfFrame);
            wayPt = LaunchVehicleGroundObjectWayPt(elemSet, durToNextWayPt);
            grndObj.wayPts = wayPt;
        end

        function lvdData = getLvdDataFromMfmsOutputs(mfmsOutputs)
            lvdData = LvdData.getDefaultLvdData(mfmsOutputs.celBodyData);
            lvdOptim = lvdData.optimizer;

            wayPtBodies = [mfmsOutputs.wayPtBodies{:}];

            [~, colorEnums] = ColorSpecEnum.getListboxStr();

            %Xfer orbit data
            xferOrbits = mfmsOutputs.xferOrbits; %sma ecc inc raan arg tru tru2 t1 t2 gmu
            xferOrbitDurations = xferOrbits(:,9) - xferOrbits(:,8);

            %Set critical settings
            maxSimDur = 10*(max(xferOrbits(:,9)) - min(xferOrbits(:,8)));
            lvdData.settings.simMaxDur = maxSimDur;
            lvdData.settings.maxScriptPropTime = 10;

            %Set up view profile
            cBodyInfo = wayPtBodies(1).getParBodyInfo();

            viewProfile = lvdData.viewSettings.selViewProfile;
            viewProfile.name = sprintf('%s-centered Inertial Frame', cBodyInfo.name);
            viewProfile.frame = cBodyInfo.getBodyCenteredInertialFrame();
            viewProfile.bodiesToPlot = wayPtBodies;

            %Set Initial State
            departBody = wayPtBodies(1);
            frame = departBody.getBodyCenteredInertialFrame();
            time = xferOrbits(1,8);
            orbit = mfmsOutputs.eOrbit;
            kepElemSet = KeplerianElementSet(time, orbit(1), orbit(2), orbit(3), orbit(4), orbit(5), orbit(8), frame);

            lvdData.initStateModel.orbitModel = kepElemSet;

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %Setup Departure Event
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            event = lvdData.script.evts(1);
            event.name = sprintf('Departure from %s (+)', departBody.name);
            event.execActionsNode = ActionExecNodeEnum.BeforeProp;
            event.colorLineSpec.color = colorEnums(1);
            event.integratorObj = event.ode113Integrator;
            event.integratorObj.options.AbsTol = 1E-7;
            event.integratorObj.options.RelTol = 1E-7;
            event.integratorObj.options.NormControl = false;

            %%%Set up termination condition
            dur = xferOrbitDurations(1)/2;
            termCond = EventDurationTermCondition(dur); %/2 so that we can meet a patch point in the middle
            event.termCond = termCond;

            var = termCond.getNewOptVar();
            lvdOptim.vars.addVariable(var);

            var.lb = 0.50*dur;
            var.ub = 1.50*dur;
            var.useTf = true;

            %%%Set up delta-v action
            deltaV = mfmsOutputs.dVDepartVectNTW;
            deltaVAction = AddDeltaVAction(deltaV, DeltaVFrameEnum.OrbitNtw, true);
            event.addAction(deltaVAction);
            deltaVAction.event = event;

            var = AddDeltaVActionVariable(deltaVAction);
            lvdOptim.vars.addVariable(var);
            var.lb = deltaV - 0.50*abs(deltaV);
            var.ub = deltaV + 0.50*abs(deltaV);
            var.varDVx = true;
            var.varDVy = true;
            var.varDVz = true;

            %%%Generate active vars cache
            event.clearActiveOptVarsCache();
            event.hasActiveOptVars();

            %%%Set previous forward prop event
            prevFwdPropEvent = event;

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %Do gravity assist maneuvers
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            for(i=2:length(wayPtBodies)-1)
                bodyInfo = wayPtBodies(i);
                frame = bodyInfo.getBodyCenteredInertialFrame();
                time = xferOrbits(i,8);

                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                %Set up periapsis state event
                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                eventPeri = LaunchVehicleEvent(lvdData.script);
                lvdData.script.addEvent(eventPeri);
                eventPeri.name = sprintf('%s Periapsis State', bodyInfo.name);
                eventPeri.execActionsNode = ActionExecNodeEnum.BeforeProp;
                eventPeri.plotMethod = EventPlottingMethodEnum.DoNotPlot;
                eventPeri.propDir = PropagationDirectionEnum.Forward;

                %%%Event line color
                colorI = mod(2*(i-1), length(colorEnums));
                eventPeri.colorLineSpec.color = colorEnums(colorI);

                %%%Set up termination condition
                termCond = EventDurationTermCondition(0); %/2 so that we can meet a patch point in the middle
                eventPeri.termCond = termCond;

                %%%Set up Set Kinematic State Action
                orbitIn = mfmsOutputs.orbitsIn(i-1,:);
                orbitModel = KeplerianElementSet(time, orbitIn(1), orbitIn(2), orbitIn(3), angleNegPiToPi(orbitIn(4)), angleNegPiToPi(orbitIn(5)), 0, frame);
                orbitModel = orbitModel.convertToUniversalElementSet();
                action = SetKinematicStateAction(lvdData.stateLog, orbitModel);

                action.inheritStateElems = true;
                action.inheritStateElemsFrom = InheritStateEnum.InheritFromLastState;
                eventPeri.addAction(action);
                action.event = eventPeri;
                action.generateLvComponentElements();

                %%%Set up Set Kinematic State variable
                var = SetKinematicStateActionVariable(action);
                lvdOptim.vars.addVariable(var);

                var.lb = 0.50*time;
                var.ub = 1.50*time;
                var.useTf = true;

                var.orbitVar.lb = [min(1.0001,0.75*orbitModel.c3), ...
                                   max(0.75*orbitModel.rP,bodyInfo.radius+bodyInfo.atmohgt), ...
                                   0, ...
                                   -2*pi, ...
                                   -2*pi, ...
                                   0];

                var.orbitVar.ub = [1.25*orbitModel.c3, ...
                                   1.25*orbitModel.rP, ...
                                   pi, ...
                                   2*pi, ...
                                   2*pi, ...
                                   0];

                var.orbitVar.varC3 = true;
                var.orbitVar.varRp = true;
                var.orbitVar.varInc = true;
                var.orbitVar.varRaan = true;
                var.orbitVar.varArg = true;
                var.orbitVar.varTau = false;

                %%%Generate active vars cache
                eventPeri.clearActiveOptVarsCache();
                eventPeri.hasActiveOptVars();

                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                %Set up event that props backwards from peri state
                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                eventMinus = LaunchVehicleEvent(lvdData.script);
                lvdData.script.addEvent(eventMinus);
                eventMinus.name = sprintf('Coast from %s Periapsis (-)', bodyInfo.name);
                eventMinus.plotMethod = EventPlottingMethodEnum.PlotContinuous;
                eventMinus.propDir = PropagationDirectionEnum.Backward;
                eventMinus.integratorObj = eventMinus.ode113Integrator;
                eventMinus.integratorObj.options.AbsTol = 1E-7;
                eventMinus.integratorObj.options.RelTol = 1E-7;
                eventMinus.integratorObj.options.NormControl = false;

                %%%Event line color
                colorI = mod(2*(i-1), length(colorEnums));
                eventMinus.colorLineSpec.color = colorEnums(colorI);

                %%%Set up termination condition
                dur = xferOrbitDurations(i-1)/2;
                termCond = EventDurationTermCondition(dur); %/2 so that we can meet a patch point in the middle
                eventMinus.termCond = termCond;

                var = termCond.getNewOptVar();
                lvdOptim.vars.addVariable(var);
                var.lb = 0.50*dur;
                var.ub = 1.50*dur;
                var.useTf = true;

                %%%Create continuity constraint
                lvdData.createContinuityConstraints(eventMinus, prevFwdPropEvent, cBodyInfo, true, 1, true, 1, true, 1, false, 1, false, 1);

                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                %Set up periapsis state event and propagate FORWARDS
                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                eventPlus = LaunchVehicleEvent(lvdData.script);
                lvdData.script.addEvent(eventPlus);
                eventPlus.name = sprintf('Coast from %s Periapsis (+)', bodyInfo.name);
                eventPlus.execActionsNode = ActionExecNodeEnum.BeforeProp;
                eventPlus.plotMethod = EventPlottingMethodEnum.SkipFirstState;
                eventPlus.propDir = PropagationDirectionEnum.Forward;
                eventPlus.integratorObj = eventPlus.ode113Integrator;
                eventPlus.integratorObj.options.AbsTol = 1E-7;
                eventPlus.integratorObj.options.RelTol = 1E-7;
                eventPlus.integratorObj.options.NormControl = false;

                %%%Event line color
                colorI = mod(2*(i-1)+1, length(colorEnums));
                eventPlus.colorLineSpec.color = colorEnums(colorI);

                %%%Set up termination condition
                dur = xferOrbitDurations(i)/2; %/2 so that we can meet a patch point in the middle
                termCond = EventDurationTermCondition(dur);
                eventPlus.termCond = termCond;

                var = termCond.getNewOptVar();
                lvdOptim.vars.addVariable(var);

                var.lb = 0.50*dur;
                var.ub = 1.50*dur;
                var.useTf = true;

                %%%Set up Set Kinematic State Action
                orbitModel = KeplerianElementSet(time, orbitIn(1), orbitIn(2), orbitIn(3), angleNegPiToPi(orbitIn(4)), angleNegPiToPi(orbitIn(5)), 0, frame);
                orbitModel = orbitModel.convertToUniversalElementSet();

                action = SetKinematicStateAction(lvdData.stateLog, orbitModel);
                eventPlus.addAction(action);
                action.event = eventPlus;

                action.inheritTime = true;
                action.inheritTimeFrom = InheritStateEnum.InheritFromSpecifiedEvent;
                action.inheritTimeFromEvent = eventPeri;

                action.inheritPosVel = true;
                action.inheritPosVelFrom = InheritStateEnum.InheritFromSpecifiedEvent;
                action.inheritPosVelFromEvent = eventPeri;

                action.inheritStateElems = true;
                action.inheritStateElemsFrom = InheritStateEnum.InheritFromSpecifiedEvent;
                action.inheritStateElemsFromEvent = eventPeri;

                action.generateLvComponentElements();

                %%%Set up delta-v action
                deltaV = mfmsOutputs.deltaVVectNTW(:,i-1);
                action = AddDeltaVAction(deltaV, DeltaVFrameEnum.OrbitNtw, true);
                eventPlus.addAction(action);
                action.event = eventPlus;

                var = AddDeltaVActionVariable(action);
                lvdOptim.vars.addVariable(var);
                var.lb = deltaV - 0.50*abs(deltaV);
                var.ub = deltaV + 0.50*abs(deltaV);
                var.varDVx = true;
                var.varDVy = false;
                var.varDVz = false;

                %%%Generate active vars cache
                eventPlus.clearActiveOptVarsCache();
                eventPlus.hasActiveOptVars();

                %%%Set previous forward prop event
                prevFwdPropEvent = eventPlus;
            end

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %Set up periapsis state for arrive body
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            arriveBodyInfo = wayPtBodies(end);
            frame = arriveBodyInfo.getBodyCenteredInertialFrame();
            time = xferOrbits(end,9);

            %Compute an inbound orbit
            vInfArriveIn = mfmsOutputs.vInfArrive;
            vInfArriveMag = norm(vInfArriveIn);

            gmu = arriveBodyInfo.gm;
            sma = -gmu / vInfArriveMag^2;
            rP = arriveBodyInfo.radius + arriveBodyInfo.atmohgt + 0.1*arriveBodyInfo.radius;
            ecc = 1 - rP/sma;

            turnAngle = 2*acos(-1/ecc);
            R = rotz(rad2deg(turnAngle));
            vInfArriveOut = R * vInfArriveIn;

            [~, ~, hHat, sHat, ~, ~, ~] = computeMultiFlyByParameters(vInfArriveIn, vInfArriveOut, gmu);
            [hSMAIn, hEccIn, hIncIn, hRAANIn, hArgIn, ~, ~, ~] = computeHyperOrbitFromMultiFlybyParams(sma, ecc, hHat, sHat, true);

            %Create event
            event = LaunchVehicleEvent(lvdData.script);
            lvdData.script.addEvent(event);
            event.name = sprintf('%s Periapsis State', arriveBodyInfo.name);
            event.execActionsNode = ActionExecNodeEnum.BeforeProp;
            event.plotMethod = EventPlottingMethodEnum.SkipFirstState;
            event.propDir = PropagationDirectionEnum.Backward;
            event.integratorObj.options.AbsTol = 1E-7;
            event.integratorObj.options.RelTol = 1E-7;
            event.integratorObj.options.NormControl = false;

            %%%Event line color
            event.colorLineSpec.color = ColorSpecEnum.Blue;

            %%%Set up termination condition
            dur = 0; %/2 so that we can meet a patch point in the middle
            termCond = EventDurationTermCondition(dur); %/2 so that we can meet a patch point in the middle
            event.termCond = termCond;

            %%%Set up Set Kinematic State Action
            orbitModel = KeplerianElementSet(time, hSMAIn, hEccIn, hIncIn, angleNegPiToPi(hRAANIn), angleNegPiToPi(hArgIn), 0, frame);
            orbitModel = orbitModel.convertToUniversalElementSet();
            action = SetKinematicStateAction(lvdData.stateLog, orbitModel);

            action.inheritStateElems = true;
            action.inheritStateElemsFrom = InheritStateEnum.InheritFromLastState;
            event.addAction(action);
            action.event = event;

            action.generateLvComponentElements();

            %%%Set up Set Kinematic State variable
            var = SetKinematicStateActionVariable(action);
            lvdOptim.vars.addVariable(var);

            var.lb = 0.50*time;
            var.ub = 1.50*time;
            var.useTf = true;

            var.orbitVar.lb = [min(1.0001,0.75*orbitModel.c3), ...
                               max(0.75*orbitModel.rP,arriveBodyInfo.radius+arriveBodyInfo.atmohgt), ...
                               0, ...
                               -2*pi, ...
                               -2*pi, ...
                               0];

            var.orbitVar.ub = [1.25*orbitModel.c3, ...
                               1.25*orbitModel.rP, ...
                               pi, ...
                               2*pi, ...
                               2*pi, ...
                               0];

            var.orbitVar.varC3 = true;
            var.orbitVar.varRp = true;
            var.orbitVar.varInc = true;
            var.orbitVar.varRaan = true;
            var.orbitVar.varArg = true;
            var.orbitVar.varTau = false;

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %Prop BACKWARDS from periapsis state for arrive body
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            %Create event
            event = LaunchVehicleEvent(lvdData.script);
            lvdData.script.addEvent(event);
            event.name = sprintf('Coast from %s Arrival (-)', arriveBodyInfo.name);
            event.execActionsNode = ActionExecNodeEnum.BeforeProp;
            event.plotMethod = EventPlottingMethodEnum.SkipFirstState;
            event.propDir = PropagationDirectionEnum.Backward;
            event.integratorObj.options.AbsTol = 1E-7;
            event.integratorObj.options.RelTol = 1E-7;
            event.integratorObj.options.NormControl = false;

            %%%Event line color
            event.colorLineSpec.color = ColorSpecEnum.Blue;

            %%%Set up termination condition
            dur = xferOrbitDurations(end)/2; %/2 so that we can meet a patch point in the middle
            termCond = EventDurationTermCondition(dur); %/2 so that we can meet a patch point in the middle
            event.termCond = termCond;

            var = termCond.getNewOptVar();
            lvdOptim.vars.addVariable(var);

            var.lb = 0.50*dur;
            var.ub = 1.50*dur;
            var.useTf = true;

            %%%Create continuity constraint
            lvdData.createContinuityConstraints(event, prevFwdPropEvent, cBodyInfo, true, 1, true, 1, true, 1, false, 1, false, 1);

            %Set up objective function
            fcn = GenericMAConstraint('Total Spacecraft Mass', event, 0, 0, [], [], cBodyInfo);
            objFcn = GenericObjectiveFcn(event, cBodyInfo.getBodyCenteredInertialFrame(), fcn, 1, lvdOptim, lvdData);
            lvdOptim.objFcn.addObjFunc(objFcn);
            lvdOptim.objFcn.dirType = ObjFcnDirectionTypeEnum.Maximize;
        end

        function initBody = getDefaultInitialBodyInfo(celBodyData)
            if(isfield(celBodyData,'kerbin'))
                initBody = celBodyData.kerbin;
            elseif(isfield(celBodyData,'earth'))
                initBody = celBodyData.earth;
            else
                fields = fieldnames(celBodyData);
                initBody = celBodyData.(fields{1});
            end
        end

        function obj = loadobj(obj)
            arguments
                obj LvdData
            end

            obj.validation = LaunchVehicleDataValidation(obj);

            if(isempty(obj.stateLog))
                obj.stateLog = LaunchVehicleStateLog(obj);
            end

            if(isempty(obj.stateLog.lvdData))
                obj.stateLog.lvdData = obj;
            end

            if(isempty(obj.plugins))
                obj.plugins = LvdPluginSet(obj);
            end

            if(isempty(obj.pluginVars))
                obj.pluginVars = LvdPluginOptimVarSet(obj);
            end

            if(isempty(obj.viewSettings))
                obj.viewSettings = LaunchVehicleViewSettings(obj);

                for(i=1:length(obj.viewSettings.viewProfiles))
                    profile = obj.viewSettings.viewProfiles(i);

                    if(isempty(profile.frame))
                        originBodyInfo = obj.getDefaultInitialBodyInfo(obj.celBodyData);
                        profile.frame = originBodyInfo.getBodyCenteredInertialFrame();
                    end
                end
            end

            if(isempty(obj.groundObjs))
                obj.groundObjs = LaunchVehicleGroundObjectSet(obj);
            end

            if(isempty(obj.geometry))
                obj.geometry = LvdGeometry(obj);
            end

            if(isempty(obj.graphAnalysis))
                obj.graphAnalysis = LvdGraphicalAnalysis(obj);
            end

            if(isempty(obj.sensors))
                obj.sensors = LvdSensorSet(obj);
            end

            if(isempty(obj.sensorTgts))
                obj.sensorTgts = LvdSensorTargetSet(obj);
            end

            evts = obj.script.evts;
            for(i=1:length(evts)) %#ok<*NO4LP>
                evts(i).createUpdatedSetKinematicStateObjs();
            end

            nonSeqEvts = obj.script.nonSeqEvts.nonSeqEvts;
            for(i=1:length(nonSeqEvts))
                nonSeqEvts(i).evt.createUpdatedSetKinematicStateObjs();
            end

            if(isempty(obj.id) || obj.id == 0)
                obj.id = rand();
            end
        end

        function obj = saveobj(obj)
            obj.stateLog.clearStateLog();
        end
    end
end


classdef DesignVariableAcademic < handle

    properties (Access = public)
        value
        valueOld
    end

    properties (Access = private)

    end

    properties (Access = private)

    end

    methods (Access = public)

        function obj = DesignVariableAcademic()

        end

        function init(obj,x0)
            obj.value = x0;
        end

    end

    methods (Access = public)

        function update(obj,x)
            obj.value = x;
        end

        function updateOld(obj)
            obj.valueOld = obj.value;
        end

    end

end


% POP_STUDYERP - create a simple design for ERP analysis
%
% Usage:
%       >> [STUDY ALLEEG] = pop_studyerp; % pop up interface
%
% Outputs:
%   STUDY        - an EEGLAB STUDY set of loaded EEG structures
%   ALLEEG       - ALLEEG vector of one or more loaded EEG dataset structures
%
% Author: Arnaud Delorme, SCCN, UCSD, 2011-
%
% See also: EEG_CHECKSET

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

function [STUDY ALLEEG com ] = pop_studyerp;

% first GUI, get the number of conditions and subjects
% ----------------------------------------------------
textinfo = [ 'This interface creates a simple STUDY and ' 10 ...
             'computes its condition grand average ERPs.' 10 ...
             'For each subject, trials for each condition' 10 ...
             'must first be stored in a separate dataset.' 10 ...
             'Create other STUDY using the standard editor.' ];
guispec = { ...
    {'style' 'text' 'string' 'Create simple ERP STUDY' ...
     'FontWeight' 'Bold' 'fontsize', 12} ...
    { 'style' 'text' 'string' textinfo } ...
    {'style' 'text' 'string' 'Number of conditions:' } ...
    {'style' 'edit' 'string' '1' 'tag' 'cond' } { } ...
    {'style' 'text' 'string' 'Number of subjects:' } ...
    {'style' 'edit' 'string' '15' 'tag' 'subjects' } { } };
guigeom = { [1] [1] [1 0.3 0.4] [1 0.3 0.4] };

optiongui = { 'geometry', guigeom, 'uilist'  , guispec, ...
              'geomvert', [ 1 4 1 1], ...
              'helpcom' , 'pophelp(''pop_studyerp'')', ...
              'title'   , 'Create a new STUDY set -- pop_studyerp()' };
[result, userdat2, strhalt, outstruct] = inputgui(optiongui{:});
STUDY  = [];
ALLEEG = [];
com = '';
if isempty(result), return; end

nSubjects = str2num(outstruct.subjects);
nConds    = str2num(outstruct.cond);

% second GUI, enter the datasets
% ------------------------------
guispec = { ...
    {'style' 'text' 'string' 'Create simple ERP STUDY' 'FontWeight' 'Bold' 'fontsize', 12} ...
    {} ...
    {} {'style' 'text' 'string' 'STUDY set name:' } { 'style' 'edit' 'string' '' 'tag' 'study_name' } ...
    {} };
guigeom = { [1]  [1] [0.2 1 3.5] [1] };

% define conditions
% -----------------
guigeom{end+1} = [];
for icond = 1:nConds
    if icond == 1, guigeom{end} = [ guigeom{end} 1 0.2];
    else           guigeom{end} = [ guigeom{end} 0.1 1 0.2];
    end
    if icond > 1, guispec{end+1} = {}; end
    guispec = { guispec{:}, {'style' 'text' 'string' [ 'Condition ' num2str(icond) ' name'] } {} };
end

% edit boxes for conditions
% -------------------------
guigeom{end+1} = [];
for icond = 1:nConds
    if icond == 1, guigeom{end} = [ guigeom{end} 1 0.2];
    else           guigeom{end} = [ guigeom{end} 0.1 1 0.2];
    end
    if icond > 1, guispec{end+1} = {}; end
    guispec = { guispec{:}, {'style' 'edit' 'string' '' 'tag' [ 'cond' num2str(icond) ] } {} };
end
guispec{end+1} = {};
guigeom{end+1} = [1];

% define dataset headers
% ----------------------
guigeom{end+1} = [];
for icond = 1:nConds
    if icond == 1, guigeom{end} = [ guigeom{end} 1 0.2];
    else           guigeom{end} = [ guigeom{end} 0.1 1 0.2];
    end
    if icond > 1, guispec{end+1} = {}; end
    guispec = { guispec{:}, {'style' 'text' 'string' ['Condition ' num2str(icond) ' datasets' ] } {} };
end

% create edit boxes
% -----------------
for index = 1:nSubjects
    guigeom{end+1} = [];
    for icond = 1:nConds
        if icond == 1, guigeom{end} = [ guigeom{end} 1 0.2];
        else           guigeom{end} = [ guigeom{end} 0.1 1 0.2];
        end
        select_com = ['[inputname, inputpath] = uigetfile2(''*.set;*.SET'', ''Choose dataset to add to STUDY -- pop_study()'');'...
                      'if inputname ~= 0,' ...
                      '   guiind = findobj(''parent'', gcbf, ''tag'', ''set' int2str(icond) '_' int2str(index) ''');' ...
                      '   set( guiind,''string'', fullfile(inputpath, inputname));' ...
                      'end; clear inputname inputpath;'];
        if icond > 1, guispec{end+1} = {}; end
        guispec = { guispec{:}, ...
                {'style' 'edit'       'string' ''    'tag' [ 'set' int2str(icond) '_' int2str(index) ] }, ...
                {'style' 'pushbutton' 'string' '...' 'Callback' select_com } };
    end
end

% last text
% ---------
textinfo = [  'When using more than 1 condition, datasets on each line must correspond to the same subject.' ];
guispec = { guispec{:}, {}, {'style' 'text' 'string' textinfo } };
guigeom = { guigeom{:} [1] [1] };

optiongui = { 'geometry', guigeom, ...
              'uilist'  , guispec, ...
              'helpcom' , 'pophelp(''pop_studyerp'')', ...
              'title'   , 'Create a new STUDY set -- pop_studyerp()' };
[result, userdat2, strhalt, outstruct] = inputgui(optiongui{:});
if isempty(result), return; end

% decode outstruct and build call to std_editset
% ----------------------------------------------
options  = { 'name' outstruct.study_name 'updatedat' 'off' };
commands = {};
for icond = 1:nConds

    % check that condition name is defined
    tagCond = ['cond' int2str(icond) ];
    if isempty(outstruct.(tagCond))
        outstruct.(tagCond) = [ 'condition ' int2str(icond) ];
    end

    for index = 1:nSubjects
        tagSet  = [ 'set' int2str(icond) '_' int2str(index) ];
        subject  = sprintf('S%2.2d', index);

        if ~isempty(outstruct.(tagSet))
            commands = { commands{:}, {'index' nConds*index+icond-1 'load' outstruct.(tagSet)  'subject' subject 'condition' outstruct.(tagCond) } };
        end
    end
end
options = { options{:}, 'commands', commands };

% call std_editset to create the STUDY
% ------------------------------------
com1 = sprintf( '[STUDY ALLEEG] = std_editset( STUDY, ALLEEG, %s );', vararg2str(options) );
[STUDY ALLEEG] = std_editset(STUDY, ALLEEG, options{:});
if exist(fullfile(ALLEEG(1).filepath, [ALLEEG(1).filename(1:end-4) '.daterp' ]))
    textmsg = [ 'WARNING: SOME ERP DATAFILES ALREADY EXIST, OVERWRITE THEM?' 10 ...
                '(the recomputed files will be identical to the existing ones, unless you modified the EEG' 10 ...
                'datasets or set a different ERP baseline using menu item STUDY > Precompute channel measures).' ];
    res = questdlg2(textmsg, 'Precomputed datafiles already present on disk', 'No', 'Yes', 'Yes');
    if strcmpi(res, 'No')
        error('User aborded precomputing ERPs');
    end;
end;

% call std_precomp for ERP (channels)
% -----------------------------------
com2 = '[STUDY ALLEEG] = std_precomp(STUDY, ALLEEG, ''channels'', ''interp'', ''on'', ''recompute'',''on'',''erp'',''on'');';
[STUDY ALLEEG] = std_precomp(STUDY, ALLEEG, 'channels','interp', 'on', 'recompute','on','erp','on');

% call std_erpplot to plot ERPs (channels)
% ----------------------------------------
com3 = 'tmpchanlocs = ALLEEG(1).chanlocs; STUDY = std_erpplot(STUDY, ALLEEG, ''channels'', { tmpchanlocs.labels }, ''plotconditions'', ''together'');';
tmpchanlocs = ALLEEG(1).chanlocs;
STUDY = std_erpplot(STUDY, ALLEEG, 'channels', { tmpchanlocs.labels }, 'plotconditions', 'together');
pos = get(gcf, 'position');
set(gcf, 'position', [10 pos(2) pos(3)*2 pos(4)*2]);

% call the STUDY plotting interface
% ---------------------------------
disp('Press OK to close plotting interface and save the STUDY');
disp('If you press CANCEL, the whole STUDY will be lost.');
[STUDY com4] = pop_chanplot(STUDY, ALLEEG);

com = sprintf('%s\n%s\n%s\n%s', com1, com2, com3, com4);


function old_default = set_matlab_bgl_default(varargin)
% SET_MATLAB_BGL_DEFAULT Sets a default option for the Matlab BGL interface
%
% old_default = set_matlab_bgl_default(options) or
% old_default = set_matlab_bgl_default(...) for key-value pair version
% options.istrans: the input matrices are already transposed [{0} | 1]
% options.nocheck: skip the input checking [{0} | 1]
% options.full2sparse: convert full matrices to sparse [{0} | 1]
%
% to get the current set of default options, call
% options = set_matlab_bgl_default()
%
% These options can make the Matlab BGL interface more efficient by
% eliminating the copying operations that occur between Matlab's structures
% and the BGL structures.  However, they are more difficult to use and are
% disabled by default.
%
% Generally, they are best used when you want to perform a large series of
% computations.
%
% Example:
%   % tranpose the matrix initially...
%   At = A'
%   old_options = set_matlab_bgl_default(struct('istrans',1));
%   % perform a bunch of graph work with At...
%   d1 = dfs(At,1); d2 = dfs(At,2); ...
%   % restore the old options
%   set_matlab_bgl_default(old_options);

% David Gleich
% Copyright, Stanford University, 2006-2008

%% History
%%

persistent default_options;
if ~isa(default_options,'struct')
    % initial default options
    default_options = struct('istrans', 0, 'nocheck', 0, 'full2sparse', 0);
end

if nargin == 0
    old_default = default_options;
else
    old_default = default_options;
    default_options = merge_options(default_options,varargin{:});
end


function file_datenum = filedatenum(fname)
% <cpp> Purpose: Returns the modification date (datenum) of a file
% Usage: file_datenum = filedatenum('myfile.dat')
% Author: Charles Plum, Varolii Corp, 21 August 2009
%*********************************************************************

% Extract the file path from the argument fname
dir_name = fname(1:max(strfind (fname,'\')));

%If a path was given, then extract the file name from the fname arg
if (dir_name)
    file_name = fname(max(strfind (fname,'\'))+1:end);
else % assume the local directory is the path
    dir_name = pwd;
    file_name = fname;
end

% Not elegant but it works ...
d = dir(dir_name);
for ifile=1:length(d)
    if (strcmp(file_name,char(d(ifile).name)))
      file_datenum = d(ifile).datenum;
      return;
    end
end
file_datenum = 0; % Return zero if no file found
return


function varargout=zerocros(varargin)
%
% For calling details please see v_zerocros.m
%
% This dummy routine is included for backward compatibility only
% and will be removed in a future release of voicebox. Please use
% v_zerocros.m in future and/or update with v_voicebox_update.m
%
%      Copyright (C) Mike Brookes 2018
%      Version: $Id: zerocros.m 10863 2018-09-21 15:39:23Z dmb $
%
if nargout
    varargout=cell(1,nargout);
    [varargout{:}]=v_zerocros(varargin{:});
else
    v_zerocros(varargin{:});
end