% ConfigurationsUtils - Utilities for Configurations

% Copyright 2011-2020 The MathWorks, Inc.

classdef ConfigurationsUtils
    methods (Static)
        function [name, values] = loadConfigurationFile(filename)
            import parallel.internal.apishared.ConfigurationsUtils
            iCheckConfigurationFilename(filename);

            % The format of the data stored in the .mat file is defined in 
            % distcomp.configuration.exportToFile.m
            try
                completeState = load(filename, '-mat');
            catch err
                ex = MException(message('parallel:settings:ConfigurationsLoadFailed', filename));
                ex = ex.addCause(err);
                throw(ex);
            end

            % Note that we're not actually making any use of the version string anymore, but
            % the absence of this field still constitutes an invalid configuration .mat file.
            try
                version = completeState.Version; %#ok<NASGU>
            catch err
                error(message('parallel:settings:ConfigurationsLoadMissingVersion', filename));
            end

            try
                name = completeState.Name;
            catch err
                error(message('parallel:settings:ConfigurationsLoadMissingName', filename));
            end

            try
                values = completeState.Values;
            catch err
                error(message('parallel:settings:ConfigurationsLoadMissingValues', filename));
            end

            % VersionNumber was introduced into the .mat file in R2009b, so it may not exist as
            % a field yet.
            if isfield(completeState, 'VersionNumber')
                versionNumber = completeState.VersionNumber;
            else
                % The configuration struct had no version associated with it - i.e. it was created
                % before R2009b (which was when the version was included).  We assume that the 
                % configuration came from R2009a (version 9).
                versionNumber = 9;
            end

            currVersionNumber = parallel.internal.version.Version.Current.Num;
            try
                if versionNumber ~= currVersionNumber
                    values = ConfigurationsUtils.upgradeConfigStruct(values, versionNumber);
                end
            catch err
                ex = MException(message('parallel:settings:ConfigurationsLoadFailedUpgrade', filename));
                ex = ex.addCause(err);
                throw(ex);
            end
        end
        
        function valuesStruct = upgradeConfigStruct(valuesStruct, originalVersionNum)
            import parallel.internal.version.Version
            
            currentVersionNum = Version.Current.Num;
            if originalVersionNum == currentVersionNum
                % Version of the configuration matches the current, so return early.
                return;
            end

            currentVersionString = Version.Current.String;
            originalVersionString = Version.fromNum(originalVersionNum).String;
            if isempty(originalVersionString)
                % Couldn't determine the corresponding version string of the original version number,
                % so warn and return.  This is most likely to occur if the version number is too large 
                % i.e. we are trying to "upgrade" a newer version to an older version.
                warning(message('parallel:settings:ConfigurationsUpgradeUnknownVersionNumber', currentVersionString, originalVersionNum));
                return;
            end

            if originalVersionNum > currentVersionNum
                % Don't know how to deal with configurations from a higher version, so warn and return.
                % Very unlikely to get in here as we will probably have already failed to convert 
                % originalVersionNum into a string in the block above.
                warning(message('parallel:settings:ConfigurationsUpgradeNewerVersion', currentVersionString, originalVersionString));
                return;
            end

            try
                % Perform the upgrades incrementally
                for i = originalVersionNum+1:currentVersionNum
                    valuesStruct = iUpgradeStructToNextVersion(valuesStruct, i); 
                end
            catch err
                ex = MException(message('parallel:settings:ConfigurationsStructUpgradeFailed', originalVersionString, currentVersionString));
                ex = ex.addCause(err);
                throw(ex);
            end
        end
    end
end


%---------------------------------------------------------------
function iCheckConfigurationFilename(filename)
validateattributes(filename, {'char'}, {'row'});
[~, ~, ext] = fileparts(filename);
if ~strcmpi(ext, '.mat')
    error(message('parallel:settings:ProfilesConfigCreateFromFileNotMat'));
end

if exist(filename, 'file') ~= 2
    error(message('parallel:settings:ConfigurationsCreateNewFileDoesNotExist', filename));
end
end

%---------------------------------------------------------------
function valuesStruct = iUpgradeStructToNextVersion(valuesStruct, nextVersionNumber)
% Upgrade the value struct to the nextVersionNumber.  The assumption is that upgrades
% will be performed incrementally i.e. to upgrade from v1 to v4, we upgrade from 
% v1 to v2 to v3 to v4.

switch nextVersionNumber
    case 10 % R2009b, V4.2
        valuesStruct = iUpgradeCCSSchedulerType(valuesStruct);
    otherwise
        % No upgrading required
end
end

%---------------------------------------------------------------
function valuesStruct = iUpgradeCCSSchedulerType(valuesStruct)
% Upgrade the deprecated 'CCS' scheduler type to 'HPCSERVER'
persistent haveDisplayedMessageAboutDeprecatedCCSType

% Need to check that valuesStruct.findResource.Type is a valid
% field in the struct because the local configuration can 
% sometimes exist but have an empty values struct
if ~isfield(valuesStruct, 'findResource')
    return;
end
if ~isfield(valuesStruct.findResource, 'Type')
    return;
end

if ~strcmpi(valuesStruct.findResource.Type, 'ccs')
    return;
end

% We want to display this message about the deprecated CCS type only once
if isempty(haveDisplayedMessageAboutDeprecatedCCSType)
    haveDisplayedMessageAboutDeprecatedCCSType = true;

    fprintf(['Found a CCS scheduler configuration from a previous version \n' ...
        'of Parallel Computing Toolbox.  The CCS scheduler type will be \n' ...
        'removed in a future version.   Please use the HPCSERVER scheduler \n' ...
        'type instead.  Your existing CCS parallel configurations will be \n' ...
        'updated automatically.\n']);
end

% Change the type to hpcserver instead
valuesStruct.findResource.Type = 'hpcserver';
end
