% SchedulerComponent - Base class for all scheduler components
 
%   Copyright 2011-2020 The MathWorks, Inc.

% NB This class is hidden for now as the Profiles API is not yet documented

classdef (Hidden) SchedulerComponent < parallel.internal.settings.NamedNode & ...
                                                 matlab.mixin.Heterogeneous
    properties (Hidden, SetAccess = immutable)
        % The scheduler component type
        TypeEnum
    end
    
    properties (Dependent, SetAccess = immutable)
        Type
    end

    properties (PCTGetSet, Transient, PCTConstraint = 'positivenonzerofiniteintscalar')
        NumThreads
    end
    
    methods
        function type = get.Type(obj)
            type = obj.TypeEnum.Name;
        end
    end

    methods (Access = protected)
        function obj = SchedulerComponent(name, typeEnum, settingsNode, parentSettingsNode, varargin)
            validateattributes(typeEnum, {'parallel.internal.types.SchedulerType'}, {'scalar'});
            
            obj@parallel.internal.settings.NamedNode(name, settingsNode, parentSettingsNode, varargin{:});
            obj.TypeEnum = typeEnum;
        end
    end

    methods (Sealed) % Sealed methods to allow operations on arrays of scheduler component
        function delete(objOrObjs, varargin)
            % NB delete must define more than 1 arg to distinguish it from 
            % "normal" handle delete
            % Provide a sealed overload of delete so arrays of scheduler
            % component can be deleted
            delete@parallel.internal.settings.NamedNode(objOrObjs, varargin{:});
        end
        
        function tf = ne(obj1, obj2)
            % Provide sealed overload of ne for setdiff support
            tf = ne@handle(obj1, obj2);
        end
        
        function tf = eq(obj1, obj2)
            % Provide sealed overload of eq for setdiff support
            tf = eq@handle(obj1, obj2);
        end
        
        function export(objOrObjs, filename)
        % export(obj, filename) - export the collapsed values to the 
        % specified file.
            export@parallel.internal.settings.NamedNode(objOrObjs, filename);
        end
        
        function tf = isequal(objOrObjs1, objOrObjs2)
        % Provide sealed overload of isequal
            tf = isequal@parallel.internal.settings.NamedNode(objOrObjs1, objOrObjs2);
        end
    end
    
    methods (Hidden)
        function [p, v] = hGetClusterPVPairs(obj)
        % [p, v] = hGetClusterPVPairs(obj) - Get the PV pairs that
        % are relevant to the cluster object.
            % Default is that all gettable Settings props are cluster props
            gettableProps = obj.hGetPublicSettingsProperties();
            % Remove the Alias key from the list
            gettableProps = setdiff(gettableProps, {parallel.internal.settings.NamedNode.AliasKeyName});
            [p, v] = obj.getCollapsedPropertiesNoDefaultValues(gettableProps);
        end
        
        function [p, v] = hGetJobPVPairs(~, resolveWithProps, resolveWithVals)
        % hGetJobPVPairs - Get the job PV pairs from the scheduler component, 
        % resolving any potential conflicts with the specified properties.
        % Returns a combined array of all of the resolved job props.
        
            % Default is no job PV Pairs on a scheduler component, so 
            % just return the ones with which we should have resolved
            p = resolveWithProps;
            v = resolveWithVals;
        end
        
        function [p, v] = hGetTaskPVPairs(~, resolveWithProps, resolveWithVals)
        % hGetTaskPVPairs - Get the task PV pairs from the scheduler component, 
        % resolving any potential conflicts with the specified properties.
        % Returns a combined array of all of the resolved task props.

            % Default is no task PV Pairs on a scheduler component, so 
            % just return the ones with which we should have resolved
            p = resolveWithProps;
            v = resolveWithVals;
        end
        
        function hSetFromCluster(obj, names, values)
        % hSetFromCluster(obj, names, values) - Set a cluster's names
        % and values on this scheduler component.
            validateattributes(names, {'cell'}, {});
            validateattributes(values, {'cell'}, {});
            
            settableProps = obj.hGetPublicSettingsProperties();
            [~, propsToSetIdx] = intersect(names, settableProps);
            obj.set(names(propsToSetIdx), values(propsToSetIdx));
        end

        function hSetFromClusterIfNotAlreadySet(obj, names, values)
        % hSetFromClusterIfNotAlreadySet(obj, names, values) - Set a 
        % cluster's names and values on this scheduler component only
        % if those names do not already have the supplied values at
        % the collapsed level
            validateattributes(names, {'cell'}, {});
            validateattributes(values, {'cell'}, {});
            
            settableProps = obj.hGetPublicSettingsProperties();
            [~, propsToSetIdx] = intersect(names, settableProps);
            namesToSet = names(propsToSetIdx);
            valsToSet  = values(propsToSetIdx);
            
            if isempty(valsToSet)
                return;
            end
            
            existingVals = obj.get(namesToSet);
            needsSet     = ~cellfun(@isequal, existingVals, valsToSet);
            obj.set(namesToSet(needsSet), valsToSet(needsSet));
        end
    end

    methods (Hidden)
        function tf = hIsClusterAlreadyDiscovered(obj, names, values)
        % hIsClusterAlreadyDiscovered(obj, names, values) - returns true
        % if the supplied names and values correspond to the same cluster
        % as this one in the context of cluster discovery.  Note that the
        % names that are supplied should be "cluster names", not 
        % "scheduler component names".
            tf = obj.isAlreadyDiscovered(names, values);
        end
    end

    methods (Access = protected) % overrides of NamedNode Abstract methods
        function errorIfInvalidName(~, newName)
            p = parallel.Settings;
            if p.hSchedulerComponentExists(newName)
                error(message('parallel:settings:SchedulerComponentNameAlreadyExists', ...
                    newName));
            end
        end
    end
    
    methods (Abstract, Access = protected)
        tf = isAlreadyDiscovered(obj, names, values)
    end
    
    methods (Access = protected)
        function vals = extractValuesFromNamesValues(~, desiredNames, names, values)
        % Extract the values for the desiredNames out of the supplied names and values.
            returnCell = true;
            if ~iscell(desiredNames)
                desiredNames = {desiredNames};
                returnCell = false;
            end
            
            [~, namesIdx, desiredIdx] = intersect(names, desiredNames);
            vals = cell(numel(desiredNames), 1); 
            vals(desiredIdx) = values(namesIdx);
            
            if ~returnCell
                vals = vals{1};
            end
        end

        function tf = isEquivalentHost(obj, otherHost)
            % Can't determine if the host is equivalent if our
            % host is actually "Use Default", so return false
            if parallel.settings.DefaultValue.isDefault(obj.Host)
                tf = false;
                return
            end

            % Return early if the 2 host strings are the same
            tf = strcmpi(obj.Host, otherHost);
            if tf
                return
            end
            
            % See if the 2 hosts actually correspond to the same host
            % (they may be specified in different ways e.g. IP address, 
            % short hostname etc).  If we can't get the host address
            % from either, then just stick with the value obtained
            % from comparing the raw strings.
            thisHostAddress = iGetHostAddressFromHost(obj.Host);
            if isempty(thisHostAddress)
                return
            end
            
            otherHostAddress = iGetHostAddressFromHost(otherHost);
            if isempty(otherHostAddress)
                return
            end

            tf = strcmp(thisHostAddress, otherHostAddress);
        end    
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Static, sealed protected methods
    methods (Access = protected, Static, Sealed)
        function obj = getDefaultScalarElement() %#ok<STOUT>
            % getDefaultScalarElement is required for matlab.mixin.Heterogeneous
            % to ensure that gaps in arrays are filled in with valid objects.
            error(message('parallel:cluster:ArrayWithGapsNotAllowed', 'parallel.SchedulerComponent'));
        end
    end
end

%---------------------------------------------------------
function addr = iGetHostAddressFromHost(host)
persistent hostMap

if isempty(hostMap)
    hostMap = containers.Map();
end

if ~hostMap.isKey(host)
    try
        inetAddr = matlab.net.internal.InetAddress.getByName(host);
        hostAddr = string(inetAddr);
    catch err %#ok<NASGU>
        hostAddr = '';
    end
    hostMap(host) = hostAddr;
end
addr = hostMap(host);
end
