function Do = spm_eeg_grandmean(S)
% Average over multiple M/EEG data sets
% FORMAT Do = spm_eeg_grandmean(S)
%
% S         - struct (optional)
%  fields of S:
%   D         - filenames (char matrix) of M/EEG MAT-files containing
%               epoched data
%   weighted  - average weighted by number of replications in inputs (1)
%               or not (0) [default: 0]
%   outfile   - name of the output file [default: 'grand_mean']
%
% Output:
% Do        - EEG data struct, result files are saved in the same
%             directory as first input file.
%__________________________________________________________________________
%
% spm_eeg_grandmean averages data over multiple files. The data must have
% the same trialtype numbering and sampling rate. This function can be used
% for grand mean averaging, i.e. computing the average over multiple
% subjects. Missing event types and bad channels are taken into account
% properly. The output is written to a user-specified new file. The default
% name is the same name as the first selected input file, but prefixed with
% a 'g'. The output file is written to the current working directory.
%__________________________________________________________________________

% Stefan Kiebel
% Copyright (C) 2008-2022 Wellcome Centre for Human Neuroimaging


%-Startup
%--------------------------------------------------------------------------
spm('FnBanner', mfilename);
spm('FigName','M/EEG Grand Mean'); spm('Pointer','Watch');

if ~isfield(S, 'weighted'),     S.weighted = 0;            end
if ~isfield(S, 'outfile'),      S.outfile = 'grand_mean';  end


%-Load MEEG data
%--------------------------------------------------------------------------
D = S.D;

if ischar(D)
    F = cell(1,size(D,1));
    try
        for i = 1:size(D, 1)
            F{i} = spm_eeg_load(deblank(D(i, :)));
        end
        D = F;
    catch
        error('Trouble reading files.');
    end
end

Nfiles = length(D);

%-Check dimension of the data files
%--------------------------------------------------------------------------
estr = '';

for i = 1:Nfiles
    flist = [];
    if ~strcmp(D{i}.type, 'evoked')
        flist = [flist ' ' D{i}.fname];
    end
    if ~isempty(flist)
       estr = ['The files' flist ' do not contain averaged (evoked) data.'];
    end
end

% This applies to # of channels, # of samples, # of conditions, sampling
% rate, and # of frequencies (if time-frequency data).
nc = zeros(Nfiles,1);
ns = zeros(Nfiles,1);
fs = zeros(Nfiles,1);
ne = zeros(Nfiles,1);
if strncmp(D{1}.transformtype, 'TF',2)
    nf = zeros(Nfiles,1);
end

megsens = [];
eegsens = [];
fid     = [];
for i = 1:Nfiles
    nc(i) = D{i}.nchannels;
    ns(i) = D{i}.nsamples;
    fs(i) = D{i}.fsample;
    ne(i) = D{i}.nconditions;
    if strncmp(D{1}.transformtype, 'TF',2)
        nf(i) = D{i}.nfrequencies;
    end
    
    if ~isempty(D{i}.sensors('MEG')) || ~isempty(D{i}.sensors('EEG'))
        D{i} = check(D{i}, '3d');
    end
    
    if ~isempty(D{i}.sensors('MEG'))
        megsens = spm_cat_struct(megsens, D{i}.sensors('MEG'));
    end
    
    if ~isempty(D{i}.sensors('EEG'))
        eegsens = spm_cat_struct(eegsens, D{i}.sensors('EEG'));
    end
    
    if ~isempty(megsens) || ~isempty(eegsens)
        fid = spm_cat_struct(fid, D{i}.fiducials);
    end
end

unc = unique(nc);
if length(unc)~=1
    ind = zeros(Nfiles,1);
    fna = cell(length(unc),1);
    for i=1:Nfiles
        ind(i) = find(unc==nc(i));
        fna{ind(i)} = [fna{ind(i)},'-',D{i}.fname,'\n'];
    end
    fprintf('\n')
    for i=1:length(unc)
        fprintf(['\nThose files have ',num2str(unc(i)),' channels:\n',...
            fna{i}])
    end
    estr = [estr,'Data don''t have the same number of channels.\n'];
