classdef SnirfClass < matlab.mixin.Copyable

    properties
        formatVersion
        metaDataTags
        data
        stim
        probe
        aux
    end

    properties (Access = private)
        fid
        gid
        location
        nirsdatanum
        nirs_tb
        stim0
        filename
        fileformat
        dataStorageScheme
    end

    methods

        % -------------------------------------------------------
        function obj = SnirfClass(varargin)
            %
            % Syntax:
            %   obj = SnirfClass()
            %   obj = SnirfClass(filename);
            %   obj = SnirfClass(filename, nirsdatanum);
            %   obj = SnirfClass(filename, nirsdatanum, dataStorageScheme);
            %   obj = SnirfClass(filename, dataStorageScheme);
            %   obj = SnirfClass(dotnirs);
            %   obj = SnirfClass(dotnirs, numdatabllocks);
            %   obj = SnirfClass(data, stim);
            %   obj = SnirfClass(data, stim, probe);
            %   obj = SnirfClass(data, stim, probe, aux);
            %   obj = SnirfClass(d, t, SD, aux, s);
            %   obj = SnirfClass(d, t, SD, aux, s, CondNames);
            %
            %   Also for debugging/simulation of time bases
            %
            %   obj = SnirfClass(dotnirs, tfactors);
            %
            % Example 1:
            %
            %   % Save .nirs file in SNIRF format
            %   snirf1 = SnirfClass(load('neuro_run01.nirs','-mat'));
            %   snirf1.Save('neuro_run01.snirf');
            %   snirf1.Info()
            %
            %   % Check that the file was saved correctly
            %   snirf2 = SnirfClass();
            %   snirf2.Load('neuro_run01.snirf');
            %   snirf2.Info()
            %
            % Example 2:
            %
            %   Nirs2Snirf('Simple_Probe1.nirs');
            %   obj = SnirfClass('Simple_Probe1.snirf');
            %
            %   Here's some of the output:
            %
            %   obj(1).data ====>
            %
            %       DataClass with properties:
            %
            %           data: [1200x8 double]
            %           time: [1200x1 double]
            %           measurementList: [1x8 MeasListClass]
            %

            % Initialize properties from SNIRF spec
            obj.Initialize()

            % Set class properties NOT part of the SNIRF format
            obj.fileformat = 'hdf5';
            obj.location = '/nirs';
            obj.nirsdatanum = 1;

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Between 1 and 4 arguments covers the following syntax variants
            %
            % obj = SnirfClass(filename);
            % obj = SnirfClass(filename, nirsdatanum);
            % obj = SnirfClass(filename, nirsdatanum, dataStorageScheme);
            % obj = SnirfClass(filename, dataStorageScheme);
            % obj = SnirfClass(dotnirs);
            % obj = SnirfClass(dotnirs, numdatabllocks);
            % obj = SnirfClass(data, stim);
            % obj = SnirfClass(data, stim, probe);
            % obj = SnirfClass(data, stim, probe, aux);
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            if nargin>0 && nargin<5

                % obj = SnirfClass(filename);
                if isa(varargin{1}, 'SnirfClass')
                    obj.Copy(varargin{1});
                    return;
                end

                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                % obj = SnirfClass(filename, nirsdatanum);
                % obj = SnirfClass(filename, nirsdatanum, dataStorageScheme);
                % obj = SnirfClass(filename, dataStorageScheme);
                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                if ischar(varargin{1})
                    obj.filename = varargin{1};
                    if nargin>1
                        % obj = SnirfClass(filename, nirsdatanum);
                        if isnumeric(varargin{2})
                            obj.nirsdatanum = varargin{2};

                            % obj = SnirfClass(filename, nirsdatanum, dataStorageScheme);
                            if nargin>2
                                obj.dataStorageScheme = varargin{3};
                            end

                            % obj = SnirfClass(filename, dataStorageScheme);
                        elseif ischar(varargin{2})
                            obj.dataStorageScheme = varargin{2};

                        end
                    end

                    % Load Snirf file here ONLY if data storage scheme is 'memory'
                    if strcmpi(obj.dataStorageScheme, 'memory')
                        obj.Load(varargin{1});
                    end

                    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                    % obj = SnirfClass(dotnirs);
                    % obj = SnirfClass(dotnirs, numdatabllocks);
                    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                elseif isstruct(varargin{1}) || isa(varargin{1}, 'NirsClass')

                    % obj = SnirfClass(dotnirs);
                    tfactors = 1;    % Debug simulation parameter

                    % obj = SnirfClass(dotnirs, numdatabllocks);
                    if nargin==2
                        tfactors = varargin{2};
                    end
                    dotnirs = varargin{1};
                    obj.GenSimulatedTimeBases(dotnirs, tfactors);
                    for ii=1:length(tfactors)
                        obj.data(ii) = DataClass(obj.nirs_tb(ii).d, obj.nirs_tb(ii).t(:), obj.nirs_tb(ii).SD.MeasList);
                    end

                    for ii=1:size(dotnirs.s,2)
                        if isfield(dotnirs, 'CondNames')
                            obj.stim(ii) = StimClass(dotnirs.s(:,ii), dotnirs.t(:), dotnirs.CondNames{ii});
                        else
                            obj.stim(ii) = StimClass(dotnirs.s(:,ii), dotnirs.t(:), num2str(ii));
                        end
                    end
                    obj.probe      = ProbeClass(dotnirs.SD);
                    for ii=1:size(dotnirs.aux,2)
                        obj.aux(ii) = AuxClass(dotnirs.aux(:,ii), dotnirs.t(:), sprintf('aux%d',ii));
                    end

                    % Add metadatatags
                    obj.metaDataTags   = MetaDataTagsClass();

                    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                    % obj = SnirfClass(data, stim);
                    % obj = SnirfClass(data, stim, probe);
                    % obj = SnirfClass(data, stim, probe, aux);
                    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                elseif isa(varargin{1}, 'DataClass')

                    % obj = SnirfClass(data, stim);
                    data = varargin{1};
                    obj.SetData(data);
                    stim = varargin{2};
                    obj.SetStim(stim);

                    % obj = SnirfClass(data, stim, probe);
                    if nargin>2
                        probe = varargin{3};
                        obj.SetSd(probe);
                    end

                    % obj = SnirfClass(data, stim, probe, aux);
                    if nargin>3
                        aux = varargin{4};
                        obj.SetAux(aux);
                    end

                end

                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                % Between 5 and 6 arguments covers the following syntax variants
                %
                % obj = SnirfClass(d, t, SD, aux, s);
                % obj = SnirfClass(d, t, SD, aux, s, CondNames);
                %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            elseif nargin>4

                % obj = SnirfClass(d, t, SD, aux, s);
                d         = varargin{1};
                t         = varargin{2}(:);
                SD        = varargin{3};
                aux       = varargin{4};
                s         = varargin{5};
                CondNames = {};

                % obj = SnirfClass(d, t, SD, aux, s, CondNames);
                if nargin>5
                    CondNames = varargin{6};
                end

                obj.data(1) = DataClass(d, t(:), SD.MeasList);
                for ii=1:size(s,2)
                    if nargin==5
                        condition = num2str(ii);
                    else
                        condition = CondNames{ii};
                    end
                    obj.stim(ii) = StimClass(s(:,ii), t(:), condition);
                end
                obj.probe      = ProbeClass(SD);
                for ii=1:size(aux,2)
                    obj.aux(ii) = AuxClass(aux, t(:), sprintf('aux%d',ii));
                end

                % Add metadatatags
                obj.metaDataTags   = MetaDataTagsClass();

            end

        end

        % -------------------------------------------------------
        function Initialize(obj)
            obj.formatVersion      = '1.0';
            obj.metaDataTags      = MetaDataTagsClass().empty();
            obj.data              = DataClass().empty();
            obj.stim              = StimClass().empty();
            obj.probe             = ProbeClass().empty();
            obj.aux               = AuxClass().empty();

            obj.stim0             = StimClass().empty();
            obj.dataStorageScheme = 'memory';
        end

        % -------------------------------------------------------
        function err = Copy(obj, obj2)
            err=0;
            if ~isa(obj2, 'SnirfClass')
                err=1;
                return;
            end
            obj.formatVersion = obj2.formatVersion;
            obj.metaDataTags  = CopyHandles(obj2.metaDataTags);
            obj.data          = CopyHandles(obj2.data);
            obj.stim          = CopyHandles(obj2.stim);
            obj.probe         = CopyHandles(obj2.probe);
            obj.aux           = CopyHandles(obj2.aux);

            try
                obj.stim0     = CopyHandles(obj2.stim0);
            catch
            end

            if ~isempty(obj2.filename)
                obj.filename = obj2.filename;
            end
        end

        % -------------------------------------------------------
        function objnew = CopyMutable(obj, options)
            if nargin==1
                options = '';
            end

            % Load mutable data from Snirf file here ONLY if data storage scheme is 'files'
            if strcmpi(obj.dataStorageScheme, 'files')
                obj.LoadStim(obj.filename);
            end

            % Generate new instance of SnirfClass
            objnew = SnirfClass();

            objnew.filename = obj.filename;

            % Copy mutable properties to new object instance;
            objnew.stim = CopyHandles(obj.stim);

            if strcmp(options, 'extended')
                t = obj.GetTimeCombined();
                objnew.data = DataClass([],t,[]);
            end
        end

        % -------------------------------------------------------
        function SortStims(obj)
            if isempty(obj.stim)
                return;
            end
            temp = CopyHandles(obj.stim);
            delete(obj.stim);
            names = cell(length(temp),1);
            for ii=1:length(temp)
                names{ii} = temp(ii).name;
            end
            [~,idx] = sort(names);
            obj.stim = temp(idx).copy;
        end

        % -------------------------------------------------------
        function err = SetLocation(obj)
            err = 0;
            gid1 = HDF5_GroupOpen(obj.fid, sprintf('%s%d', obj.location, obj.nirsdatanum));
            gid2 = HDF5_GroupOpen(obj.fid, obj.location);

            if gid1.double > 0
                obj.location = sprintf('%s%d', obj.location, obj.nirsdatanum);
                return;
            elseif gid2.double > 0
                return;
            end
            err = -1;
        end

        % -------------------------------------------------------
        function err = LoadFormatVersion(obj)
            err = 0;
            formatVersionFile = HDF5_DatasetLoad(obj.gid, 'formatVersion'); %#ok<*PROPLC>
            formatVersionFile = str2double(formatVersionFile);
            formatVersionCurr = str2double(obj.formatVersion);
            if formatVersionFile < formatVersionCurr
                fprintf('Warning: Current SNIRF version is %0.1f. Cannot load older version (%0.1f) file. Backward compatibility not yet implemented ...\n', formatVersionCurr, formatVersionFile)
                err = -2;
                return
            end
        end

        % -------------------------------------------------------
        function err = LoadMetaDataTags(obj, fileobj)
            err = 0;
            obj.metaDataTags = MetaDataTagsClass();
            if obj.metaDataTags.LoadHdf5(fileobj, [obj.location, '/metaDataTags']) < 0
                err = -1;
            end
        end

        % -------------------------------------------------------
        function err = LoadData(obj, fileobj)
            err = 0;
            ii=1;
            while 1
                if ii > length(obj.data)
                    obj.data(ii) = DataClass;
                end
                if obj.data(ii).LoadHdf5(fileobj, [obj.location, '/data', num2str(ii)]) < 0
                    obj.data(ii).delete();
                    obj.data(ii) = [];
                    if ii==1
                        err = -1;
                    end
                    break;
                end
                ii=ii+1;
            end
        end

        % -------------------------------------------------------
        function err = LoadStim(obj, fileobj)
            err = 0;

            ii=1;
            while 1
                if ii > length(obj.stim)
                    obj.stim(ii) = StimClass;
                end
                if obj.stim(ii).LoadHdf5(fileobj, [obj.location, '/stim', num2str(ii)]) < 0
                    obj.stim(ii).delete();
                    obj.stim(ii) = [];
                    if ii==1
                        err = -1;
                    end
                    break;
                end
                ii=ii+1;
            end
            obj.SortStims();

            % Load original, unedited stims, if they exist
            ii=1;
            while 1
                if ii > length(obj.stim0)
                    obj.stim0(ii) = StimClass;
                end
                if obj.stim0(ii).LoadHdf5(fileobj, [obj.location, '/stim0', num2str(ii)]) < 0
                    obj.stim0(ii).delete();
                    obj.stim0(ii) = [];
                    break;
                end
                ii=ii+1;
            end

        end

        % -------------------------------------------------------
        function err = LoadProbe(obj, fileobj, ~)
            obj.probe = ProbeClass();
            err = obj.probe.LoadHdf5(fileobj, [obj.location, '/probe']);
        end

        % -------------------------------------------------------
        function err = LoadAux(obj, fileobj)
            err = 0;
            ii=1;
            while 1
                if ii > length(obj.aux)
                    obj.aux(ii) = AuxClass;
                end
                if obj.aux(ii).LoadHdf5(fileobj, [obj.location, '/aux', num2str(ii)]) < 0
                    obj.aux(ii).delete();
                    obj.aux(ii) = [];
                    if ii==1
                        err = -1;
                    end
                    break;
                end
                ii=ii+1;
            end
        end

        % -------------------------------------------------------
        function err = LoadHdf5(obj, fileobj, ~)
            err = 0;

            % Arg 1
            if ~exist('fileobj','var') || ~exist(fileobj,'file')
                fileobj = '';
            end

            % Error checking
            if ~isempty(fileobj) && ischar(fileobj)
                obj.filename = fileobj;
            elseif isempty(fileobj)
                fileobj = obj.filename;
            end
            if isempty(fileobj)
                err = -1;
                return;
            end

            % Don't reload if not empty
            if ~obj.IsEmpty()
                return;
            end

            %%%%%%%%%%%% Ready to load from file

            try

                % Open group
                [obj.gid, obj.fid] = HDF5_GroupOpen(fileobj, '/');

                if obj.SetLocation() < 0
                    err = -1;
                    return
                end

                %%%% Load formatVersion
                if obj.LoadFormatVersion() < 0
                    err = -2;
                end

                %%%% Load metaDataTags
                if obj.LoadMetaDataTags(obj.fid) < 0
                    err = -3;
                end

                %%%% Load data
                if obj.LoadData(obj.fid) < 0
                    err = -4;
                end

                %%%% Load stim
                if obj.LoadStim(obj.fid)
                    err = -5;
                end

                %%%% Load probe
                if obj.LoadProbe(obj.fid)
                    err = -6;
                end

                %%%% Load aux. This is an optional field, therefore error must
                %%%% be less then -1 (-1 means aux is not in SNIRF file) to be
                %%%% error for whole SNIRF file
                if obj.LoadAux(obj.fid)<-1
                    err = -7;
                end

                % Close group
                HDF5_GroupClose(fileobj, obj.gid, obj.fid);

            catch

                err = -1;

            end

            if obj.fid>0
                H5F.close(obj.fid);
            end

        end

        % -------------------------------------------------------
        function err = Load(obj, fileobj)
            err = LoadHdf5(obj, fileobj);
        end

        % -------------------------------------------------------
        function SaveMetaDataTags(obj, fileobj)
            obj.metaDataTags.SaveHdf5(fileobj, [obj.location, '/metaDataTags']);
        end

        % -------------------------------------------------------
        function SaveData(obj, fileobj)
            for ii=1:length(obj.data)
                obj.data(ii).SaveHdf5(fileobj, [obj.location, '/data', num2str(ii)]);
            end
        end

        % -------------------------------------------------------
        function SaveStim(obj, fileobj)
            for ii=1:length(obj.stim)
                obj.stim(ii).SaveHdf5(fileobj, [obj.location, '/stim', num2str(ii)]);
            end
            if isempty(obj.stim0)
                obj.stim0 = obj.stim.copy();
                for ii=1:length(obj.stim0)
                    obj.stim0(ii).SaveHdf5(fileobj, [obj.location, '/stim0', num2str(ii)]);
                end
            end
        end

        % -------------------------------------------------------
        function SaveProbe(obj, fileobj)
            obj.probe.SaveHdf5(fileobj, [obj.location, '/probe']);
        end

        % -------------------------------------------------------
        function SaveAux(obj, fileobj)
            for ii=1:length(obj.aux)
                obj.aux(ii).SaveHdf5(fileobj, [obj.location, '/aux', num2str(ii)]);
            end
        end

        % -------------------------------------------------------
        function SaveHdf5(obj, fileobj, ~)
            % Arg 1
            if ~exist('fileobj','var') || isempty(fileobj)
                error('Unable to save file. No file name given.')
            end

            % Args
            if exist(fileobj, 'file')
                delete(fileobj);
            end
            obj.fid = H5F.create(fileobj, 'H5F_ACC_TRUNC', 'H5P_DEFAULT', 'H5P_DEFAULT');
            H5F.close(obj.fid);

            %%%%% Save this object's properties

            % Save formatVersion
            if isempty(obj.formatVersion)
                obj.formatVersion = '1.1';
            end
            hdf5write_safe(fileobj, '/formatVersion', obj.formatVersion);

            % Save metaDataTags
            obj.SaveMetaDataTags(fileobj);

            % Save data
            obj.SaveData(fileobj);

            % Save stim
            obj.SaveStim(fileobj);

            % Save sd
            obj.SaveProbe(fileobj);

            % Save aux
            obj.SaveAux(fileobj);
        end

        % -------------------------------------------------------
        function Save(obj, fileobj)
            SaveHdf5(obj, fileobj);
        end

        % -------------------------------------------------------
        function [stimFromFile, changes] = UpdateStim(obj, fileobj)
            flags = zeros(length(obj.stim), 1);

            % Load stim from file and update it
            snirfFile = SnirfClass();
            snirfFile.LoadStim(fileobj);

            % Update stims from file with edited stims
            for ii = 1:length(obj.stim)
                for jj = 1:length(snirfFile.stim)
                    if strcmp(obj.stim(ii).GetName(), snirfFile.stim(jj).GetName())
                        if obj.stim(ii) ~= snirfFile.stim(jj)
                            snirfFile.stim(jj).Copy(obj.stim(ii));
                        end
                        flags(ii) = 1;
                        break;
                    end
                end
                if ~flags(ii)
                    % We have new stimulus condition added
                    if ~obj.stim(ii).IsEmpty()
                        snirfFile.stim(jj+1) = StimClass(obj.stim(ii));
                    end
                end
            end

            % If stims were edited then update snirf file with new stims
            changes = sum(flags);
            if changes
                snirfFile.SaveStim(fileobj);
            end
            stimFromFile = snirfFile.stim;
        end

        % -------------------------------------------------------
        function CopyStim(obj, obj2)
            for ii = 1:length(obj2.stim)
                if ii > length(obj.stim)
                    obj.stim(ii) = StimClass(obj2.stim(ii));
                else
                    obj.stim(ii).Copy(obj2.stim(ii));
                end
            end
        end

        % -------------------------------------------------------
        function changes = StimChangesMade(obj)

            flags = zeros(length(obj.stim), 1);

            % Load stims from file
            snirf = SnirfClass();
            snirf.LoadStim(obj.filename);
            stimFromFile = snirf.stim;

            % Update stims from file with edited stims
            for ii = 1:length(obj.stim)
                for jj = 1:length(stimFromFile)
                    if strcmp(obj.stim(ii).GetName(), stimFromFile(jj).GetName())
                        if obj.stim(ii) ~= stimFromFile(jj)
                            flags(ii) = 1;
                        else
                            flags(ii) = -1;
                        end
                        break;
                    end
                end
                if flags(ii)==0
                    % We have new stimulus condition added
                    if ~obj.stim(ii).IsEmpty()
                        flags(ii) = 1;
                    end
                end
            end
            flags(flags ~= 1) = 0;
            changes = sum(flags)>0;
        end

        % -------------------------------------------------------
        function b = DataModified(obj)
            b = obj.StimChangesMade();
        end

        % -------------------------------------------------------
        function err = SaveMutable(obj, fileobj)
            if isempty(obj)
                return
            end

            % Arg 1
            if ~exist('fileobj','var') || ~exist(fileobj,'file')
                fileobj = '';
            end

            % Error checking
            if ~isempty(fileobj) && ischar(fileobj)
                obj.filename = fileobj;
            elseif isempty(fileobj)
                fileobj = obj.filename;
            end
            if isempty(fileobj)
                err = -1;
                return;
            end

            % Update original stims and save back to file
            obj.UpdateStim(fileobj);
        end

        % -------------------------------------------------------
        function B = eq(obj, obj2)
            B = false;
            if ~strcmp(obj.formatVersion, obj2.formatVersion)
                return;
            end
            if length(obj.data) ~= length(obj2.data)
                return;
            end
            for ii = 1:length(obj.data)
                if ~(obj.data(ii) == obj2.data(ii))
                    return;
                end
            end
            if length(obj.stim)~=length(obj2.stim)
                return;
            end
            for ii = 1:length(obj.stim)
                flag = false;
                for jj = 1:length(obj2.stim)
                    if obj.stim(ii) == obj2.stim(jj)
                        flag = true;
                        break;
                    end
                end
                if flag==false
                    return;
                end
            end
            if ~(obj.probe == obj2.probe)
                return;
            end
            if length(obj.aux) ~= length(obj2.aux)
                return;
            end
            for ii=1:length(obj.aux)
                if ~(obj.aux(ii) == obj2.aux(ii))
                    return;
                end
            end
            if length(obj.metaDataTags) ~= length(obj2.metaDataTags)
                return;
            end
            for ii = 1:length(obj.metaDataTags)
                if ~(obj.metaDataTags(ii) == obj2.metaDataTags(ii))
                    return;
                end
            end
            B = true;
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Basic methods to Set/Get native variable
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods

        % ---------------------------------------------------------
        function val = GetFormatVersion(obj)
            val = obj.formatVersion;
        end

        % ---------------------------------------------------------
        function val = GetFormatVersionString(obj)
            val = sprintf('SNIRF v%s', obj.formatVersion);
        end

        % ---------------------------------------------------------
        function SetData(obj, val)
            obj.data = CopyHandles(val);
        end

        % ---------------------------------------------------------
        function val = GetData(obj)
            val = obj.data;
        end

        % ---------------------------------------------------------
        function SetStim(obj, val)
            obj.stim = CopyHandles(val);
        end

        % ---------------------------------------------------------
        function val = GetStim(obj)
            val = obj.stim;
        end

        % ---------------------------------------------------------
        function SetSd(obj, val)
            obj.probe = CopyHandles(val);
        end

        % ---------------------------------------------------------
        function val = GetSd(obj)
            val = obj.probe;
        end

        % ---------------------------------------------------------
        function SetAux(obj, val)
            obj.aux = CopyHandles(val);
        end

        % ---------------------------------------------------------
        function val = GetAux(obj)
            val = obj.aux;
        end

        % ---------------------------------------------------------
        function SetMetaDataTags(obj, val)
            obj.metaDataTags = val;
        end

        % ---------------------------------------------------------
        function val = GetMetaDataTags(obj)
            val = obj.metaDataTags;
            if isempty(obj)
                return;
            end
            if isempty(obj.metaDataTags)
                return;
            end
            val = obj.metaDataTags.Get();
        end

    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Methods that must be implemented as a child class of AcqDataClass
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods

        % ---------------------------------------------------------
        function t = GetTime(obj, iBlk)
            t = [];
            if ~exist('iBlk','var') || isempty(iBlk)
                iBlk=1;
            end
            if iBlk>length(obj.data)
                return;
            end
            t = obj.data(iBlk).GetTime();
        end

        % ---------------------------------------------------------
        function datamat = GetDataTimeSeries(obj, options, iBlk)
            datamat = [];
            if ~exist('options','var')
                options = '';
            end
            if ~exist('iBlk','var') || isempty(iBlk)
                iBlk=1;
            end
            if iBlk>length(obj.data)
                return;
            end
            datamat = obj.data(iBlk).GetDataTimeSeries(options);
        end

        % ---------------------------------------------------------
        function datamat = GetAuxDataMatrix(obj)
            datamat = [];
            if isempty(obj.aux)
                return;
            end
            for ii=1:length(obj.aux)
                datamat(:,ii) = obj.aux(ii).GetDataTimeSeries();
            end
        end

        % ---------------------------------------------------------
        function names = GetAuxNames(obj)
            names = {};
            for ii=1:length(obj.aux)
                names{ii} = obj.aux(ii).GetName();
            end
        end

        % ---------------------------------------------------------
        function aux = GetAuxiliary(obj)
            aux = struct('names',{{}}, 'data', obj.aux);
            aux.names = obj.GetAuxNames();
            aux.data = obj.GetAuxDataMatrix();
        end

        % ---------------------------------------------------------
        function ml = GetMeasList(obj, iBlk)
            ml = [];
            if ~exist('iBlk','var') || isempty(iBlk)
                iBlk=1;
            end
            if iBlk>length(obj.data)
                return;
            end
            ml = obj.data(iBlk).GetMeasList();
        end

        % ---------------------------------------------------------
        function wls = GetWls(obj)
            wls = obj.probe.GetWls();
        end

        % ---------------------------------------------------------
        function SetStims_MatInput(obj, s, t)
            if nargin<2
                return
            end
            if isempty(t)
                return;
            end
            for ii=1:size(s,2)
                tidxs = find(s(:,ii)~=0);
                for jj=1:length(tidxs)
                    if ~obj.stim(ii).Exists(t(tidxs(jj)))
                        obj.stim(ii).AddStims(t(tidxs(jj)));
                    else
                        obj.stim(ii).EditValue(t(tidxs(jj)), s(tidxs(jj),ii));
                    end
                end
            end
        end

        % ---------------------------------------------------------
        function s = GetStims(obj, t)
            s = zeros(length(t), length(obj.stim));
            for ii=1:length(obj.stim)
                [ts, v] = obj.stim(ii).GetStim();
                [~, k] = nearest_point(t, ts);
                if isempty(k)
                    continue;
                end
                s(k,ii) = v;
            end
        end

        % ----------------------------------------------------------------------------------
        function SetConditions(obj, CondNames)
            if nargin==1
                return;
            end
            CondNamesLocal = unique({obj.stim.name});
            stimnew = StimClass().empty;
            for ii=1:length(CondNames)
                k = find(strcmp(CondNamesLocal, CondNames{ii}));
                if ~isempty(k)
                    stimnew(ii) = StimClass(obj.stim(k));
                else
                    stimnew(ii) = StimClass(CondNames{ii});
                end
            end
            obj.stim = stimnew.copy;
        end

        % ---------------------------------------------------------
        function CondNames = GetConditions(obj)
            CondNames = cell(1,length(obj.stim));
            for ii=1:length(obj.stim)
                CondNames{ii} = obj.stim(ii).GetName();
            end
        end

        % ---------------------------------------------------------
        function SD = GetSDG(obj)
            SD = [];
            if isempty(obj)
                return;
            end
            if isempty(obj.probe)
                return;
            end
            SD.Lambda = obj.probe.GetWls();
            SD.SrcPos = obj.probe.GetSrcPos();
            SD.DetPos = obj.probe.GetDetPos();
        end

        % ---------------------------------------------------------
        function srcpos = GetSrcPos(obj)
            srcpos = obj.probe.GetSrcPos();
        end

        % ---------------------------------------------------------
        function detpos = GetDetPos(obj)
            detpos = obj.probe.GetDetPos();
        end

        % ----------------------------------------------------------------------------------
        function n = GetDataBlocksNum(obj)
            n = length(obj.data);
        end

        % ----------------------------------------------------------------------------------
        function [iDataBlks, ich] = GetDataBlocksIdxs(obj, ich0)
            iDataBlks=[];
            ich={};
            if nargin==1
                ich0=[];
            end
            if isempty(ich0)
                iDataBlks=1:length(obj.data);
                return;
            end

            % Get channel matrix for whole probe
            mlAll = [];
            nDataBlks = length(obj.data);
            for iBlk = 1:nDataBlks
                mlAll = [mlAll; obj.GetMeasList(iBlk)];
            end

            iSrc = mlAll(ich0,1);
            iDet = mlAll(ich0,2);

            % Now search block by block for the selecdted channels
            ich = cell(nDataBlks,1);
            for iBlk=1:nDataBlks
                ml = obj.GetMeasList(iBlk);
                for ii=1:length(ich0)
                    k = find(ml(:,1)==iSrc(ii) & ml(:,2)==iDet(ii));
                    if ~isempty(k)
                        iDataBlks = [iDataBlks; iBlk];
                        ich{iBlk} = [ich{iBlk}, k(1)];
                    end
                end
            end

            % Important: make sure iDataBlks is row vector (: + transpose does that) .
            % For some reason a for-loop traversing through empty column vector doesn't work properly
            iDataBlks = sort(unique(iDataBlks(:)'));

        end

    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Pubic interface for .nirs processing stream
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods

        % ----------------------------------------------------------------------------------
        function d = Get_d(obj, iBlk)
            d = [];
            if ~exist('iBlk','var') || isempty(iBlk)
                iBlk = 1;
            end
            if iBlk>length(obj.data)
                return;
            end
            d = obj.data(iBlk).GetDataTimeSeries();
        end

        % ----------------------------------------------------------------------------------
        function t = Get_t(obj, iBlk)
            t = [];
            if ~exist('iBlk','var') || isempty(iBlk)
                iBlk = 1;
            end
            if iBlk>length(obj.data)
                return;
            end
            t = obj.data(iBlk).GetTime();
        end

        % ----------------------------------------------------------------------------------
        function SD = Get_SD(obj, iBlk)
            SD = [];
            if isempty(obj.probe)
                return;
            end
            if ~exist('iBlk','var') || isempty(iBlk)
                iBlk = 1;
            end
            if iBlk>length(obj.data)
                return;
            end
            SD.Lambda   = obj.probe.GetWls();
            SD.SrcPos   = obj.probe.GetSrcPos();
            SD.DetPos   = obj.probe.GetDetPos();
            SD.MeasList = obj.data(iBlk).GetMeasList();
            SD.MeasListAct = ones(size(SD.MeasList,1),1);
        end

        % ----------------------------------------------------------------------------------
        function s = Get_s(obj, iBlk)
            s = [];
            if ~exist('iBlk','var') || isempty(iBlk)
                iBlk = 1;
            end
            if iBlk>length(obj.data)
                return;
            end
            t = obj.data(iBlk).GetTime();
            s = zeros(length(t), length(obj.stim));
            for ii=1:length(obj.stim)
                [ts, v] = obj.stim(ii).GetStim();
                [~, k] = nearest_point(t, ts);
                if isempty(k)
                    continue;
                end
                s(k,ii) = v;
            end
        end

    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % All other public methods
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods

        % ----------------------------------------------------------------------------------
        function AddStims(obj, tPts, condition)
            % Try to find existing condition to which to add stims.
            for ii=1:length(obj.stim)
                if strcmp(condition, obj.stim(ii).GetName())
                    obj.stim(ii).AddStims(tPts);
                    return;
                end
            end

            % Otherwise we have a new condition to which to add the stims.
            obj.stim(end+1) = StimClass(tPts, condition);
            obj.SortStims();
        end

        % ----------------------------------------------------------------------------------
        function DeleteStims(obj, tPts, condition)
            % Find all stims for any conditions which match the time points.
            for ii=1:length(obj.stim)
                obj.stim(ii).DeleteStims(tPts);
            end
        end

        % ----------------------------------------------------------------------------------
        function ToggleStims(obj, tPts, condition)
            % Find all stims for any conditions which match the time points.
            for ii=1:length(obj.stim)
                obj.stim(ii).ToggleStims(tPts);
            end
        end

        % ----------------------------------------------------------------------------------
        function MoveStims(obj, tPts, condition)
            if ~exist('tPts','var') || isempty(tPts)
                return;
            end
            if ~exist('condition','var') || isempty(condition)
                return;
            end

            % Find the destination condition to move stims (among the time pts in tPts)
            % to
            j = [];
            for ii=1:length(obj.stim)
                if strcmp(condition, obj.stim(ii).GetName())
                    j=ii;
                    break;
                end
            end

            % If no destination condition found among existing conditions,
            % then create a new condition to move stims to
            if isempty(j)
                j = length(obj.stim)+1;

                % Otherwise we have a new condition to which to add the stims.
                obj.stim(j) = StimClass([], condition);
                obj.SortStims();

                % Recalculate j after sort
                for ii=1:length(obj.stim)
                    if strcmp(condition, obj.stim(ii).GetName())
                        j=ii;
                        break;
                    end
                end
            end

            % Find all stims for any conditions which match the time points.
            for ii=1:length(tPts)
                for kk=1:length(obj.stim)
                    d = obj.stim(kk).GetData();
                    if isempty(d)
                        continue;
                    end
                    k = find(d(:,1)==tPts(ii));
                    if ~isempty(k)
                        if kk==j
                            continue;
                        end

                        % If stim at time point tPts(ii) exists in stim
                        % condition kk, then move stim from obj.stim(kk) to
                        % obj.stim(j)
                        obj.stim(j).AddStims(tPts(ii), d(k(1),2), d(k(1),3));

                        % After moving stim from obj.stim(kk) to
                        % obj.stim(j), delete it from obj.stim(kk)
                        d(k(1),:)=[];
                        obj.stim(kk).SetData(d);

                        % Move on to next time point
                        break;
                    end
                end
            end
        end

        % ----------------------------------------------------------------------------------
        function SetStimTpts(obj, icond, tpts)
            obj.stim(icond).SetTpts(tpts);
        end

        % ----------------------------------------------------------------------------------
        function tpts = GetStimTpts(obj, icond)
            if icond>length(obj.stim)
                tpts = [];
                return;
            end
            tpts = obj.stim(icond).GetTpts();
        end

        % ----------------------------------------------------------------------------------
        function SetStimDuration(obj, icond, duration)
            obj.stim(icond).SetDuration(duration);
        end

        % ----------------------------------------------------------------------------------
        function duration = GetStimDuration(obj, icond)
            if icond>length(obj.stim)
                duration = [];
                return;
            end
            duration = obj.stim(icond).GetDuration();
        end

        % ----------------------------------------------------------------------------------
        function SetStimValues(obj, icond, vals)
            obj.stim(icond).SetValues(vals);
        end

        % ----------------------------------------------------------------------------------
        function vals = GetStimValues(obj, icond)
            if icond>length(obj.stim)
                vals = [];
                return;
            end
            vals = obj.stim(icond).GetValues();
        end

        % ----------------------------------------------------------------------------------
        function RenameCondition(obj, oldname, newname)
            if ~exist('oldname','var') || ~ischar(oldname)
                return;
            end
            if ~exist('newname','var')  || ~ischar(newname)
                return;
            end
            k=[];
            for ii=1:length(obj.stim)
                if strcmp(obj.stim(ii).GetName(), oldname)
                    k = ii;
                    break;
                end
            end
            if isempty(k)
                return;
            end
            obj.stim(k).SetName(newname);
            obj.SortStims();
        end

        % ----------------------------------------------------------------------------------
        function b = IsEmpty(obj)
            b = true;
            if isempty(obj)
                return;
            end
            if isempty(obj.data) || (isa(obj.data, 'DataClass') && obj.data(1).IsEmpty())
                return;
            end
            if isempty(obj.data) || (isa(obj.probe, 'ProbeClass') && obj.probe.IsEmpty())
                return;
            end
            b = false;
        end

        % ----------------------------------------------------------------------------------
        function nbytes = MemoryRequired(obj)
            nbytes = 0;
            nbytes = nbytes + sizeof(obj.formatVersion);
            for ii=1:length(obj.metaDataTags)
                nbytes = nbytes + obj.metaDataTags(ii).MemoryRequired();
            end
            for ii=1:length(obj.data)
                nbytes = nbytes + obj.data(ii).MemoryRequired();
            end
            for ii=1:length(obj.stim)
                nbytes = nbytes + obj.stim(ii).MemoryRequired();
            end
            if ~isempty(obj.probe)
                nbytes = nbytes + obj.probe.MemoryRequired();
            end
            for ii=1:length(obj.aux)
                nbytes = nbytes + obj.aux(ii).MemoryRequired();
            end
        end

        % ----------------------------------------------------------------------------------
        function Info(obj)

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Read formatVersion
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            fprintf('\n');
            fprintf('    FormatVersion:\n');
            fv = obj.GetFormatVersion();
            fprintf('        Format version: %s\n', fv);
            fprintf('\n');

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Load meta data tags from file and extract the tag names and values for display
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            fprintf('    MetaDataTags:\n');
            tags = obj.GetMetaDataTags();
            for ii=1:length(tags)
                fprintf('        Tag #%d: {''%s'', ''%s''}\n', ii, tags(ii).key, tags(ii).value);
            end
            fprintf('\n');

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Load data from file and extract .nirs-style d and ml matrices for display
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            fprintf('    Data (.nirs-style display):\n');
            for ii=1:length(obj.data)

                % Display data matrix dimensions and data type
                d = obj.data(ii).GetDataTimeSeries();
                pretty_print_struct(d, 8, 1);

                % Display meas list dimensions and data type
                ml = obj.data(ii).GetMeasList();
                pretty_print_struct(ml, 8, 1);

            end
            fprintf('\n');

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Load probe and extract .nirs-style SD structure
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            fprintf('    Probe (.nirs-style display):\n');
            SD = obj.GetSDG();
            pretty_print_struct(SD, 8, 1);
            fprintf('\n');

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Load stim from file and extract it for display
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            fprintf('    Stim (.snirf-style display):\n');
            for ii=1:length(obj.stim)
                fprintf('        stim(%d): {name = ''%s'', data = [', ii, obj.stim(ii).name);
                for jj=1:size(obj.stim(ii).data,1)
                    if jj==size(obj.stim(ii).data,1)
                        fprintf('%0.1f', obj.stim(ii).data(jj,1));
                    else
                        fprintf('%0.1f, ', obj.stim(ii).data(jj,1));
                    end
                end
                fprintf(']}\n');
            end
            fprintf('\n');

            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            % Load aux from file and extract nirs-style data for display
            %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
            fprintf('    Aux (.nirs-style display):\n');
            auxl = obj.GetAuxiliary();
            pretty_print_struct(auxl, 8, 1);
            fprintf('\n');

        end

    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Private methods
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods (Access = private)

        function GenSimulatedTimeBases(obj, nirs, tfactors)
            obj.nirs_tb = struct('SD', struct('Lambda',nirs.SD.Lambda, 'MeasList',[], 'SrcPOos',[], 'DetPOos',[]), ...
                't',[], ...
                'd',[] ...
                );

            if length(tfactors)==1 && tfactors==1
                obj.nirs_tb = nirs;
                return;
            end

            % a) Subdivide data and measurement list among time bases
            % b) Resample time and data
            % c) Throw out source and detectors that don't belong in each time base
            nCh = size(nirs.SD.MeasList,1)/length(nirs.SD.Lambda);
            nTimeBases = length(tfactors);
            baseSize = round(nCh/nTimeBases);
            irows = 1:baseSize:nCh;

            % Assign channels for time bases
            obj.nirs_tb = repmat(obj.nirs_tb, nTimeBases,1);
            for iWl=1:length(nirs.SD.Lambda)
                iBase = 1;
                for ii=irows+(iWl-1)*nCh
                    istart = ii;
                    if ii+baseSize-1 <= iWl*nCh
                        iend = ii+baseSize-1;
                    else
                        iend = iWl*nCh;
                    end
                    nChAdd = iend-istart+1;
                    obj.nirs_tb(iBase).d(:,end+1:end+nChAdd) = nirs.d(:,istart:iend);
                    obj.nirs_tb(iBase).SD.MeasList(end+1:end+nChAdd,:) = nirs.SD.MeasList(istart:iend,:);
                    if iBase<nTimeBases
                        iBase = iBase+1;
                    end
                end
            end

            % Resample data time and throw out optodes that don't belong in each time base
            for iBase=1:length(obj.nirs_tb)
                % Resample time
                [n,d] = rat(tfactors(iBase));
                obj.nirs_tb(iBase).t = resample(nirs.t, n, d);

                % Resample data
                obj.nirs_tb(iBase).d = resample(obj.nirs_tb(iBase).d, n, d);

                % Throw out source and detectors that don't belong in each time base
                iSrc = unique(obj.nirs_tb(iBase).SD.MeasList(:,1));
                obj.nirs_tb(iBase).SD.SrcPos = nirs.SD.SrcPos(iSrc);
                iDet = unique(obj.nirs_tb(iBase).SD.MeasList(:,2));
                obj.nirs_tb(iBase).SD.DetPos = nirs.SD.DetPos(iDet);
            end

        end

    end

