function [outlier, resultTable, inferData, islandTable, relPosTable, studyOverview] = RunInference(filePath, modelName, varargin)

inputParam = inputParser;
inputParam.addRequired('filePath', @(x) ischar(x) || isstring(x));
inputParam.addRequired('model', @(x) ischar(x) || isstring(x));
inputParam.addParameter('preloadedData', struct(), @(x) isstruct(x))
parse(inputParam, filePath, modelName, varargin{:});

filePath = inputParam.Results.filePath;
modelName = inputParam.Results.model;
preloadedData = inputParam.Results.preloadedData;

% Construct path to modelConfigurations.mat inside Models directory
modelsDir = fullfile(fileparts(mfilename('fullpath')), '..', 'Models');
configFile = fullfile(modelsDir, 'modelConfigurations.mat');

load(configFile, "configData");
modelFn = matlab.lang.makeValidName(modelName);
Model = configData.(modelFn).Model;
averageDirection = Model.averageDirection;
Model = rmfield(Model, 'averageDirection');
Modality = configData.(modelFn).Modality;
mappingROI = configData.(modelFn).Mapping;
mergeROI = configData.(modelFn).Merging;

if isempty(fieldnames(preloadedData))
    studyOverview = CollectDataForModel(filePath, mappingROI, mergeROI);
else
    studyOverview = preloadedData;
end

% if we have generated any empty rows in the studyOverview (maybe a bad
% file was read) then sort them out here
emptyStructIndex = all(cellfun(@isempty, struct2cell(studyOverview)));
studyOverview(emptyStructIndex) = [];

for iStudy = 1:length(studyOverview)
roiNameMap = CompareRoiName(studyOverview(iStudy), Model);
if ~isempty(roiNameMap)
  roiNames = fieldnames(studyOverview(iStudy).ROIInformation);
  
  for iField = 1:length(roiNames)
    roiName = roiNames{iField};
    if isKey(roiNameMap, roiName) && ~cellfun(@(C) any(isnan(C(:))), values(roiNameMap, {roiName}))
      promptText = fprintf('Changing Roi Name of Sample from "%s" to "%s"). \n', roiName, char(values(roiNameMap, {roiName})));
      roiNames{iField} = char(values(roiNameMap, {roiName}));
    end
  end
  studyOverview(iStudy).ROIInformation = cell2struct( struct2cell(studyOverview(iStudy).ROIInformation), roiNames);
end
end
assert(length(studyOverview) > 0, 'No studies found to analyse. Please check your filepath');

% sometimes for small models we do not have all structures in the model
% that are in the inferred RTStruct. So we need to sort that out.
roiInformationNames = arrayfun(@(x) fieldnames(x.ROIInformation), studyOverview, 'UniformOutput', false);
roiInformationNames = unique(vertcat(roiInformationNames{:}));
roiNamesNotInModel = setdiff(roiInformationNames, fieldnames(Model));
for i = 1:length(studyOverview)
    for iRoiRemove = 1:length(roiNamesNotInModel)
        % if the roiNamesNotInModel that need to be removed from some of
        % the studyOverview elements is not found in the particular row
        % then just skip it
        if any(ismember(fieldnames(studyOverview(i).ROIInformation), roiNamesNotInModel(iRoiRemove)))
            studyOverview(i).ROIInformation = rmfield(studyOverview(i).ROIInformation, roiNamesNotInModel{iRoiRemove});
        end
    end
end


inferData = CalculateFurtherParameterValuesForInference(studyOverview, averageDirection, Model);

% sometimes there are duplicates defined. For cosmetic reasons we want to
% throw such duplicates out.
% inferData = RemoveDuplicates(inferData);

% NOTE: CoM position was replaced with relative position. Therefore this is
% deprecated
% inferData = TranslationCorrectionInference(inferData, Model);
% inferData.xcm = inferData.PosValTranslated(:,1);
% inferData.ycm = inferData.PosValTranslated(:,2);
% inferData.zcm = inferData.PosValTranslated(:,3);


outlier = CalcLikelihood(inferData, Model);
nOutlier = size(outlier,1);
for iData = 1:nOutlier
    roiNamesData = fieldnames(outlier{iData,2});
    roiNamesModel = fieldnames(Model);
    nRoiNames = length(roiNamesModel);
    pRes = zeros(1, nRoiNames);
    islandRes = zeros(1, nRoiNames);
    relPosRes = zeros(1, nRoiNames);
    for iRoi = 1:nRoiNames
        try
            if ismember(roiNamesModel{iRoi}, roiNamesData)
                pRes(iRoi) = outlier{iData,2}.(roiNamesModel{iRoi}).p;
                islandRes(iRoi) = outlier{iData,2}.(roiNamesModel{iRoi}).LikelihoodIslands;
                relPosRes(iRoi) = outlier{iData,2}.(roiNamesModel{iRoi}).pRelPos;
            else
                pRes(iRoi) = NaN;
                islandRes(iRoi) = NaN;
                relPosRes(iRoi) = NaN;
            end
        catch ME
            pRes(iRoi) = NaN;
            islandRes(iRoi) = NaN;
            relPosRes(iRoi) = NaN;
            fprintf('ROI %s not found.\n', roiNamesModel{iRoi});
        end
    end
    resultTable(iData,:) = array2table(pRes);
    islandTable(iData,:) = array2table(islandRes);
    relPosTable(iData,:) = array2table(relPosRes);
    % TODO check that this is always the same
    resultTable.Properties.VariableNames = fieldnames(Model); 
    islandTable.Properties.VariableNames = fieldnames(Model); 
    relPosTable.Properties.VariableNames = fieldnames(Model);
end
resultTable.Properties.RowNames = [outlier{:,1}];
% Add lowest probability per row for result table
minProb = min(resultTable, [], 2); % Get min value for each row
resultTable = [minProb, resultTable]; % Add max values as the first column
resultTable.Properties.VariableNames{'min'} = 'Minimum Probability';
% Add lowest probability per row for island table
minIsland = min(islandTable, [], 2); % Get min value for each row
islandTable = [minIsland, islandTable]; % Add max values as the first column
islandTable.Properties.VariableNames{'min'} = 'Minimum Likelihood';
% Add lowest probability per row for relPos table
minRelPos = min(relPosTable, [], 2); % Get min value for each row
relPosTable = [minRelPos, relPosTable]; % Add max values as the first column
relPosTable.Properties.VariableNames{'min'} = 'Minimum Likelihood';
disp('Inference is done!')
end
