function this = checkmeeg(this)
% Check the internal structure of meeg objects
% FORMAT this = checkmeeg(this)
% this  - the struct to check (is returned modified if necessary)
%__________________________________________________________________________

% Vladimir Litvak
% Copyright (C) 2008-2022 Wellcome Centre for Human Neuroimaging


%-Initialise data dimensions
%--------------------------------------------------------------------------
if ~isfield(this, 'Nsamples')
    this.Nsamples = 0;
end
Nsamples = this.Nsamples;

if ~isfield(this, 'channels')
    this.channels = struct([]);
end
Nchannels = length(this.channels);

if ~isfield(this, 'trials')
    this.trials = struct([]);
end
Ntrials = length(this.trials);

if ~isfield(this, 'transform')
    this.transform.ID = 'time';
end

isTF =  strncmp(this.transform.ID, 'TF', 2);

if isTF
    if ~isfield(this.transform, 'frequencies')
        error('Frequency axis must be defined for spectral dataset.');
    end
    Nfrequencies = length(this.transform.frequencies);
else
    this.transform = struct('ID', 'time');
    Nfrequencies = 0;
end

if isTF
    expected_size = [Nchannels Nfrequencies Nsamples];
else
    expected_size = [Nchannels Nsamples];
end

if Ntrials > 1
    expected_size = [expected_size Ntrials];
end

is_empty = ~all(expected_size);


%-Link to the data file if necessary
%--------------------------------------------------------------------------
if ~isfield(this, 'path') || ~exist(this.path, 'dir')
    this.path = pwd;
end

if ~isfield(this, 'data')
    this.data = [];
end

% Conversion from SPM8 format
if isfield(this.data, 'y')
    this.data = this.data.y;
end