end


function ft_realtime_signalrecorder(cfg)

% FT_REALTIME_SIGNALRECORDER is an example realtime application for recording of data
% that is streaming to the buffer in real-time. It should work both for EEG and MEG.
%
% Use as
%   ft_realtime_signalrecorder(cfg)
% with the following configuration options
%   cfg.blocksize  = number, size of the blocks/chuncks that are processed (default = 1 second)
%   cfg.channel    = cell-array, see FT_CHANNELSELECTION (default = 'all')
%   cfg.bufferdata = whether to start on the 'first or 'last' data that is available (default = 'last')
%   cfg.jumptoeof  = whether to skip to the end of the stream/file at startup (default = 'yes')
%
% The source of the data, i.e. where it comes from, is configured as
%   cfg.dataset       = string
% or alternatively to obtain more low-level control as
%   cfg.datafile      = string
%   cfg.headerfile    = string
%   cfg.eventfile     = string
%   cfg.dataformat    = string, default is determined automatic
%   cfg.headerformat  = string, default is determined automatic
%   cfg.eventformat   = string, default is determined automatic
%
% The target for the data, i.e. where it goes to, is configured as
%   cfg.export.dataset    = string with the output file name
%   cfg.export.dataformat = string describing the output file format, see FT_WRITE_DATA
%
% Some notes about skipping data and catching up with the data stream:
%
% cfg.jumptoeof='yes' causes the realtime function to jump to the end
% when the function _starts_. It causes all data acquired prior to
% starting the realtime function to be skipped.
%
% cfg.bufferdata='last' causes the realtime function to jump to the last
% available data while _running_. If the realtime loop is not fast enough,
% it causes some data to be dropped.
%
% If you want to skip all data that was acquired before you start the
% RT function, but don't want to miss any data that was acquired while
% the realtime function is started, then you should use jumptoeof=yes and
% bufferdata='first'. If you want to analyze data from a file, then you
% should use cfg.jumptoeof='no' and cfg.bufferdata='first'.
%
% To stop this realtime function, you will have have to press Ctrl-C.

