function varargout = load_xls(varargin)
% LOAD_XLS M-file for load_xls.fig
%      LOAD_XLS, by itself, creates a new LOAD_XLS or raises the existing
%      singleton*.
%
%      H = LOAD_XLS returns the handle to a new LOAD_XLS or the handle to
%      the existing singleton*.
%
%      LOAD_XLS('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in LOAD_XLS.M with the given input arguments.
%
%      LOAD_XLS('Property','Value',...) creates a new LOAD_XLS or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before load_xls_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to load_xls_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% This file is part of the Matlab Toolbox for Dimensionality Reduction v0.7.2b.
% The toolbox can be obtained from http://homepage.tudelft.nl/19j49
% You are free to use, change, or redistribute this code in any way you
% want for non-commercial purposes. However, it is appreciated if you
% maintain the name of the original author.
%
% (C) Laurens van der Maaten, 2010
% University California, San Diego / Delft University of Technology

% Edit the above text to modify the response to help load_xls

% Last Modified by GUIDE v2.5 11-Sep-2008 10:53:04

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @load_xls_OpeningFcn, ...
                   'gui_OutputFcn',  @load_xls_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before load_xls is made visible.
function load_xls_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to load_xls (see VARARGIN)

% Choose default command line output for load_xls
handles.output = hObject;

nmc=varargin{1}; % number of columns

% Update handles structure
guidata(hObject, handles);

set(handles.noc,'string',num2str(nmc));
set(handles.col,'string',num2str(nmc));

cl=[0.4 0.4 0.4];
set(handles.coltxt,'ForegroundColor',cl);
set(handles.col,'Enable','off');

% UIWAIT makes load_xls wait for user response (see UIRESUME)
uiwait(handles.figure1);

% --- Outputs from this function are returned to the command line.
function varargout = load_xls_OutputFcn(hObject, eventdata, handles)
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;

function col_Callback(hObject, eventdata, handles)
% hObject    handle to col (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of col as text
%        str2double(get(hObject,'String')) returns contents of col as a double

% --- Executes during object creation, after setting all properties.
function col_CreateFcn(hObject, eventdata, handles)
% hObject    handle to col (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on button press in ok.
function ok_Callback(hObject, eventdata, handles)
uiresume(handles.figure1);

% --- Executes when selected object is changed in uipanel1.
function uipanel1_SelectionChangeFcn(hObject, eventdata, handles)
if get(handles.uc,'value')
    cl=[0 0 0];
    set(handles.coltxt,'ForegroundColor',cl);
    set(handles.col,'Enable','on');
else
    cl=[0.4 0.4 0.4];
    set(handles.coltxt,'ForegroundColor',cl);
    set(handles.col,'Enable','off');
end


function f = cancelExponents(f)
%CANCELEXPONENTS  Cancel the exponents of a CHEBTECH.
%   F = CANCELEXPONENTS(F) returns the given F without change, since CHEBTECH
%   has no exponents.
%
% See also EXTRACTBOUNDARYROOTS.

% Copyright 2017 by The University of Oxford and The Chebfun Developers.
% See http://www.chebfun.org/ for Chebfun information.

end


function [infoPackets, version] = openCCF(filename)

% openCCF
%
% This script reads a .ccf file and outputs it in a structure.
%
%   filename:  Name of the file to be opened. If the fname is omitted
%              the user will be prompted to select a file.
%              DEFAULT: Will open Open File UI.
%
%   Channels   1-128 are neural channels
%   Channels 129-144 are external analog inputs
%   Channels 145-148 are external analog outpus
%   Channels
%
%   Kian Torab
%   kian@blackrockmicro.com
%   Blackrock Microsystems
%   Version 2.1.0.0
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Version History
%
% 1.0.0.0:
%   - Initial release.
%
% 1.2.3.0:
%   - Minor bug fix that led to a crash in certain cases.
%
% 1.2.4.0:
%   - Minor bug fix regarding passing a filename variable to the function.
%
% 2.0.0.0:
%   - Implemented XML CCF file format.
%
% 2.1.0.0:
%   - Fixed a bug in loading nTrode groups with a base of 0.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

infoPackets.openCCFVersion = '2.1.0.0';

%% Parameters

% Which versions of .ccf's are supported
supportedVersions = {'3.6', '3.7', '3.8', '3.9'};
% Number of cbPKT_CHANINFO packets to read in the file
nValidPKT_CHANINFO = 160;

if( nargin ~= 1 )
    [filename, pathname] = getFile('*.ccf');
    if ~filename
        disp('No file was selected.');
        return;
    end
    fullfilename = [pathname filename];
elseif (nargin == 1)
    fullfilename = filename;
else
    if strcmpi(filename, 'ver')
    disp(['Version is ' infoPackets.openCCFVersion]);
    infoPackets = [];
    return;
    end
end

%% Open file
[fid, message] = fopen( fullfilename, 'rb', 'l');
if( fid == -1 ),
   warning(['Unable to open file: ' fullfilename '. Error message ' message]);
   return;
end;

%% Read header incl. version
head = fread(fid, 16, '*char*1' )';

if strcmp(head(1:5), 'cbCCF')
    version = deblank(strtrim(head(6:end))); % Strip possible leading space and trailing nulls
elseif strcmp(head(1:5), '<?xml')
    version = '3.9';
else
  warning('Not a well-formed .ccf file');
  fclose(fid);
  return;
end

% Save version info in the structure.
infoPackets.Version = version;

% Verify to see if the file version is supported
if ~ismember(version, supportedVersions)
  warning(sprintf('Unsupported file version: %s', version));
  fclose(fid);
  return;
end

if strcmpi(version, '3.9')
    infoPackets = parseCCF(fullfilename);
    for nTrodeIDX = 1:length(infoPackets.Children(7).Children)
        for trodeIDX = 1:length(infoPackets.Children(7).Children(nTrodeIDX).Children(9).Children)
            tempTrodes(trodeIDX) =  ...
            str2double(infoPackets.Children(7).Children(nTrodeIDX).Children(9).Children(trodeIDX).Children.Data) + 1;
        end
        if ~all(tempTrodes == 1)
            infoPackets.NTrodeInfo.NTrodeID(nTrodeIDX) = nTrodeIDX;
            infoPackets.NTrodeInfo.NTrodeMembers{nTrodeIDX} = tempTrodes;
        end
    end
    return;
end

%% Read packets
for p = 1:nValidPKT_CHANINFO
    if p == 128
%    disp('w');
end

  infoPackets.ChanInfo(p) = readInfoPacket(fid, version);
end
infoPackets.AdaptiveFilterInfo = readAdapFilterPacket(fid);
infoPackets.SortingInfo = readSortingPacket(fid);
infoPackets.SystemInfo = []; %readSystemInfoPacket(fid);
infoPackets.NTrodeInfo = readNTrodeInfoPacket(infoPackets);

%% Close file
fclose(fid);

function p = readInfoPacket(fid, version)
% Read a cbPKT_CHANINFO packet
    p = [];
    cbLEN_STR_LABEL = 16;
    cbMAXUNITS = 5;
    cbMAXHOOPS = 4;
    p.SystemClockTimestamp = fread(fid, 1, '*uint32');
    p.Child = fread(fid, 1, '*uint16');
    p.AnaInPacketType = fread(fid, 1, '*uint8');
    p.SystemDataLength = fread(fid, 1, '*uint8');
    p.AnaInChannelID = fread(fid, 1, '*uint32');
    p.AnaInChannelProcessor = fread(fid, 1, '*uint32');
    p.AnaInBankID = fread(fid, 1, '*uint32');
    p.AnaInPinID = fread(fid, 1, '*uint32');
    h = fread(fid, 1, '*uint32');
    p.CapabilitiesGeneralChannel = setFlagsCBCHAN(h);
    h = fread(fid, 1, '*uint32');
    p.CapabilitiesDigOutChannel = setFlagsCBDOUT(h);
    h = fread(fid, 1, '*uint32');
    p.CapabilitiesDigInChannel = setFlagsCBDINP(h);
    h = fread(fid, 1, '*uint32');
    p.CapabilitiesAnaOutChannel = setFlagsCBAOUT(h);
    h = fread(fid, 1, '*uint32');
    p.CapabilitiesAnaInChannel = setFlagsCBAINP(h);
    p.CapabilitiesSpikeProcessing = fread(fid, 1, '*uint32');

    p.InputPhysicalChannelScalingInfo       = readCbScaling(fid);
    p.InputPhysicalChannelFilterDefinition  = readFiltDesc(fid);
    p.OutputPhysicalChannelScalingInfo      = readCbScaling(fid);
    p.OutputPhysicalChannelFilterDefinition = readFiltDesc(fid);

    p.ChannelLabel     = fread(fid, cbLEN_STR_LABEL, '*char*1')';
    p.ChannelUserFlags = fread(fid, 1, '*uint32');
    p.ChannelPosition  = fread(fid, 4, '*int32')';

    p.InputPhysicalChannelScalingInfoCustom  = readCbScaling(fid);
    p.OuputPhysicalChannelScalingInfoCustom = readCbScaling(fid);

    p.doutopts = fread(fid, 1, '*uint32');
    p.dinpopts = fread(fid, 1, '*uint32');
    p.aoutopts = fread(fid, 1, '*uint32');
    p.eopchar  = fread(fid, 1, '*uint32');

    % Below two lines are actually part of a union, not sure how to figure out
    % which option is actually being used
    p.AnaInMonitorChannelAddress             = fread(fid, 1, '*uint32');
    p.AnaInOutputValue                       = fread(fid, 1, '*int32');
    p.AnaInOptions                           = fread(fid, 1, '*uint32');
    p.AnaInLNCAdaptationRate                 = fread(fid, 1, '*uint32');
    p.AnaInContinuousStreamFilterID          = fread(fid, 1, '*uint32');
    p.AnalogInputContinuousStreamSampleGroup = fread(fid, 1, '*uint32');
    p.AnaInContinuousStreamDisplayFactorMin  = fread(fid, 1, '*int32');
    p.AnaInContinuousStreamDisplayFactorMax  = fread(fid, 1, '*int32');
    p.AnaInSpikeStreamFilterID               = fread(fid, 1, '*uint32');
    p.AnaInSpikeStreamDisplayFactorMax       = fread(fid, 1, '*int32');
    p.AnaInLNCDisplayFactorMax               = fread(fid, 1, '*int32');
    p.AnaInSpikeProcessingOptions            = fread(fid, 1, '*uint32');
    p.AnaInSpikeStreamThresholdLevel         = fread(fid, 1, '*int32');
    p.AnaInSpikeStreamThresholdLimit         = fread(fid, 1, '*int32');
    p.AnaInNTrodeGroupID                     = fread(fid, 1, '*uint32');
    p.AnaInAmplitudeRejectionValuePositive   = fread(fid, 1, '*int16');
    p.AnaInAmplitudeRejectionValueNegative   = fread(fid, 1, '*int16');
    p.AnaInDigitalReferencingChannel         = fread(fid, 1, '*uint32');
    p.AnaInManualMappingUnit                 = readCbManualUnitMappings(fid, cbMAXUNITS, version);
    p.AnaInSpikeHoopsSorting                 = readCbHoops(fid, cbMAXUNITS, cbMAXHOOPS);

function p = readAdapFilterPacket(fid)
    p = [];
    p.Time = fread(fid, 1, '*uint32');
    p.Child = fread(fid, 1, '*uint16');
    p.Type = fread(fid, 1, '*uint8');
    p.DataLength = fread(fid, 1, '*uint8');
    p.Chan = fread(fid, 1, '*uint32');
    p.Mode = fread(fid, 1, '*uint32');
    p.LearningRate = fread(fid, 1, '*float32');
    p.RefChan1 = fread(fid, 1, '*uint32');
    p.RefChan2 = fread(fid, 1, '*uint32');

function p = readSortingPacket(fid)
    p = [];
    p.Time = fread(fid, 1, '*uint32');
    p.Child = fread(fid, 1, '*uint16');
    p.Type = fread(fid, 1, '*uint8');
    p.DataLength = fread(fid, 1, '*uint8');
    p.NumMaxSimultChans = fread(fid, 1, '*uint32');
    p.RefractoryPeriodInSamples = fread(fid, 1, '*uint32');

function p = readSystemInfoPacket(fid)
    p = [];

function p = readNTrodeInfoPacket(infoPackets)
    p = [];
    NTrodeGroups = unique([infoPackets.ChanInfo.AnaInNTrodeGroupID]); NTrodeGroups(1) = [];
    NTrodeGroupsCount = length(NTrodeGroups);

for ntrodeIDX = 1:NTrodeGroupsCount
    p.NTrodeID(ntrodeIDX) = NTrodeGroups(ntrodeIDX);
    p.NTrodeMembers{ntrodeIDX} = find([infoPackets.ChanInfo.AnaInNTrodeGroupID] == ntrodeIDX);
end

%% Sub-struct readers
function sc = readCbScaling(fid)
% Read a cbSCALING struct

cbLEN_STR_UNIT = 8;

sc.AnalogChannelScaleMin = fread(fid, 1, '*int16');
sc.AnalogChannelScaleMax = fread(fid, 1, '*int16');
sc.AnalogChannelScaleMin = fread(fid, 1, '*int32');
sc.AnalogChannelScaleMax = fread(fid, 1, '*int32');
sc.AnalogChannelGain = fread(fid, 1, '*int32');
sc.AnalogChannelUnit = fread(fid, cbLEN_STR_UNIT, '*char*1')';

function fd = readFiltDesc(fid)
% Read a cbFILTDESC struct

cbLEN_STR_FILT_LABEL = 16;

fd.FilterLabel = fread(fid, cbLEN_STR_FILT_LABEL, '*char*1')';
fd.FilterHighPassFrequency = fread(fid, 1, '*uint32');
fd.FilterHighPassOrder = fread(fid, 1, '*uint32');
fd.FilterHighPassType = fread(fid, 1, '*uint32');
fd.FilterLowPassFrequency = fread(fid, 1, '*uint32');
fd.FilterLowPassOrder = fread(fid, 1, '*uint32');
fd.FilterLowPassType = fread(fid, 1, '*uint32');

function ums = readCbManualUnitMappings(fid, maxUnits, version)
% Loop to read all the cbMANUALUNITMAPPING packets. Note that this requires
% knowing the file version: v3.6 is different from 3.7 and 3.8

% Loop growing is inefficient, but it's just not that much data.
if strcmp(version, '3.6')
    for i = 1:maxUnits
        ums(i) = readCbManualUnitMapping3_6(fid);
    end
else
    for i = 1:maxUnits
        ums(i) = readCbManualUnitMapping(fid);
    end
end

function um = readCbManualUnitMapping(fid)
% Read a cbMANUALUNITMAPPING packet for a v3.7 or 3.8 file

um.nOverride = fread(fid, 1,     '*int16');
um.afOrigin  = fread(fid, 3,     '*int16')';
um.afShape   = fread(fid, [3 3], '*int16')';
um.aPhi      = fread(fid, 1,     '*int16')';
um.bValid    = fread(fid, 1,     '*uint32')';

function um = readCbManualUnitMapping3_6(fid)
% Read a cbMANUALUNITMAPPING packet for a v3.6 file

um.nOverride = fread(fid, 1,     '*uint32');
um.afOrigin  = fread(fid, 3,     '*float32')';
um.afShape   = fread(fid, [3 3], '*float32')';
um.aPhi      = fread(fid, 1,     '*float32')';
um.bValid    = fread(fid, 1,     '*uint32')';

function hs = readCbHoops(fid, maxUnits, maxHoops)
% Loop to read the cbHOOP packets

% Loop growing is inefficient, but it's just not that much data.
for u = 1:maxUnits
  for h = 1:maxHoops
    hs(u, h) = readCbHoop(fid);
  end
end

function h = readCbHoop(fid)
% Read a cbHOOP packet
h.valid = fread(fid, 1, '*uint16')';
h.time = fread(fid, 1, '*int16')';
h.min = fread(fid, 1, '*int16')';
h.max = fread(fid, 1, '*int16')';

function h = setFlagsCBCHAN(flags)

binaryValue = dec2bin(flags,12);

% define constants
h.ChanExists     = 0;  % Channel id is allocated
h.ChanConnected  = 0;  % Channel is connected and mapped and ready to use
h.ChanIsolated   = 0;  % Channel is electrically isolated
h.ChanType       = '';

if strcmpi(binaryValue(1), '1')
    h.ChanType = 'DigitalOut';
end
if strcmpi(binaryValue(2), '1')
    h.ChanType = 'DigitalInput';
end
if strcmpi(binaryValue(3), '1')
    h.ChanType = 'AnalogOutput';
end
if strcmpi(binaryValue(4), '1')
    h.ChanType = 'AnalogInput';
end
if strcmpi(binaryValue(10), '1')
    h.ChanIsolated = 1;
end
if strcmpi(binaryValue(11), '1')
    h.ChanConnected = 1;
end
if strcmpi(binaryValue(12), '1')
    h.ChanExists = 1;
end

function h = setFlagsCBDOUT(flags)

binaryValue = dec2bin(flags,30);

h.BaudRate = 0;
h.NumberOfBits = 0;
h.CanBeManuallyConfigured = 0;
h.TrackMostRecentlySelected = 0;
h.CanOutputFrequency = 0;
h.ChanMonitorUnit0 = 0;
h.ChanMonitorUnit1 = 0;
h.ChanMonitorUnit2 = 0;
h.ChanMonitorUnit3 = 0;
h.ChanMonitorUnit4 = 0;
h.ChanMonitorUnit5 = 0;
h.ChanMonitorUnitAll = 0;

if strcmpi(binaryValue(1:6), '111111')
    h.ChanMonitorUnitAll = 1;
else
    if strcmpi(binaryValue(1), '1')
        h.ChanMonitorUnit5 = 1;
    end
    if strcmpi(binaryValue(2), '1')
        h.ChanMonitorUnit4 = 1;
    end
    if strcmpi(binaryValue(3), '1')
        h.ChanMonitorUnit3 = 1;
    end
    if strcmpi(binaryValue(4), '1')
        h.ChanMonitorUnit2 = 1;
    end
    if strcmpi(binaryValue(5), '1')
        h.ChanMonitorUnit1 = 1;
    end
    if strcmpi(binaryValue(6), '1')
        h.ChanMonitorUnit0 = 1;
    end
end
if strcmpi(binaryValue(12), '1')
    h.CanOutputFrequency = 1;
end
if strcmpi(binaryValue(13), '1')
    h.TrackMostRecentlySelected = 1;
end
if strcmpi(binaryValue(18), '1')
    h.CanBeManuallyConfigured = 1;
end
if strcmpi(binaryValue(19), '1')
    h.NumberOfBits = 32;
end
if strcmpi(binaryValue(20), '1')
    h.NumberOfBits = 16;
end
if strcmpi(binaryValue(21), '1')
    h.NumberOfBits = 8;
end
if strcmpi(binaryValue(22), '1')
    h.NumberOfBits = 1;
end
if strcmpi(binaryValue(25), '1')
    h.BaudRate = 115200;
end
if strcmpi(binaryValue(26), '1')
    h.BaudRate = 57600;
end
if strcmpi(binaryValue(27), '1')
    h.BaudRate = 38400;
end
if strcmpi(binaryValue(28), '1')
    h.BaudRate = 19200;
end
if strcmpi(binaryValue(29), '1')
    h.BaudRate = 9600;
end
if strcmpi(binaryValue(30), '1')
    h.BaudRate = 2400;
end

function h = setFlagsCBDINP(flags)

binaryValue = dec2bin(flags,22);

h.BaudRate = 0;
h.NumberOfBits = 0;
h.PortReadType = 0;
h.CapturePacketMethod = 0;
h.PortControlsEvent = 0;

if strcmpi(binaryValue(1), '1')
    h.PortReadType = '8-bit strobe/8-bit falling edge';
end
if strcmpi(binaryValue(2), '1')
    h.PortReadType = '8-bit strobe/8-bit rising edge';
end
if strcmpi(binaryValue(3), '1')
    h.PortReadType = '8-bit strobe/8-bit any edge';
end
if strcmpi(binaryValue(4), '1')
    h.PortReadType = '8-bit any bit falling edge';
end
if strcmpi(binaryValue(5), '1')
    h.PortReadType = '8-bit any bit rising edge';
end
if strcmpi(binaryValue(6), '1')
    h.PortControlsEvent = 1;
end
if strcmpi(binaryValue(7), '1')
    h.CapturePacketMethod = 'Logic input';
end
if strcmpi(binaryValue(8), '1')
    h.CapturePacketMethod = 'Character';
end
if strcmpi(binaryValue(9), '1')
    h.CapturePacketMethod = 'Word strob';
end
if strcmpi(binaryValue(10), '1')
    h.CapturePacketMethod = 'Any bit change';
end
if strcmpi(binaryValue(11), '1')
    h.NumberOfBits = 32;
end
if strcmpi(binaryValue(12), '1')
    h.NumberOfBits = 16;
end
if strcmpi(binaryValue(13), '1')
    h.NumberOfBits = 8;
end
if strcmpi(binaryValue(14), '1')
    h.NumberOfBits = 1;
end
if strcmpi(binaryValue(17), '1')
    h.BaudRate = 115200;
end
if strcmpi(binaryValue(18), '1')
    h.BaudRate = 57600;
end
if strcmpi(binaryValue(19), '1')
    h.BaudRate = 38400;
end
if strcmpi(binaryValue(20), '1')
    h.BaudRate = 19200;
end
if strcmpi(binaryValue(21), '1')
    h.BaudRate = 9600;
end
if strcmpi(binaryValue(22), '1')
    h.BaudRate = 2400;
end

function h = setFlagsCBAOUT(flags)

binaryValue = dec2bin(flags,22);

h.Audio = 0;
h.AnalogScale = 0;
h.ChanFunction = '';

if strcmpi(binaryValue(1), '1')
    h.ChanFunction = 'Waveform generator';
end
if strcmpi(binaryValue(2), '1')
    h.ChanFunction = 'Monitors spikes';
end
if strcmpi(binaryValue(3), '1')
    h.ChanFunction = 'Monitors SMP';
end
if strcmpi(binaryValue(4), '1')
    h.ChanFunction = 'Monitors LNC';
end
if strcmpi(binaryValue(5), '1')
    h.ChanFunction = 'Monitors raw';
end
if strcmpi(binaryValue(6), '1')
    h.AnalogScale = 'Waveform generator';
end
if strcmpi(binaryValue(7), '1')
    h.AnalogScale = 'Waveform generator';
end
if strcmpi(binaryValue(8), '1')
    h.AnalogScale = 'Waveform generator';
end
if strcmpi(binaryValue(9), '1')
    h.Audio = 1;
end

function h = setFlagsCBAINP(flags)

binaryValue = dec2bin(flags,22);

h.Audio = 0;
h.AnalogScale = 0;
h.ChanFunction = '';

if strcmpi(binaryValue(1), '1')
    h.ChanFunction = 'Waveform generator';
end
if strcmpi(binaryValue(2), '1')
    h.ChanFunction = 'Monitors spikes';
end
if strcmpi(binaryValue(3), '1')
    h.ChanFunction = 'Monitors SMP';
end
if strcmpi(binaryValue(4), '1')
    h.ChanFunction = 'Monitors LNC';
end
if strcmpi(binaryValue(5), '1')
    h.ChanFunction = 'Monitors raw';
end
if strcmpi(binaryValue(6), '1')
    h.AnalogScale = 'Waveform generator';
end
if strcmpi(binaryValue(7), '1')
    h.AnalogScale = 'Waveform generator';
end
if strcmpi(binaryValue(8), '1')
    h.AnalogScale = 'Waveform generator';
end
if strcmpi(binaryValue(9), '1')
    h.Audio = 1;
end


function prefs = mrPrefs;
% Prompts user to adjust MR object-related preferences. Initializes defaults
% if they don't exist.
%
% prefs = mrPrefs;
%
% The user will usually only be prompted if the preferences don't yet
% exist. However, if you call this function with no return arg, it will
% always prompt.
%
% STILL UNDER CONSTRUCTION
%
% ras, 04/2006.
if(nargout==0)
    promptUser = true;
else
    promptUser = false;
end

% get default values for prefs --
% to edit new prefs, define them in this sub-routine.
dlg = defaultPrefsDialog;
prefNames = {dlg.fieldName}; % names of all prefs to be defined

% check that all methods are defined
if ispref('mr')
    prefs = getpref('mr');
    notAssigned = setdiff(prefNames, fieldnames(prefs));
    if ~isempty(notAssigned)
        promptUser = true;
    end
else
    % need to initialize
    disp('Initializing mrVista2 MR object preferences')
    dlg = defaultPrefsDialog;
    for i = 1:length(dlg)
        setpref('mr', dlg(i).fieldName, dlg(i).value);
    end
    prefs = getpref('mr');
    promptUser = true;
end

% put up a dialog, if requested or necessary
if promptUser
    % plug in any already-defined prefs over the default values
    if exist('prefs') & ~isempty(prefs)
        for f = fieldnames(prefs)
            ii = cellfind(prefNames, f{1});
            if ii>0 % this is a valid pref to set
                dlg(ii).value = prefs.(f{1});
            end
        end
    end

    prefs = generalDialog(dlg, 'mrVista2 MR Preferences');

    % exit quietly if canceled -- otherwise update prefs
    if isempty(prefs)
        return;
    else
        for f = fieldnames(prefs)'
            setpref('mr', f{1}, prefs.(f{1}));
        end
    end
end

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

% /------------------------------------------------------------------/ %
function dlg = defaultPrefsDialog;
% return the default MR prefs names/values as a dialog. This seems
% to be the easiest way to describe new preferences.
dlg(1).fieldName = 'interp';
dlg(1).style = 'popup';
dlg(1).string = 'Interpolation Method for Different Coordinate Spaces?';
dlg(1).list = {'Nearest-Neighbor' 'Linear' 'Cubic' 'Spline'};
dlg(1).value = 'Linear';

dlg(end+1).fieldName = 'alphaMethod';
dlg(end).style = 'popup';
dlg(end).string = 'Alpha Method for Multiple Overlays?';
dlg(end).list = {'Average: [1 0 0] + [.2 0 1] => [.6 0 .5]' ...
                'Add and saturate: [1 0 0] + [.2 0 1] => [1 0 1]' ...
                'Opaque: [1 0 0] + [.2 0 1] => [.2 0 1]'};
dlg(end).value = 1;

return


function state= util_warning(msg, id, varargin)
%UTIL_WARNING - Display warning message
%
%Description:
% Essentially this function is just a work-around such that one can
% use the increased functionality of the warning function of Matlab R>=13,
% while having programs that still run under Matlab R<13.
% Furthermore, this function allows to suppress warnings which follow each
% other within a short interval of time.
%
%Usage:
% STATE= util_warning(MSG, ID, <OPT>)
%
%Input:
% MSG:  (string) the warning message, or 'off' or 'query', see the
%       help of 'warning'.
% ID:   (string)
% OPT:  (struct(proplist) Property/value list or struct of optional parameters:
%   'Interval': Warnings that appear earlier than this interval of time [s]
%        after a warning (for the same ID) are suppressed. Default 10*60.
%   'File': Display this as the file that issued the warning.
%        Default: name of the calling function.
%
%
%Example 1 (within a matlab function):
% util_warning('outer model selection sucks', 'validation');
%
%Example 2:
% wstat= util_warning('off', 'validation');
% %% ... some code admittedly producing 'validation' warnings ...
% util_warning(wstat);

persistent id_list last_warning

st= dbstack;
if length(st)>1,
  default_File= st(2).name;
else
  default_File= '';
end
props= {'File'       default_File   'CHAR';
        'Interval'   10*60          '!DOUBLE[1]';
       };
opt= opt_proplistToStruct(varargin{:});
opt= opt_setDefaults(opt, props, 1);

misc_checkType(msg, 'CHAR');
misc_checkType(id, 'CHAR');

this_warning= clock;
id_idx= strmatch(msg, id_list, 'exact');
if isempty(id_idx),
  id_list= cat(2, id_list, {msg});
  last_warning= [last_warning; this_warning];
else
  if etime(this_warning, last_warning(id_idx,:)) < opt.Interval,
    return;
  end
  last_warning(id_idx,:)= this_warning;
end

if isempty(opt.File),
  ff= '';
else
  ff= [opt.File ': '];
end

a= sscanf(getfield(ver('MATLAB'), 'Release'), '(R%d)');
if a>=13,
  if isstruct(msg),
    warning(msg);
  elseif strcmp(msg, 'off'),
    if nargout>0,
      state= warning('query', ['BBCI:' id]);
    end
    warning('off', ['bbci:' id]);
  elseif strcmp(msg, 'query'),
    state= warning('query', ['BBCI:' id]);
  else
    warning(['bbci:' id], [ff msg]);
  end
else
  if ~isempty(msg) & isempty(strmatch(msg, {'off','query'})),
    state= warning([ff msg]);
  else
    state= [];
  end
end


import matlab.unittest.TestRunner;
import matlab.unittest.TestSuite;
import matlab.unittest.plugins.TestReportPlugin;
import matlab.unittest.plugins.XMLPlugin
import matlab.unittest.plugins.DiagnosticsValidationPlugin

try
    suite = testsuite({'DemoTests'});

    runner = matlab.unittest.TestRunner.withTextOutput('OutputDetail',1);
    runner.addPlugin(DiagnosticsValidationPlugin)

    xmlFile = 'BSPDemoTests.xml';
    plugin = XMLPlugin.producingJUnitFormat(xmlFile);
    runner.addPlugin(plugin);

    results = runner.run(suite);

    t = table(results);
    disp(t);
    disp(repmat('#',1,80));
    for test = results
        if test.Failed
            disp(test.Name);
        end
    end
catch e
    disp(getReport(e,'extended'));
    bdclose('all');
    exit(1);
end

save(['BSPInstallerTest_',datestr(now,'dd_mm_yyyy-HH:MM:SS'),'.mat'],'t');
bdclose('all');
exit(any([results.Failed]));


classdef MimModelMap < CoreBaseClass

    properties (Access = private)
        Mim
        ModelCache
    end

    methods
        function obj = MimModelMap(mim)
            obj.Mim = mim;
            obj.clear();
        end

        function [value, hash] = getValue(obj, modelId)
            model = obj.ModelCache(modelId);
            [value, hash] = model.getOrRun();
        end

        function value = getDependentValue(obj, modelId, dependentModelId)
            % Gets a model value and adds the caller as a dependent

            model = obj.ModelCache(modelId);
            model.addDependentModel(obj.ModelCache(dependentModelId));
            value = model.getOrRun();
        end

        function setValue(obj, modelId, value)
            model = obj.ModelCache(modelId);
            model.setValue(value);
        end

        function addItem(obj, modelId, itemId)
            model = obj.ModelCache(modelId);
            model.addItem(modelId, itemId);
        end

        function removeItem(obj, modelId, itemId)
            model = obj.ModelCache(modelId);
            model.removeItem(itemId);
        end

        function autoUpdate(obj)
            % Triggers updating of invalid models that have AutoUpdate

            for model = obj.ModelCache.values()
                model{1}.update();
            end
        end

        function modelId = buildModelId(obj, modelClassName, parameters)
            % ToDo: Check parameters so as not to duplicate identical models

            modelConstructor = str2func(modelClassName);
            modelId = CoreSystemUtilities.GenerateUid();
            model = modelConstructor();
            model.initialise(modelId, parameters, MimModelCallback(modelId, obj, obj.Mim), true);
            obj.ModelCache(modelId) = model;
        end

        function clear(obj)
            obj.ModelCache = containers.Map();
            % Explicitly add in models with unique names
            model = MimSubjectList();
            model.initialise('MimSubjectList', {}, MimModelCallback('MimSubjectList', obj, obj.Mim), true);
            obj.ModelCache('MimSubjectList') = model;
        end
    end
end


classdef RollAngleTermCondOptimizationVariable < AbstractOptimizationVariable
    %RollAngleTermCondOptimizationVariable Summary of this class goes here
    %   Detailed explanation goes here

    properties
        varObj(1,1) RollTermCondition = RollTermCondition(0);

        lb(1,1) double = 0;
        ub(1,1) double = 0;

        useTf(1,1) = false;
    end

    methods
        function obj = RollAngleTermCondOptimizationVariable(varObj)
            obj.varObj = varObj;
            obj.varObj.optVar = obj;

            obj.id = rand();
        end

        function x = getXsForVariable(obj)
            x = [];

            if(obj.useTf)
                x = obj.varObj.targetRollAngle;
            end
        end

        function [lb, ub] = getBndsForVariable(obj)
            lb = obj.lb(obj.useTf);
            ub = obj.ub(obj.useTf);
        end

        function [lb, ub] = getAllBndsForVariable(obj)
            lb = obj.lb;
            ub = obj.ub;
        end

        function setBndsForVariable(obj, lb, ub)
            obj.lb = lb;
            obj.ub = ub;
        end

        function useTf = getUseTfForVariable(obj)
            useTf = obj.useTf;
        end

        function setUseTfForVariable(obj, useTf)
            obj.useTf = useTf;
        end

        function updateObjWithVarValue(obj, x)
            obj.varObj.targetRollAngle = x;
        end

        function nameStrs = getStrNamesOfVars(obj, evtNum, varLocType)
            nameStrs = {sprintf('Event %i Roll Angle Termination Condition', evtNum)};
        end

        function varsStoredInRad = getVarsStoredInRad(obj)
            varsStoredInRad = true;
        end
    end
end


classdef testPrintingPostProcessComputer < handle

    properties (Access = private)
        iter
        fileName
        postProcessCase
        postProcessCreateData
        postProcessPrintData
    end

    methods (Access = public)

        function obj = testPrintingPostProcessComputer(d)
            obj.init(d);
            obj.loadPostProcessCreateData();
            obj.loadPostProcessPrintData();
            obj.print();
        end

        function print(obj)
            it = obj.iter;
            pC = obj.postProcessCase;
            cD = obj.postProcessCreateData;
            pD = obj.postProcessPrintData;
            postProcess = Postprocess(pC,cD);
            postProcess.print(it,pD);
        end

    end

    methods (Access = private)

        function init(obj,d)
            obj.iter            = d.iter;
            obj.fileName        = d.file;
            obj.postProcessCase = d.postCase;
        end

        function loadPostProcessCreateData(obj)
            loadData = load(obj.fileName);
            cD = loadData.postProcessData.createData;
            obj.postProcessCreateData = cD;
        end

        function loadPostProcessPrintData(obj)
            loadData = load(obj.fileName);
            pD = loadData.postProcessData.printData;
            obj.postProcessPrintData = pD;
        end

    end

end


function [speciespKas,includedMets] = assignpKasToSpecies(mets,neutralMolfileDir,msDistrDir,pKaDir)

% Use each metabolite's InChI and ChemAxon's cxcalc (calculator plugin) to
% get pKas(I=0), charges and numbers of protons for all microspecies
% present at pH 5-9.
%
% [speciespKas,includedMets] = assignpKasToSpecies(mets,neutralMolfileDir,msDistrDir,pKaDir)
%
% INPUTS
% mets                  Cell array of metabolite ID. Current implementation
%                       assumes ID are formatted as BiGG_ID[Compartment];
%                       e.g., atp[c] for ATP in cytosol.
% neutralMolfileDir     Path to directory containing molfiles for neutral
%                       metabolites. Molfiles must be named with the
%                       metabolite ID in mets; e.g., atp.mol.
% msDistrDir            Path to directory containing metabolite species
%                       distributions at pH 5, 6, 7, 8 and 9. Species
%                       distributions are created with the ChemAxon
%                       calculator plugin. Names of SD files containing
%                       species distributions should be formatted as
%                       metID_msdistr_pHA.sdf; e.g., atp_msdistr_pH5.sdf.
% pKaDir                Path to directory containing pKa estimates for
%                       metabolites. pKas are estimated with the ChemAxon
%                       calculator plugin. Names of text files containing
%                       pKa estimates should be formatted as
%                       metID_pkas.txt; e.g., atp_pkas.txt.
%
% OUTPUTS
% speciespKas       Structure containing the following fields for each
%                   metabolite:
% .inchis           n x 1 cell array with a species-specific InChI string
%                   for each species. inchis(1) = InChI for species 1 etc.
%                   NOTE: Species 1 is the species with the fewest hydrogen
%                   atoms etc.
% .formulas         n x 1 cell array with species-specific chemical
%                   formulas.
% .zs               n x 1 vector containing the charge on each species.
% .nHs              n x 1 vector containing the number of hydrogen atoms in
%                   each species.
% .majorMSpH7       n x 1 boolean vector with a logical 1 in the row for
%                   the major microspecies at pH 7 according to ChemAxon.
% .abundanceAtpH7   n x 1 vector with species percentage abundance at pH 7.
% .pKas             n x n matrix where element i,j is the pKa for the
%                   acid-base equilibrium between species i and j.
% includedMets      Cell array of metabolite ID for those metabolites that
%                   were included in speciespKas. Should include all
%                   metabolites that had molfiles in neutralMolfileDir.

phs = 5:9; % Physiological pH range

dirData = dir(neutralMolfileDir);
molfileList = cat(2,dirData.name);
molfileList = regexp(molfileList,'.mol','split');
molfileList = regexprep(molfileList,'\.','');
molfileList = molfileList(~cellfun('isempty',molfileList));

includedMets = {};

model.mets = mets;
nMets = length(model.mets);
done = cell(nMets,1);

for k = 1:nMets
    disp(['Starting on metabolite ' num2str(k) '. Only ' num2str(nMets-k) ' metabolites to go.'])

    if ~any(strcmp(model.mets{k}(1:end-3),done))
        done{k} = model.mets{k}(1:end-3);

        if any(strcmp(molfileList,model.mets{k}(1:(end-3))))
            includedMets = [includedMets; model.mets{k}(1:end-3)];

            % Get data from sdf files with microspecies distributions
            % for all species with molfractions above threshold
            totalCharges = {};
            chargedAtoms = {};
            atomicCharges = {};
            currentPhs = {};
            percentages = {};
            mmsPh7Bool = {};

            fid5 = fopen('abundantSpecies.sdf', 'w+'); % Create separate sdf file for species with molfractions above threshold
            fclose(fid5);

            for n = 1:length(phs)
                ph = phs(n);

                newPh = 1; % True while processing first microspecies at each pH

                %     Open microspecies distribution at ph
                fid2 = fopen([msDistrDir filesep model.mets{k}(1:(end-3)) '_msdistr_ph' num2str(ph) '.sdf'], 'r'); % Open sdf file with microspecies distribution for reading

                charged = 0; % Neutral until proven charged

                %     Parse speciesDistribution.sdf for all microspecies that are present at ph
                while 1
                    tline = fgetl(fid2); % Get next line of speciesPhDistribution.sdf

                    if ~ischar(tline) % Exit while loop if the last line of speciesPhDistribution.sdf is reached, continue at next pH
                        fclose(fid2);
                        delete currentSpecies.sdf
                        pause(eps);
                        break;
                    end

                    fid3 = fopen('currentSpecies.sdf', 'a'); % Create separate sdf file for species with molfractions above threshold
                    fprintf(fid3, '%s\r\n', tline); % Copy line to abundantSpecies.sdf
                    fclose(fid3);

                    if ~isempty(strfind(tline, 'M  CHG'))
                        charged = 1;
                        chargeLine = tline;
                    end

                    if ~isempty(strfind(tline, '>  <DISTR')) % Get the percentage of current microspecies at ph
                        percentLine = fgetl(fid2);
                        fid3 = fopen('currentSpecies.sdf', 'a'); % Create separate sdf file for species with molfractions above threshold
                        fprintf(fid3, '%s\r\n', percentLine); % Copy line to abundantSpecies.sdf
                        fclose(fid3);

                        percentage = str2double(regexprep(percentLine, ',', '\.'));

                        if percentage == 0 % Exit while loop at first microspecies that is not present at current pH. Continue at next pH.
                            fclose(fid2);
                            delete currentSpecies.sdf
                            pause(eps);
                            break;
                        else
                            currentPhs = [currentPhs; {ph}];
                            percentages = [percentages; {percentage}];

                            % Get numbers of atoms carrying charge, charge on each atom
                            % and total charge
                            currentChargedAtoms = [];
                            currentAtomicCharges = [];

                            if charged
                                splitLine = regexp(strtrim(chargeLine(11:end)), '\s+', 'split');
                                AtomIdx = 1:2:(length(splitLine)-1);
                                chargeIdx = 2:2:length(splitLine);

                                for m = 1:length(AtomIdx)
                                    currentChargedAtoms = [currentChargedAtoms, str2double(splitLine{AtomIdx(m)})];
                                end

                                for m = 1:length(chargeIdx)
                                    currentAtomicCharges = [currentAtomicCharges, str2double(splitLine{chargeIdx(m)})];
                                end

                                currentTotalCharge = sum(currentAtomicCharges);

                            else
                                currentTotalCharge = 0;

                            end

                            chargedAtoms = [chargedAtoms; {currentChargedAtoms}];
                            atomicCharges = [atomicCharges; {currentAtomicCharges}];
                            totalCharges = [totalCharges; {currentTotalCharge}];
                        end
                    end

                    if ~isempty(strfind(tline, '$$$$')) % End of sdf for current species
                        system('type currentSpecies.sdf >> abundantSpecies.sdf');
                        delete currentSpecies.sdf
                        pause(eps);
                        if ph == 7 && newPh
                            mmsPh7Bool = [mmsPh7Bool; {true}]; % True for major microspecies at pH 7, the one WebGCM computes Gibbs energy of formation for
                        else
                            mmsPh7Bool = [mmsPh7Bool; {false}];
                        end

                        newPh = 0; % reset
                        charged = 0; % Reset
                    end
                end
            end

            % Get additional species data
            % Get formulas
            [~, formulas] = system('cxcalc formula abundantSpecies.sdf');
            formulas = regexp(formulas,'\s','split');
            formulas = formulas(4:2:2*length(mmsPh7Bool)+3)';

            % Get number of hydrogen atoms
            numberHs = cell(size(formulas));
            for spnr = 1:length(formulas)
                numberHs{spnr} = numAtomsOfElementInFormula(formulas{spnr},'H');
            end

            % Get inchis for all species in abundantSpecies.sdf
            [~, inchis] = system('babel abundantSpecies.sdf -oinchi -xFwT/nostereo');
            inchis = regexp(inchis, '\n', 'split');
            inchiIdx = regexp(inchis,'InChI=');
            inchis = inchis(~cellfun('isempty',inchiIdx))';
            inchiIdx = inchiIdx(~cellfun('isempty',inchiIdx))';

            if ~isempty(setdiff(1:length(inchis),strmatch('InChI=',inchis)))
                errlins = setdiff(1:length(inchis),strmatch('InChI=',inchis));
                for errlini = 1:length(errlins)
                    inchis{errlins(errlini)} = inchis{errlins(errlini)}(inchiIdx{errlins(errlini)}:end);
                end
            end

            % Create dataArray with all species data (non-unique)
            dataArray = [inchis formulas numberHs totalCharges chargedAtoms atomicCharges currentPhs percentages mmsPh7Bool];

            % Sum up percentages for different stereoisomers of the same
            % species at the same pH
            stiCorrectedDataArray = [];

            for stph = 5:9
                currentpHBool = cat(1,dataArray{:,7}) == stph;
                dataArrayCurrentpH = dataArray(currentpHBool,:);
                uniqueInchisCurrentpH = unique(inchis(currentpHBool));
                for stinr = 1:length(uniqueInchisCurrentpH)
                    stiCorrectedDataArray = [stiCorrectedDataArray; dataArrayCurrentpH(find(ismember(dataArrayCurrentpH(:,1),uniqueInchisCurrentpH{stinr}),1),:)];
                    stiCorrectedDataArray{end,8} = sum(cat(1,dataArrayCurrentpH{ismember(dataArrayCurrentpH(:,1),uniqueInchisCurrentpH{stinr}),8}));
                end
            end

            dataArray = stiCorrectedDataArray;

            % Discard species that are present below some logical
            % threshold percentage
            thresholdPercentage = 1;
            belowThresholdBool = cat(1,dataArray{:,8}) < thresholdPercentage;
            dataArray = dataArray(~belowThresholdBool,:);

            abundanceAtpH7 = zeros(size(dataArray,1),1);
            abundanceAtpH7(cat(1,dataArray{:,7}) == 7) = cat(1,dataArray{cat(1,dataArray{:,7})==7,8});
            dataArray = [dataArray, num2cell(abundanceAtpH7)];

            % Get inchis for all species in abundantSpecies.sdf
            %                 [~, uniqueInchis] = system('babel abundantSpecies.sdf -oinchi -xFwuT/nostereo');
            %                 uniqueInchis = regexp(uniqueInchis, '\n', 'split');
            %                 uniqueInchis = uniqueInchis(strmatch('InChI=',uniqueInchis))';
            inchis = dataArray(:,1);
            uniqueInchis = unique(inchis);

            uniqueDataArray = {};
            for inr = 1:length(uniqueInchis)
                sameSpeciesBool = ismember(dataArray(:,1),uniqueInchis{inr});
                tmpDataArray = dataArray(sameSpeciesBool,:);
                [~, speciesIdxInTmpDataArray] = max(cat(1,tmpDataArray{:,8}));
                uniqueDataArray = [uniqueDataArray; tmpDataArray(speciesIdxInTmpDataArray,:)];
                if any(cat(1,dataArray{sameSpeciesBool,9}))
                    uniqueDataArray{end,9} = true;
                end
                uniqueDataArray{end,10} = max(cat(1,dataArray{sameSpeciesBool,10}));
            end

            dataArray = uniqueDataArray;

            % Get charge and number of H atoms for fully protonated
            % species for later corrections
            nmoldir = fopen([neutralMolfileDir filesep model.mets{k}(1:(end-3)), '.mol'], 'r');
            nmolcharged = false;

            while 1
                tline = fgetl(nmoldir);

                if ~ischar(tline)
                    break;
                end

                if ~isempty(strfind(tline, 'M  CHG'))
                    nmolcharged = true;
                    nmolAcharges = regexp(tline(12:end),'\s+','split');
                    nmolAcharges = nmolAcharges(~cellfun('isempty',nmolAcharges));
                    nmolAcharges = nmolAcharges(2:2:end);
                    acnum = [];
                    for acIdx = 1:length(nmolAcharges)
                        acnum = [acnum, str2double(nmolAcharges(acIdx))];
                    end
                    nmolcharge = sum(acnum);
                end
            end

            fclose(nmoldir);

            if ~nmolcharged
                nmolcharge = 0;
            end

            [~, nmolformula] = system(['cxcalc formula ' neutralMolfileDir filesep model.mets{k}(1:(end-3)), '.mol']);
            nmolformula = regexp(nmolformula,'\s','split');
            nmolformula = nmolformula{4};
            nmolnH = numAtomsOfElementInFormula(nmolformula,'H');

            dataArray(:,3) = num2cell(nmolnH + (cat(1,dataArray{:,4}) - nmolcharge));
            dataArray = sortrows(dataArray,[3, 4]); % Sort by number of hydrogen atoms, then charge, in ascending order so row 1 contains data for species 1.

            % Boolean vector indicating major microspecies at pH 7
            mmsBool = logical(cat(1,dataArray{:,9}));

            % % Figure out which groups are in equilibrium at the specified pH by
            % comparing numbering of atoms carrying charge in each microspecies.
            nSpecies = size(dataArray,1);
            metIdx = length(includedMets);

            speciespKas(metIdx).abbreviation = includedMets{metIdx};
            speciespKas(metIdx).inchis = dataArray(:,1);
            speciespKas(metIdx).formulas = dataArray(:,2);
            speciespKas(metIdx).zs = cat(1,dataArray{:,4});
            speciespKas(metIdx).nHs = cat(1,dataArray{:,3});
            speciespKas(metIdx).majorMSpH7 = mmsBool;
            speciespKas(metIdx).abundanceAtpH7 = cat(1,dataArray{:,10});

            if nSpecies >= 2 % If there is only one species there is no need to compute pKas

                % Get species pKas
                maxChargedAtom = max(unique(cat(2,dataArray{:,5}))); % Find highest atomic number carrying charge
                chargeMatrix = zeros (nSpecies,maxChargedAtom); % Each column is an atom, each row a species. Each element is the charge on an atom in a species.
                atomNumbers = {};

                for n = 1:nSpecies
                    if ~isempty(dataArray{n,5}) % If empty the species is neutral and no atoms carry charge
                        chargeMatrix(n,dataArray{n,5}) = dataArray{n,6}; % Populate chargeMatrix
                    end
                end

                speciesEquilibriumBool = false(nSpecies,nSpecies);
                for spRows = 1:nSpecies
                    for spCols = 1:nSpecies
                        if length(find(chargeMatrix(spRows,:) ~= chargeMatrix(spCols,:))) == 1 && abs(abs(dataArray{spRows,4}) - abs(dataArray{spCols,4})) == 1
                            speciesEquilibriumBool(spRows,spCols) = true;
                        end
                    end
                end

                speciesEquilibriumAtomNrs = zeros(size(speciesEquilibriumBool));
                for n = 1:(nSpecies-1)
                    theseAtomNumbers = [];
                    oneup = find(speciesEquilibriumBool(n,:) & (1:nSpecies > n)); % Find row indices of all species with one more H atom

                    if ~isempty(oneup) % Not for species with the highest number of H atoms
                        for l = 1:length(oneup)
                            theseAtomNumbers = [theseAtomNumbers, find(chargeMatrix(n,:) ~= chargeMatrix(oneup(l),:))]; % Find number of atom being protonated/deprotonated in acid-base equilibrium between species n and species n+1
                            speciesEquilibriumAtomNrs(n,oneup(l)) = find(chargeMatrix(n,:) ~= chargeMatrix(oneup(l),:));
                        end

                        atomNumbers = [atomNumbers; {theseAtomNumbers}];
                    end
                end
                speciesEquilibriumAtomNrs = speciesEquilibriumAtomNrs + speciesEquilibriumAtomNrs';

                fid4 = fopen([pKaDir filesep model.mets{k}(1:(end-3)) '_pkas.txt'], 'r'); % Open txt file with pkas for reading
                headerline = fgetl(fid4);
                pkaline = fgetl(fid4);
                fclose(fid4);

                %     Construct two column cell array with pkas of all functional groups
                %     and numbers of atoms associated with each pka
                splitline = regexp(pkaline, '\s', 'split');
                pkaCells = splitline(2:end-1);
                pkaCells = pkaCells(~cellfun('isempty',pkaCells));
                pkaCells = regexprep(pkaCells, ',', '\.');

                splitAtomNumbers = regexp(splitline{end}, ',', 'split');
                pkaCells = [pkaCells;splitAtomNumbers]';
                pkaMat = zeros(size(pkaCells));

                for n = 1:size(pkaMat,1);
                    pkaMat(n,1) = str2double(pkaCells{n,1});
                    pkaMat(n,2) = str2double(pkaCells{n,2});

                end

                %     Correlate numbers of atoms associated with each pka with numbers of
                %     atoms in atomNumbers to get pka for equilibrium between each pair of
                %     microspecies
                speciesEquilibriumPkas = zeros(size(speciesEquilibriumBool));

                for n = 1:length(atomNumbers)
                    for m = 1:length(atomNumbers{n})
                        pkaIdx = find(atomNumbers{n}(m) == pkaMat(:,2));
                        if length(pkaIdx) == 1
                            speciesEquilibriumPkas(speciesEquilibriumAtomNrs == pkaMat(pkaIdx,2)) = pkaMat(pkaIdx,1);

                        elseif length(pkaIdx) > 1 % Many atoms will have two pKas; one acidic (for when it donates a proton) and one basic (for when it accepts a proton)
                            [~, minIdx] = min(abs(dataArray{n,7} - pkaMat(pkaIdx,1))); % Assume the appropriate pKa is the one closer to the pH where species is present at its maximum
                            speciesEquilibriumPkas(speciesEquilibriumAtomNrs == pkaMat(pkaIdx(minIdx),2)) = pkaMat(pkaIdx(minIdx),1);
                        end

                    end

                end

                speciespKas(metIdx).pKas = speciesEquilibriumPkas;

            else
                speciespKas(metIdx).abbreviation = includedMets{metIdx};
                speciespKas(metIdx).inchis = dataArray(:,1);
                speciespKas(metIdx).formulas = dataArray(:,2);
                speciespKas(metIdx).zs = cat(1,dataArray{:,4});
                speciespKas(metIdx).nHs = cat(1,dataArray{:,3});
                speciespKas(metIdx).majorMSpH7 = mmsBool;
                speciespKas(metIdx).abundanceAtpH7 = 100;
                speciespKas(metIdx).pKas = [];
            end

        end

    end

end

speciespKas = speciespKas';


function p = printRxnFormulaLinked(model, rxnAbbrList, printFlag, metNameFlag, flux, nCharBreak, commandUsingMet)
% print reaction formulae in which the metabolites are hyperlinked to run a
% specific command involving the metabolites when displayed in the Matlab
% command window. Called by `surfNet.m`.
%
% USAGE:
%    p = printRxnFormulaLinked(model, rxnAbbrList, printFlag, metNameFlag, flux, nCharBreak, commandUsingMet)
%
% INPUTS:
%    model:                COBRA model
%
% OPTIONAL INPUTS:
%    rxnAbbrList:          cell array of reactions to be printed (default all reactions)
%    printFlag:            true to print, otherwise return the strings only (default true)
%    metNameFlag:          true to display met names in *.metNames instead of *.mets (default false)
%    flux:                 the flux of the reaction. When non-empty, determine the arrow for the formula accordingly
%    nCharBreak:           max. number of character printed on each line
%    commandUsingMet:      string of Matlab command for the genes in the model, e.g., 'fprintf(''%s'')' where %s
%                          will be replaced by the metabolites (default [], not adding the field for hyperlinked grRules)
if nargin < 2 || isempty(rxnAbbrList)
    rxnAbbrList = model.rxns;
end
if nargin < 3 || isempty(printFlag)
    printFlag = true;
end
if nargin < 4 || isempty(metNameFlag)
    metNameFlag = false;
end
if nargin < 5
    flux = [];
end
if nargin < 6 || isempty(nCharBreak)
    nCharBreak = get(0, 'CommandWindowSize');
    nCharBreak = nCharBreak(1);
end
if nargin < 7 || isempty(commandUsingMet)
    commandUsingMet = 'fprintf(''%s\\n'')';
end

if ~ischar(rxnAbbrList) && ~(iscell(rxnAbbrList) && numel(rxnAbbrList) == 1)
    p = cell(numel(rxnAbbrList), 1);
    for j = 1:numel(rxnAbbrList)
        p(j) = printRxnFormulaLinked(model, rxnAbbrList{j}, printFlag, metNameFlag, flux, nCharBreak);
    end
    return
end

rxnID = findRxnIDs(model, rxnAbbrList);

% determine the arrow sign
arrow = '->';
if (isempty(flux) || flux == 0) && model.lb(rxnID) < 0 && model.ub(rxnID) > 0
    arrow = '<=>';
elseif (isempty(flux) || flux == 0) && model.lb(rxnID) < 0 && model.ub(rxnID) <= 0
    arrow = '<-';
elseif ~isempty(flux) && flux < 0
    arrow = '<-';
end

% reactant
metR = model.S(:, rxnID) < 0;
if ~any(metR)
    reactantSide = cell(2, 0);
    isMetR = false(2, 0);
    isMetRst = false(2, 0);
else% reactant stoichiometry
    metRst = cellfun(@num2str, num2cell(abs(model.S(metR, rxnID))), 'UniformOutput', false);
    % not printing the coefficient if it is 1
    metRst(model.S(metR, rxnID) == -1) = {''};
    reactantSide = [metRst(:)'; columnVector(model.mets(metR))'; repmat({'+'}, 1, sum(metR) - 1), {arrow}];
    isMetR = [false(1, sum(metR)); true(1, sum(metR)); false(1, sum(metR))];
    isMetRst = [true(1, sum(metR)); false(2, sum(metR))];
end

% product
metP = model.S(:, rxnID) > 0;
if ~any(metP)
    productSide = cell(2, 0);
    isMetP = false(2, 0);
    isMetPst = false(2, 0);
else
    % product stoichiometry
    metPst = cellfun(@num2str, num2cell(model.S(metP, rxnID)), 'UniformOutput', false);
    % not printing the coefficient if it is 1
    metPst(model.S(metP, rxnID) == 1) = {''};
    productSide = [metPst(:)'; columnVector(model.mets(metP))'; repmat({'+'}, 1, sum(metP) - 1), {''}];
    isMetP = [false(1, sum(metP)); true(1, sum(metP)); false(1, sum(metP))];
    isMetPst = [true(1, sum(metP)); false(2, sum(metP))];
end

formulaeParts = [reactantSide(:); productSide(:)];
isMet = [isMetR(:); isMetP(:)];
isStoich = [isMetRst(:); isMetPst(:)];
% remove empty cells
isMet(cellfun(@isempty, formulaeParts)) = [];
isStoich(cellfun(@isempty, formulaeParts)) = [];
formulaeParts(cellfun(@isempty, formulaeParts)) = [];
isArrow = strcmp(formulaeParts, arrow);

if metNameFlag
    productSide(2, :) = columnVector(model.metNames(metP))';
    reactantSide(2, :) = columnVector(model.metNames(metR))';
    formulaePartsToPrint = [reactantSide(:); productSide(:)];
    formulaePartsToPrint(cellfun(@isempty, formulaePartsToPrint)) = [];
else
    formulaePartsToPrint = formulaeParts;
end

%printFormulae(formulaeParts, dict, arrow, metNameFlag, nCharBreak, nCharBreakReal)

nChar = 0;  % number of characters printed in the current line
p = '';
[lineBreak, firstWordOnLine] = deal(false, true);

for jPart = 1:numel(formulaeParts)

    nChar = nChar + length(formulaePartsToPrint{jPart}) + 1;
    if ~isMet(jPart)  % not a metabolite name (stoich/'+'/'->'/'<=>')
        spaceAdded = 0;  % additional space for arrow
        if isArrow(jPart)
            spaceAdded = 4;
        end
        % check if a new line is needed
        if ~firstWordOnLine
            if ~isStoich(jPart) && jPart < numel(formulaeParts)
                % not a stoich ('+'/'->'/'<=>'): new line if togther with the next stoich + met exceed the limit
                if isStoich(jPart + 1) && nChar + length(formulaePartsToPrint{jPart + 1}) + length(formulaePartsToPrint{jPart + 2}) + 1 + spaceAdded >= nCharBreak
                    lineBreak = true;
                elseif isMet(jPart + 1) && nChar + length(formulaePartsToPrint{jPart + 1}) + spaceAdded >= nCharBreak
                    lineBreak = true;
                end
            end
        end
        firstWordOnLine = false;
        if lineBreak
            p = [p '\n  '];
            nChar = length(formulaePartsToPrint{jPart}) + 3;  % 2 spaces before + 1 space after
            [lineBreak, firstWordOnLine] = deal(false, true);
            if isArrow(jPart)
                spaceAdded = 2;
            end
        end
        [spaceAddLeft, spaceAddRight] = deal('');
        if isArrow(jPart)
            if jPart < numel(formulaeParts)
                spaceAddRight = '  ';
            end
            if ~firstWordOnLine
                spaceAddLeft = '  ';
            end
            nChar = nChar + length(spaceAddLeft) + length(spaceAddRight);
        end
        p = [p, spaceAddLeft, formulaeParts{jPart}, spaceAddRight, ' '];
    else
        % print hyperlink if it is a metabolite name
        p = [p, printHyperlink(strrep(commandUsingMet, '%s', formulaeParts{jPart}), formulaePartsToPrint{jPart}, 0, false), ' '];
        firstWordOnLine = false;
    end
end
if printFlag
    fprintf(p);
end
p = {p};
end