end

uns = unique(ns);
if length(uns)~=1
    ind = zeros(Nfiles,1);
    fna = cell(length(uns),1);
    for i=1:Nfiles
        ind(i) = find(uns==ns(i));
        fna{ind(i)} = [fna{ind(i)},'-',D{i}.fname,'\n'];
    end
    fprintf('\n')
    for i=1:length(uns)
        fprintf(['\nThose files have ',num2str(uns(i)),' time points:\n',...
            fna{i}])
    end
    estr = [estr,'Data don''t have the same number of time points.\n'];
end

ufs = unique(fs);
if length(ufs)~=1
    ind = zeros(Nfiles,1);
    fna = cell(length(ufs),1);
    for i=1:Nfiles
        ind(i) = find(ufs==fs(i));
        fna{ind(i)} = [fna{ind(i)},'-',D{i}.fname,'\n'];
    end
    fprintf('\n')
    for i=1:length(ufs)
        fprintf(['\nThose files have a sampling rate of ',num2str(ufs(i)),' Hz:\n',...
            fna{i}])
    end
    estr = [estr,'Data don''t have the same sampling rate.\n'];
end

une = unique(ne);
if length(une)~=1
    ind = zeros(Nfiles,1);
    fna = cell(length(une),1);
    for i=1:Nfiles
        ind(i) = find(une==ne(i));
        fna{ind(i)} = [fna{ind(i)},'-',D{i}.fname,'\n'];
    end
    fprintf('\n')
    for i=1:length(unc)
        fprintf(['\nThose files have ',num2str(une(i)),' conditions:\n',...
            fna{i}])
    end
    %estr = [estr,'Data don''t have the same number of conditions.\n'];
end

if strncmp(D{1}.transformtype, 'TF',2)
    unf = unique(nf);
    if length(unf)~=1
        ind = zeros(Nfiles,1);
        fna = cell(length(unf),1);
        for i=1:Nfiles
            ind(i) = find(unf==nf(i));
            fna{ind(i)} = [fna{ind(i)},'-',D{i}.fname,'\n'];
        end
        fprintf('\n')
        for i=1:length(unf)
            fprintf(['\nThose files have ',num2str(unf(i)),' frequency bins:\n',...
                fna{i}])
        end
        estr = [estr,'Data don''t have the same number of frequency bins.\n'];
    end
end

% send message error (if any)
if ~isempty(estr)
    error(estr)
else
    fprintf('Ok: All data files have the same dimensions.\n')
end



%-Initialise output
%--------------------------------------------------------------------------
Do = D{1};

% how many different trial types and bad channels
types = {};
for i = 1:Nfiles
    types = unique([types, D{i}.condlist]);
end

% The order of the conditions will be consistent with the first file
[sel1, sel2] = spm_match_str(D{1}.condlist, types);
sel2  = sel2(:)';
types = types([sel2, setdiff(1:length(types), sel2)]);

Ntypes = numel(types);

% how many repetitons per trial type
nrepl = zeros(Nfiles, Ntypes);
for i = 1:Nfiles
    for j = 1:numel(types)
        ind = D{i}.indtrial(types{j});
        if ~isempty(ind)
            nrepl(i, j) =  D{i}.repl(ind);
        end
    end 
end

if ~S.weighted
    nrepl = ones(Nfiles, Ntypes);
end

% generate new meeg object with new filenames
if strncmp(D{1}.transformtype, 'TF',2)
    Do = clone(Do, S.outfile, [Do.nchannels Do.nfrequencies Do.nsamples Ntypes]);
else
    Do = clone(Do, S.outfile, [Do.nchannels Do.nsamples Ntypes]);
end

% for determining bad channels of the grandmean
w = zeros(Do.nchannels, Ntypes);