% Copyright (C) 2012, Robert Oostenveld
%
% $Id$

% set the default configuration options
if ~isfield(cfg, 'dataformat'),     cfg.dataformat = [];      end % default is detected automatically
if ~isfield(cfg, 'headerformat'),   cfg.headerformat = [];    end % default is detected automatically
if ~isfield(cfg, 'eventformat'),    cfg.eventformat = [];     end % default is detected automatically
if ~isfield(cfg, 'blocksize'),      cfg.blocksize = 1;        end % in seconds
if ~isfield(cfg, 'overlap'),        cfg.overlap = 0;          end % in seconds
if ~isfield(cfg, 'channel'),        cfg.channel = 'all';      end
if ~isfield(cfg, 'bufferdata'),     cfg.bufferdata = 'last';  end % first or last
if ~isfield(cfg, 'jumptoeof'),      cfg.jumptoeof = 'no';     end % jump to end of file at initialization

if ~isfield(cfg, 'dataset') && ~isfield(cfg, 'header') && ~isfield(cfg, 'datafile')
  cfg.dataset = 'buffer://localhost:1972';
end

% translate dataset into datafile+headerfile
cfg = ft_checkconfig(cfg, 'dataset2files', 'yes');
cfg = ft_checkconfig(cfg, 'required', {'datafile' 'headerfile'});