is_linked = 0;
if isa(this.data, 'file_array')
    fname = this.data.fname;
    if ~ispc, fname = strrep(fname, '\', filesep); end
    
    [p, f, x] = fileparts(fname);
    
    % this.path takes precedence over the path in file_array to support
    % both the case when the dataset is copied (more common) and the
    % case when header saved in a mat file is used to link to a
    % datafile saved somewhere else
    fname = fullfile(this.path, [f x]);
    if ~exist(fname, 'file')
        fname = fullfile(p, [f x]);
        if ~exist(fname, 'file')
            error('Could not find the data file at either:\n  %s\nor\n  %s',...
                fullfile(this.path, [f x]),fname);
        end
    end
    
    this.data.fname = fname;
    try
        % Try reading data, i.e. check if it's a "good" filearray
        this.data(1, 1, 1);
    catch
        if ~is_empty
            scl_slope = this.data.scl_slope;
            offset    = this.data.offset;
            this.data = file_array(fname, expected_size, this.data.dtype);
            
            if  size(this.data, 1) == length(scl_slope)
                this.data.scl_slope = scl_slope;
            end
            this.data.offset = offset;
        end
    end
    
    try % once more
        this.data(1, 1, 1);
        is_linked = 1;
    catch
        warning_flexible('SPM:checkmeeg', 'Failed to link to the data file. Unlinking.');
        this.data = [];
        is_linked = 0;
    end       
end

actual_size = size(this.data); 
actual_size = [actual_size ones(1, isTF + 3 - length(actual_size))];

if ~isfield(this, 'Fsample')
    this.Fsample = 0.01;
end

if ~isfield(this, 'timeOnset')
    this.timeOnset = 0;
elseif isempty(this.timeOnset)
    if ~is_empty
        this.timeOnset = 1/this.Fsample;
    else
        this.timeOnset = 0;
    end
end

%-Check channel description
%--------------------------------------------------------------------------
if ~isfield(this, 'channels') 
    this.channels = struct([]);
end

if is_linked && ~isempty(this.channels) && (numel(this.channels) ~= actual_size(1))
    error('Channel description does not match the data.');
end
    
if is_linked
    Nchannels = actual_size(1);
else
    Nchannels = numel(this.channels);
end

if Nchannels > 0
    if ~isfield(this.channels, 'label')
        for i = 1:Nchannels
            this.channels(i).label = ['Ch' num2str(i)];
        end
    end
    
    if ~isfield(this.channels, 'bad')
        [this.channels.bad] = deal(0);
    else
        [this.channels(cellfun('isempty', {this.channels.bad})).bad] = deal(0);
    end
    
    for i = 1:Nchannels
        this.channels(i).bad = double(~~this.channels(i).bad);
    end
    
    if ~isfield(this.channels, 'type')
        [this.channels.type] = deal('Other');
    end
    
    if ~isfield(this.channels, 'X_plot2D')
        [this.channels.X_plot2D] = deal([]);
        [this.channels.Y_plot2D] = deal([]);
    end
    
    if ~isfield(this.channels, 'units')
        [this.channels.units] = deal('unknown');
    else
        [this.channels(cellfun('isempty', {this.channels.units})).units] = deal('unknown');
    end
    
    for i = 1:Nchannels
        if ~(length(this.channels(i).X_plot2D)==1 && isfinite(this.channels(i).X_plot2D))
            this.channels(i).X_plot2D = [];
        end
        
        if ~(length(this.channels(i).Y_plot2D)==1 && isfinite(this.channels(i).Y_plot2D))
            this.channels(i).Y_plot2D = [];
        end
    end
end

%-Check trial description
%--------------------------------------------------------------------------
if ~isfield(this, 'trials') 
    this.trials = struct([]);
end

if is_linked && ~isempty(this.trials) && (numel(this.trials) ~= actual_size(end))
    error('Trial description does not match the data.');
end
    
if is_linked
    Ntrials = actual_size(end);
else
    Ntrials = numel(this.trials);
end

if Ntrials > 0 
    if ~isfield(this.trials, 'label')       
        [this.trials.label] = deal('Undefined');
    elseif any(cellfun('isempty', {this.trials(:).label}))
        warning_flexible('SPM:checkmeeg', 'Some trial labels empty, assigning default.');
        [this.trials(cellfun('isempty', {this.trials(:).label})).label] = deal('Undefined');
    end
    if ~isfield(this.trials, 'bad')
        [this.trials.bad] = deal(0);
    end
    
    if ~isfield(this.trials, 'tag')
        [this.trials.tag] = deal([]);
    end
    
    if ~isfield(this.trials, 'events')
        [this.trials.events] = deal([]);
    end
    
    for i = 1:Ntrials
        
        label = this.trials(i).label;
        
        if iscell(label) && numel(label) == 1
            label = label{1};
        end
        
        if isnumeric(label)
            label = num2str(label);
        end
        
        if isa(label, 'char')
            this.trials(i).label = deblank(label);
        else
            this.trials(i).label = 'Unknown';
            warning_flexible('SPM:checkmeeg', 'Some trial labels were not strings, changing back to ''Unknown''.');
        end
        
        if  length(this.trials(i).bad)>1 || ~(this.trials(i).bad == 0 || this.trials(i).bad == 1)
            warning_flexible('SPM:checkmeeg', ['Illegal value for bad flag in trial ' num2str(i) ', resetting to zero.']);
            this.trials(i).bad = 0;
        end
        
        event = this.trials(i).events;
        
        if ~isempty(event) && ~(numel(event) == 1 && isequal(event.type, 'no events'))
            % make sure that all required elements are present
            if ~isfield(event, 'type'),     error('type field not defined for each event.');  end
            if ~isfield(event, 'time'),     error('time field not defined for each event.');  end
            if ~isfield(event, 'value'),    [event.value]    = deal([]);                     end
            if ~isfield(event, 'offset'),   [event.offset]   = deal(0);                      end
            if ~isfield(event, 'duration'), [event.duration] = deal([]);                     end
            
            
            % make sure that all numeric values are double
            for j = 1:length(event)
                if isempty(event(j).value)
                    event(j).value = [];
                end
                if isnumeric(event(j).value)
                    event(j).value = double(event(j).value);
                end
                event(j).time      = double(event(j).time);
                event(j).offset    = double(event(j).offset);
                event(j).duration  = double(event(j).duration);
            end
            
            if ~isempty(event)
                % sort the events on the sample on which they occur
                % this has the side effect that events without time are discarded
                [junk, indx] = sort([event.time]);
                event = event(indx);
            end
        else
            event = [];
        end
        
        this.trials(i).events = event;
    end
    
    if Ntrials == 1
        this.trials.onset = this.timeOnset;
    end
    
    if ~isfield(this.trials, 'onset')
        [this.trials.onset] = deal(0);
    else
        [this.trials(cellfun('isempty', {this.trials.onset})).onset] = deal(0);
    end
    if ~isfield(this.trials, 'repl')
        [this.trials.repl] = deal(1);
    end
end

%-Check frequency axis
%--------------------------------------------------------------------------
if isTF
    if is_linked && (length(this.transform.frequencies) ~= actual_size(2))
        error('Frequency axis does not match the data.');
    end
    
    df = diff(this.transform.frequencies);
    % To avoid small numerical errors
    if length(unique(df)) > 1 && (max(diff(df))/mean(df))<0.1
        this.transform.frequencies = (0:(Nfrequencies-1))*round(100*mean(df))/100+...
            round(100*this.transform.frequencies(1))/100;
    end
end


%-Check data type
%--------------------------------------------------------------------------
if ~isfield(this, 'type') ||...
        (strcmp(this.type, 'continuous') && Ntrials>1) ||...
        (strcmp(this.type, 'evoked') && (numel(unique({this.trials.label})) ~= Ntrials))
    disp('Data type is missing or incorrect, assigning default.');
    % rule of thumb - 10 sec
    if Nsamples == 0
        this.type = 'continuous';
    elseif Ntrials==1 && (Nsamples/this.Fsample) > 10 
        this.type = 'continuous';
    elseif numel(unique({this.trials.label})) == Ntrials
        this.type = 'evoked';
    else
        this.type = 'single';
    end
end

%-Check file name
%--------------------------------------------------------------------------
if ~isfield(this, 'fname')
    if is_linked
        this.fname = [f '.mat'];
    else
        this.fname = 'spm_meeg.mat';
    end
end


%-Check sensor description
%--------------------------------------------------------------------------
ft_warning off
if ~isfield(this, 'sensors')
    this.sensors = struct([]);
else
    if isfield(this.sensors, 'eeg')
        if isempty(this.sensors.eeg)
            this.sensors = rmfield(this.sensors, 'eeg');
        else   
            this.sensors.eeg = ft_datatype_sens(this.sensors.eeg, 'version', 'latest', 'distance', 'mm');
            % Enforce units unless pre-whitened data
            if ~isequal(unique(this.sensors.eeg.chanunit(strmatch('EEG', this.sensors.eeg.chantype))), {'snr'})
                this.sensors.eeg = ft_datatype_sens(this.sensors.eeg, 'version', 'latest', 'amplitude', 'V', 'distance', 'mm');
            end
        end
    end
    if isfield(this.sensors, 'meg')
        if isempty(this.sensors.meg)
            this.sensors = rmfield(this.sensors, 'meg');
        else                    
            this.sensors.meg = ft_datatype_sens(this.sensors.meg, 'version', 'latest', 'distance', 'mm');
            % Enforce units unless pre-whitened data
            if ~isequal(unique(this.sensors.meg.chanunit(strmatch('MEG', this.sensors.meg.chantype))), {'snr'})
                this.sensors.meg = ft_datatype_sens(this.sensors.meg, 'version', 'latest', 'amplitude', 'T', 'distance', 'mm');
            end
        end
    end
end
ft_warning on
%-Check other fields
%--------------------------------------------------------------------------
if ~isfield(this, 'fiducials')
   this.fiducials = struct([]);
else
   this.fiducials  = ft_convert_units(ft_struct2double(fixpnt(this.fiducials)), 'mm');
end

if ~isfield(this, 'artifacts')
    this.artifacts = struct([]);
end

if ~isfield(this, 'other')
    this.other = struct([]);
end

if ~isfield(this, 'condlist')
    if isfield(this.other, 'condlist')
        this.condlist = this.other.condlist;
        this.other = rmfield(this.other, 'condlist');
    else
        this.condlist = {};
    end
end

if ~isempty(this.condlist)
    [junk, ind] = unique(this.condlist, 'first');
    this.condlist =  this.condlist(sort(ind));
end

if ~isfield(this, 'history')
    this.history = struct([]);
end

if ~isfield(this, 'montage') ||  ~isfield(this.montage,'M')
    this.montage = struct('M',[],'Mind',0);
else    
    if this.montage.Mind > numel(this.montage.M) || ...
            this.montage.Mind < 0
        % check montage index, if not good -> 0
        this.montage.Mind = 0;
    end
end

%-Check field order 
%--------------------------------------------------------------------------
fieldnames_order = {
    'type'
    'Nsamples'
    'Fsample'
    'timeOnset'
    'trials'
    'channels'
    'data'
    'fname'
    'path'
    'sensors'
    'fiducials'
    'transform'
    'condlist'
    'montage'
    'history'
    'other'};

[sel1, sel2] = match_str(fieldnames_order, fieldnames(this));
tempcell = struct2cell(this);
this = cell2struct(tempcell(sel2), fieldnames_order, 1);