badchans  = cellfun(@badchannels, D, 'UniformOutput', 0); 
trialinds = cellfun(@(x) x.indtrial(types, 'GOOD'), D, 'UniformOutput', 0); 

%-Do the averaging
%--------------------------------------------------------------------------
spm_progress_bar('Init', Ntypes, 'responses averaged');
if Ntypes > 100, Ibar = floor(linspace(1, Ntypes, 100));
else Ibar = [1:Ntypes]; end

if strncmp(D{1}.transformtype, 'TF',2)
    for i = 1:Ntypes
        d = zeros(D{1}.nchannels, D{1}.nfrequencies, D{1}.nsamples);

        for j = 1:D{1}.nchannels

            for k = 1:Nfiles
                 if ~any(badchans{k}==j) 
                    ind = trialinds{k}(i);
                    if ~isempty(ind)
                        d(j, :, :) = d(j, :, :) + nrepl(k, i)*D{k}(j, :, :, ind);
                        w(j, i) = w(j, i) + nrepl(k, i);
                    end
                end
            end

            if w(j, i) > 0
                d(j, :, :) = d(j, :, :)/w(j, i);
            end

        end

        Do(1:Do.nchannels, 1:Do.nfrequencies, 1:Do.nsamples, i) = d;

        if any(Ibar==i), spm_progress_bar('Set', i); end

    end

else
    for i = 1:Ntypes
        
        d = zeros(D{1}.nchannels, D{1}.nsamples);

        for j = 1:D{1}.nchannels

            for k = 1:Nfiles
                if ~any(badchans{k}==j) 
                    ind = trialinds{k}(i);
                    if ~isempty(ind)
                        d(j, :) = d(j, :) + nrepl(k, i)*D{k}(j, :, ind); 
                        w(j, i) = w(j, i) + nrepl(k, i);
                    end
                end
            end

            if w(j, i) > 0
                d(j, :) = d(j, :)/w(j, i);
            end

        end

        Do(1:Do.nchannels, 1:Do.nsamples, i) = d;

        if any(Ibar==i), spm_progress_bar('Set', i); end

    end
end

spm_progress_bar('Clear');

%-Average sensor locations
%--------------------------------------------------------------------------
nograph = spm('CmdLine');
h       = [];
Ntrials = sum(nrepl, 2);
if ~isempty(megsens)
    if ~nograph, spm_figure('GetWin','Graphics');clf; end
    if ~isempty(eegsens)
        if ~nograph, h = subplot(2, 1, 1); end
        aeegsens = ft_average_sens(eegsens, 'weights', Ntrials, 'feedback', h);
        Do = sensors(Do, 'EEG', aeegsens);
        if ~nograph, h = subplot(2, 1, 2); end
    else
        if ~nograph, h = axes; end
    end
    [amegsens,afid] = ft_average_sens(megsens, 'fiducials', fid, 'weights', Ntrials, 'feedback', h);
    Do = sensors(Do, 'MEG', amegsens);
    Do = fiducials(Do, afid);
elseif ~isempty(eegsens)
    if ~nograph
        spm_figure('GetWin','Graphics');clf;
        h = axes;
    end
    [aeegsens,afid] = ft_average_sens(eegsens, 'fiducials', fid, 'weights', Ntrials, 'feedback', h);
    Do = sensors(Do, 'EEG', aeegsens);
    Do = fiducials(Do, afid);
end

%-Save Grand Mean to disk
%--------------------------------------------------------------------------
Do = type(Do, 'evoked');

nrepl = sum(nrepl, 1);

Do = badchannels(Do, ':', ~any(w, 2));
Do = conditions(Do, ':', types);
Do = repl(Do, ':', nrepl);
Do = badtrials(Do, ':', 0);
Do = trialonset(Do, ':', []);
Do = trialtag(Do, ':', []);
Do = Do.history('spm_eeg_grandmean', S, 'reset');

save(Do);

%-Cleanup
%--------------------------------------------------------------------------
spm('FigName','M/EEG Grand Mean: done'); spm('Pointer','Arrow');