% ensure that the persistent variables related to caching are cleared
clear ft_read_header

% start by reading the header from the realtime buffer
hdr = ft_read_header(cfg.headerfile, 'headerformat', cfg.headerformat, 'cache', true, 'retry', true);

% define a subset of channels for reading
cfg.channel = ft_channelselection(cfg.channel, hdr.label);
chanindx    = match_str(hdr.label, cfg.channel);
nchan       = length(chanindx);
if nchan==0
  ft_error('no channels were selected');
end

% make a copy of the header that will be passed to the writing function, update with the channel selection
writehdr          = hdr;
writehdr.nChans   = length(chanindx);
writehdr.label    = writehdr.label(chanindx);
writehdr.chantype = writehdr.chantype(chanindx);
writehdr.chanunit = writehdr.chanunit(chanindx);

% determine the size of blocks to process
blocksize = round(cfg.blocksize * hdr.Fs);
overlap   = round(cfg.overlap*hdr.Fs);

if strcmp(cfg.jumptoeof, 'yes')
  prevSample = hdr.nSamples * hdr.nTrials;
else
  prevSample = 0;
end
count = 0;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% this is the general BCI loop where realtime incoming data is handled
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
while true

  % determine number of samples available in buffer
  hdr = ft_read_header(cfg.headerfile, 'headerformat', cfg.headerformat, 'cache', true);

  % see whether new samples are available
  newsamples = (hdr.nSamples*hdr.nTrials-prevSample);

  if newsamples>=blocksize

    % determine the samples to process
    if strcmp(cfg.bufferdata, 'last')
      begsample  = hdr.nSamples*hdr.nTrials - blocksize + 1;
      endsample  = hdr.nSamples*hdr.nTrials;
    elseif strcmp(cfg.bufferdata, 'first')
      begsample  = prevSample+1;
      endsample  = prevSample+blocksize;
    else
      ft_error('unsupported value for cfg.bufferdata');
    end

    % this allows overlapping data segments
    if overlap && (begsample>overlap)
      begsample = begsample - overlap;
      endsample = endsample - overlap;
    end

    % remember up to where the data was read
    prevSample  = endsample;
    count       = count + 1;
    fprintf('processing segment %d from sample %d to %d\n', count, begsample, endsample);

    % read data segment from buffer
    dat = ft_read_data(cfg.datafile, 'header', hdr, 'dataformat', cfg.dataformat, 'begsample', begsample, 'endsample', endsample, 'chanindx', chanindx, 'checkboundary', false);

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % from here onward it is specific to saving of the data
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    if count==1
      ft_write_data(cfg.export.dataset, dat, 'header', writehdr, 'dataformat', cfg.export.dataformat, 'append', false);
    else
      ft_write_data(cfg.export.dataset, dat, 'header', writehdr, 'dataformat', cfg.export.dataformat, 'append', true);
    end

  end % if enough new samples
end % while true


% std_selectdesign() - select an existing STUDY design.
%                      Use std_makedesign() to add a new STUDY.design.
%
% Usage:
%   >> [STUDY] = std_selectdesign(STUDY, ALLEEG, designind);
%
% Inputs:
%   STUDY     - EEGLAB STUDY structure
%   STUDY     - EEGLAB ALLEEG structure
%   designind - desired (existing) design index
%
% Outputs:
%   STUDY     - EEGLAB STUDY structure with currentdesign set to the input design index.
%
% Author: Arnaud Delorme, Institute for Neural Computation, UCSD, 2010-

% Copyright (C) Arnaud Delorme, arno@salk.edu
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

function STUDY = std_selectdesign(STUDY, ALLEEG, designind);

if nargin < 3
    help std_selectdesign;
    return;
end;

if designind < 1 || designind > length(STUDY.design) || isempty(STUDY.design(designind).name)
    disp('Cannot select an empty STUDY.design');
    return;
end;
STUDY.currentdesign = designind;
STUDY = std_rmalldatafields( STUDY );

% remake setinds and allinds
% --------------------------
STUDY = std_changroup(STUDY, ALLEEG, [], 'on'); % with interpolation HAVE TO FIX THAT

% update the component indices
% ----------------------------
STUDY.cluster(1).setinds = {};
STUDY.cluster(1).allinds = {};
for index = 1:length(STUDY.cluster)
    STUDY.cluster(index) = std_setcomps2cell(STUDY, index);
end;


function colorbarDataIndex = findColorbarData(obj,colorbarIndex)
%locate index of data associated with colorbar
colorbarDataIndex = find(arrayfun(@(x)eq(x.AssociatedAxis,obj.State.Colorbar(colorbarIndex).AssociatedAxis),obj.State.Plot),1);
%if no matching data index found
if isempty(colorbarDataIndex)
    colorbarDataIndex = max(min(colorbarIndex,obj.State.Figure.NumPlots),1);
end
end


function [s, h] = getReplicationSettings(repName)
% GETREPLICATIONSETTINGS Get settings object corresponding to previous
% replication classes. Aditionally, return a second output with the help
% text of the replication containing the information of the authors of that
% study.

switch repName

    case "bear_settings_"
        s = bear_settings_;
        fn = 'bear_settings_';
    case "61"
        s = bear_settings_61;
        fn = 'bear_settings_61';
    case "AAU2009"
        s = bear_settings_AAU2009;
        fn = 'bear_settings_AAU2009';
    case "BBE2005"
        s = bear_settings_BBE2005;
        fn = 'bear_settings_BBE2005';
    case "BvV2018"
        s = bear_settings_BvV2018;
        fn = 'bear_settings_BvV2018';
    case "CH2019"
        s = bear_settings_CH2019;
        fn = 'bear_settings_CH2019';
    case "WGP2016"
        s = bear_settings_WGP2016;
        fn = 'bear_settings_WGP2016';
    otherwise
        error('getReplicationSettings:ReplicationNotFound','Unable to find the specified replication')

end
% Change results folder to current folder
s.results_path = pwd();

% Get model definition on non-deployed setups
if ~isdeployed
    h = help(fn);
else
    h = '';
end

end


function test_graphWin()
%Validate opening and closing of graph windows
%
%   test_graphWin()
%
% Tests: newGraphWin(), closeGraphWin()
%
% INPUTS
%  No inputs
%
% RETURNS
%  No returns
%
% Example:  test_graphWin()
%
% See also MRVTEST
%
% Copyright Stanford team, mrVista, 2015

% Because the figure handles changed from numbers (Matlab2014a and prior)
% to a FIGURE class (Matlab 2014b and later), it is important to ensure
% that opening and closing of figures works properly.

% open a figure using the newGraphWin command
newGraphWin();

% close the figure using closeGraphWin
closeGraphWin;

return


function varargout = spm_api_nmm(varargin)
% SPM_API_NMM M-file for spm_api_nmm.fig
%      SPM_API_NMM, by itself, creates a new SPM_API_NMM or raises the existing
%      singleton*.
%
%      H = SPM_API_NMM returns the handle to a new SPM_API_NMM or the handle to
%      the existing singleton*.
%
%      SPM_API_NMM('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in SPM_API_NMM.M with the given input arguments.
%
%      SPM_API_NMM('Property','Value',...) creates a new SPM_API_NMM or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before spm_api_nmm_OpeningFunction gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to spm_api_nmm_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

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

% Last Modified by GUIDE v2.5 17-Oct-2008 16:14:27

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
    'gui_Singleton',  gui_Singleton, ...
    'gui_OpeningFcn', @spm_api_nmm_OpeningFcn, ...
    'gui_OutputFcn',  @spm_api_nmm_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 spm_api_nmm is made visible.
%--------------------------------------------------------------------------
function spm_api_nmm_OpeningFcn(hObject, eventdata, handles, varargin)
set(gcf,'name','Neural mass modelling: review of priors');
try
    handles.DCM = varargin{1};
    unpack_Callback(hObject, eventdata, handles);
end

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

% Update handles structure
guidata(hObject, handles);

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

% --- Outputs from this function are returned to the command line.
function varargout = spm_api_nmm_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;

%==========================================================================

% --- Executes on button press in load.
%--------------------------------------------------------------------------
function load_Callback(hObject, eventdata, handles)

[f,p]       = uigetfile('*.mat','please select DCM file'); cd(p)
name        = fullfile(p,f);
DCM         = load(name,'-mat');
DCM         = DCM.DCM;
DCM.name    = name;
handles.DCM = DCM;
set(handles.name,'String',f);

% display priors
%--------------------------------------------------------------------------
unpack_Callback(hObject, eventdata, handles);
guidata(hObject,handles);

% --- Executes on button press in save.
%--------------------------------------------------------------------------
function save_Callback(hObject, eventdata, handles)
try
    [p,file] = fileparts(handles.DCM.name);
catch
    try
        [p,file] = fileparts(handles.DCM.xY.Dfile);
        file     = ['DCM_' file];
    catch
        file     = ['DCM'  date];
    end
end
[file,fpath]     = uiputfile(['DCM*.mat'],'DCM file to save',file);

if fpath
    handles.DCM.name = fullfile(fpath,file);
    set(handles.name,'String',file);
    DCM              = handles.DCM;
    save(DCM.name,'DCM', spm_get_defaults('mat.format'))
    cd(fpath)
end

% assign in base
%--------------------------------------------------------------------------
assignin('base','DCM',handles.DCM)
guidata(hObject,handles);

% --- Executes on button press in kernels.
%--------------------------------------------------------------------------
function kernels_Callback(hObject, eventdata, handles)
clear spm_erp_L spm_gen_erp
DCM   = handles.DCM;
pst   = str2num(get(handles.pst,'String'));
Hz    = str2num(get(handles.Hz, 'String'));

% initialise states
%--------------------------------------------------------------------------
M     = DCM.M;
model = DCM.options.model;
[x f] = spm_dcm_x_neural(M.pE,model);
M.x   = x;
M.f   = f;
M.m   = size(M.pE.C,2);
M.g   = {};
M.ons = 32*ones(M.m,1);

% Volterra Kernels
%==========================================================================

% augment and bi-linearise
%--------------------------------------------------------------------------
[M0,M1,L1,L2] = spm_bireduce(M,M.pE);

% compute kernels (over 64 ms)
%--------------------------------------------------------------------------
M.ns    = 128;
U.dt    = pst/M.ns/1000;
t       = [1:M.ns]*U.dt*1000;
[K0,K1] = spm_kernels(M0,M1,L1,L2,M.ns,U.dt);

axes(handles.kernel)
plot(t,K1(:,:,1))
set(gca,'XLim',[min(t) max(t)])
ylabel('1st-order Volterra kernel')
xlabel('time (ms)')

% transfer function (source space)
%--------------------------------------------------------------------------
for i = 1:size(K1,2)
    g(:,i) = fft(K1(:,i));
    g(:,i) = abs(g(:,i).*conj(g(:,i)));
end
i   = [1:M.ns/2];
g   = g(i + 1,:);
w   = i/(M.ns*U.dt);

axes(handles.transfer)
plot(w,g)
set(gca,'XLim',[1 Hz])
xlabel('frequency time (Hz)')
ylabel('spectral density')

% prediction (source space)
%--------------------------------------------------------------------------
x     = spm_gen_erp(M.pE,M,U);
x     = x{1};

axes(handles.erp)
plot(t,x)
set(gca,'XLim',[min(t) max(t)])
xlabel('peristimulus time (ms)')
ylabel('voltage and conductance')

% fft (source space)
%--------------------------------------------------------------------------
clear g
for i = 1:size(x,2)
    g(:,i) = fft(x(:,i));
    g(:,i) = abs(g(:,i).*conj(g(:,i)));
end
i   = [1:M.ns/2];
g   = g(i + 1,:);
w   = i/(M.ns*U.dt);

axes(handles.fft)
plot(w,g)
set(gca,'XLim',[1 Hz])
xlabel('frequency time (Hz)')
ylabel('spectral density')

% unpack priors and display
%==========================================================================
function unpack_Callback(hObject, eventdata, handles);

% clear previous objects
%--------------------------------------------------------------------------
h = get(gcf,'Children');
for i = 1:length(h)
    if strcmp(get(h(i),'Tag'),'tmp')
        delete(h(i));
    end
end

% Type of model (neuronal)
%--------------------------------------------------------------------------
% 'ERP'    - (linear second order NMM slow)
% 'SEP'    - (linear second order NMM fast)
% 'CMC'    - (linear second order NMM Canonical microcircuit)
% 'LFP'    - (linear second order NMM self-inhibition)
% 'NMM'    - (nonlinear second order NMM first-order moments)
% 'MFM'    - (nonlinear second order NMM second-order moments)
try
    model = handles.DCM.options.model;
catch
    model = get(handles.model,'String');
    model = model{get(handles.model,'Value')};
    DCM.options.model = model;
end
switch model
    case{'ERP'}, set(handles.model,'Value',1);
    case{'SEP'}, set(handles.model,'Value',2);
    case{'CMC'}, set(handles.model,'Value',3);
    case{'LFP'}, set(handles.model,'Value',4);
    case{'NMM'}, set(handles.model,'Value',5);
    case{'MFM'}, set(handles.model,'Value',6);
    case{'NMDA'}, set(handles.model,'Value',8);
    otherwise
end

% get priors
%--------------------------------------------------------------------------
try
    handles.DCM.M.pE;
catch
    handles = reset_Callback(hObject, eventdata, handles);
end
pE = handles.DCM.M.pE;
pC = handles.DCM.M.pC;
try
    pC = spm_unvec(diag(pC),pE);
end

% display connection switches later
%--------------------------------------------------------------------------
try, pE = rmfield(pE,'B');,  end
try, pE = rmfield(pE,'SA');, end
try, pE = rmfield(pE,'GE');, end
try, pE = rmfield(pE,'GI');, end

% do not display spatial and spectral priors
%--------------------------------------------------------------------------
try, pE = rmfield(pE,{'Lpos','L','J'});,      end
try, pE = rmfield(pE,{'a','b','c','d'});, end

% display fields
%--------------------------------------------------------------------------
color = {[1 1 1],get(handles.name,'BackgroundColor')};

x0    = 1/8;
y0    = 1 - 1/8;
sx    = 1/24;
sy    = 1/48;
dx    = 1/256 + sx;
dy    = 1/256 + sy;
F     = fieldnames(pE);
for f = 1:length(F)

    P = getfield(pE,F{f});

    if iscell(P)

        % cell
        %------------------------------------------------------------------
        for k = 1:length(P)
            for i = 1:size(P{k},1);
                for j = 1:size(P{k},2)
                    x   = x0 + j*dx + (size(P{k},2) + 1)*dx*(k - 1);
                    y   = y0 - (i - 1)*dy;
                    str = sprintf('handles.DCM.M.pE.%s{%i}(%i,%i)',F{f},k,i,j);
                    str = ['handles=guidata(gcbo);' str '=str2num(get(gcbo,''String''));guidata(gcbo,handles);'];
                    h = uicontrol(gcf,...
                        'Units','Normalized',...
                        'Position',[x y sx sy],...
                        'Style','edit',...
                        'String',sprintf('%-4.2f',P{k}(i,j)),...
                        'enable','on',...
                        'Tag','tmp',...
                        'BackgroundColor',color{1 + ~eval(sprintf('pC.%s{%i}(%i,%i)',F{f},k,i,j))},...
                        'Callback',str);
                end
            end
        end

    elseif isvector(P)

        % vector
        %------------------------------------------------------------------
        for i   = 1:length(P);
            x   = x0 + i*dx;
            y   = y0;
            str = sprintf('handles.DCM.M.pE.%s(%i)',F{f},i);
            str = ['handles=guidata(gcbo);' str '=str2num(get(gcbo,''String''));guidata(gcbo,handles);'];
            uicontrol(gcf,...
                'Units','Normalized',...
                'Position',[x y sx sy],...
                'Style','edit',...
                'String',sprintf('%-4.2f',full(P(i))),...
                'enable','on',...
                'Tag','tmp',...
                'BackgroundColor',color{1 + ~eval(sprintf('pC.%s(%i)',F{f},i))},...
                'Callback',str);
        end
    else

        % matrix
        %------------------------------------------------------------------
        for i = 1:size(P,1);
            for j   = 1:size(P,2)
                x   = x0 + j*dx;
                y   = y0 - (i - 1)*dy;
                str = sprintf('handles.DCM.M.pE.%s(%i,%i)',F{f},i,j);
                str = ['handles=guidata(gcbo);' str '=str2num(get(gcbo,''String''));guidata(gcbo,handles);'];
                uicontrol(gcf,...
                    'Units','Normalized',...
                    'Position',[x y sx sy],...
                    'Style','edit',...
                    'String',sprintf('%-4.2f',full(P(i,j))),...
                    'enable','on',...
                    'Tag','tmp',...
                    'BackgroundColor',color{1 + ~eval(sprintf('pC.%s(%i,%i)',F{f},i,j))},...
                    'Callback',str);
            end
        end
    end

    % label
    %----------------------------------------------------------------------
    uicontrol(gcf,...
        'Units','Normalized',...
        'Position',[x0 - dx y0 2*sx sy],...
        'Style','text',...
        'String',sprintf('pE.%s',F{f}),...
        'ForegroundColor',[1 0 0],...
        'HorizontalAlignment','left',...
        'FontSize',12,...
        'Tag','tmp');
    y0 = y - dy - dy/2;

end

% connectivity matrices
%==========================================================================
pE      = handles.DCM.M.pE;
try, SA = pE.SA; catch, SA = []; end
try, GE = pE.GE; catch, GE = []; end
try, GI = pE.GI; catch, GI = []; end

% SA matrix
%--------------------------------------------------------------------------
x0    = 0.7;
y0    = 0.3;
for i = 1:size(SA,1);
    for j   = 1:size(SA,2)
        x   = x0 + j*dx;
        y   = y0 - (i - 1)*dy;
        str = sprintf('handles.DCM.M.pE.SA(%i,%i)',i,j);
        str = ['handles=guidata(gcbo);' str '=str2num(get(gcbo,''String''));guidata(gcbo,handles);'];
        uicontrol(gcf,...
            'Units','Normalized',...
            'Position',[x y sx sy],...
            'Style','edit',...
            'String',sprintf('%-4.2f',SA(i,j)),...
            'enable','on',...
            'Tag','tmp',...
            'Callback',str);
    end
end

% GE matrix
%--------------------------------------------------------------------------
x0    = 0.7;
y0    = 0.2;
for i = 1:size(GE,1);
    for j   = 1:size(GE,2)
        x   = x0 + j*dx;
        y   = y0 - (i - 1)*dy;
        str = sprintf('handles.DCM.M.pE.GE(%i,%i)',i,j);
        str = ['handles=guidata(gcbo);' str '=str2num(get(gcbo,''String''));guidata(gcbo,handles);'];
        uicontrol(gcf,...
            'Units','Normalized',...
            'Position',[x y sx sy],...
            'Style','edit',...
            'String',sprintf('%-4.2f',GE(i,j)),...
            'enable','on',...
            'Tag','tmp',...
            'Callback',str);
    end
end

% GI matrix
%--------------------------------------------------------------------------
x0    = 0.7;
y0    = 0.1;
for i = 1:size(GI,1);
    for j   = 1:size(GI,2)
        x   = x0 + j*dx;
        y   = y0 - (i - 1)*dy;
        str = sprintf('handles.DCM.M.pE.GI(%i,%i)',i,j);
        str = ['handles=guidata(gcbo);' str '=str2num(get(gcbo,''String''));guidata(gcbo,handles);'];
        uicontrol(gcf,...
            'Units','Normalized',...
            'Position',[x y sx sy],...
            'Style','edit',...
            'String',sprintf('%-4.2f',GI(i,j)),...
            'enable','on',...
            'Tag','tmp',...
            'Callback',str);
    end
end

% onset time
%==========================================================================
try
    ons  = handles.DCM.M.ons;
catch
    ons  = 32;
    handles.DCM.M.ons = ons;
end
str = 'handles.DCM.M.ons';
str = ['handles=guidata(gcbo);' str '=str2num(get(gcbo,''String''));guidata(gcbo,handles);'];
uicontrol(gcf,...
    'Units','Normalized',...
    'Position',[0.848 0.341 0.071 0.022],...
    'Style','edit',...
    'String',sprintf('%-4.0f',ons),...
    'enable','on',...
    'Tag','tmp',...
    'Callback',str);

guidata(hObject,handles);

% --- Executes on button press in reset.
%--------------------------------------------------------------------------
function handles = reset_Callback(hObject, eventdata, handles)

try
    DCM          = handles.DCM;
catch
    load_Callback(hObject, eventdata, handles)
    return
end
model            = DCM.options.model;
[pE,pC]          = spm_dcm_neural_priors(DCM.A,DCM.B,DCM.C,model);
handles.DCM.M.pE = pE;
handles.DCM.M.pC = pC;
guidata(hObject,handles);
unpack_Callback(hObject, eventdata, handles);

% --- Executes on button press in conditional.
%--------------------------------------------------------------------------
function conditional_Callback(hObject, eventdata, handles)

% conditional moments on parameters
%--------------------------------------------------------------------------
try
    handles.DCM.M.pE = handles.DCM.Ep;
    unpack_Callback(hObject, eventdata, handles);
catch
    warndlg('please invert and load a DCM to obtain conditional estimates')
end

% --- Executes on selection change in model.
function model_Callback(hObject, eventdata, handles)

% model type
%--------------------------------------------------------------------------
model = get(handles.model,       'String');
model = model{get(handles.model, 'Value')};
handles.DCM.options.model = model;
try
    reset_Callback(hObject, eventdata, handles);
catch
    load_Callback(hObject, eventdata, handles)
    return
end