% This class acts as map which contains all property values for a Cluster
% object. It stores the "modification level" for each property, thereby
% supporting the Cluster.Modified flag.

% Copyright 2011-2020 The MathWorks, Inc.

classdef ( Hidden, Sealed ) ClusterProps < handle

    properties ( GetAccess = private, SetAccess = immutable )
        PropMap   % Map property name to value
        PropLevel % Map property name to ValueType
    end

    properties ( Dependent, SetAccess = private )
        UserModified % Is any value >= ValueType.User
    end
    methods
        function tf = get.UserModified( obj )
        % Modified if any value is ValueType.User
            tf = any( cellfun( ...
                @(x) isequal( x, parallel.internal.settings.ValueType.User ), ...
                obj.PropLevel.values() ) );
        end
    end

    methods ( Access = private )
        function checkPropertyExists( obj, name )
            validateattributes( name, {'char'}, {'row'} );
            assert( obj.PropMap.isKey( name ) && obj.PropLevel.isKey( name ) );
        end
        function checkPropertyDoesNotExist( obj, name )
            validateattributes( name, {'char'}, {'row'} );
            assert( ~obj.PropMap.isKey( name ) && ~obj.PropLevel.isKey( name ) );
        end
    end
    methods
        function obj = ClusterProps( names, factoryVals )
        % Specify a list of property names and factory values.

            obj.PropMap   = containers.Map( 'KeyType', 'char', 'ValueType', 'any' );
            obj.PropLevel = containers.Map( 'KeyType', 'char', 'ValueType', 'any' );
            if nargin > 0
                assert( iscellstr( names ) || isstring( names ) && ...
                        iscell( factoryVals ) && ...
                        numel( names ) == numel( factoryVals ) );
                for ii = 1:length( names )
                    obj.addFactoryValue( names{ii}, factoryVals{ii} );
                end
            end
        end

        function tf = isProperty( obj, name )
        % Is the named property valid for this object?
            tf = obj.PropMap.isKey( name );
        end

        function setValue( obj, name, valuetype, value )
        % set a single value with specified ValueType.

            validateattributes( valuetype, ...
                                {'parallel.internal.settings.ValueType'}, ...
                                {'scalar'} );
            obj.checkPropertyExists( name );

            obj.PropLevel( name ) = valuetype;
            obj.PropMap( name )   = value;
        end

        function setValueAtSameLevel( obj, name, value )
        % Set a new value without modifying the level
            obj.checkPropertyExists( name );
            obj.PropMap( name ) = value;
        end

        function setValueType( obj, name, valuetype )
        % Overwrite "ValueType"
            validateattributes( valuetype, ...
                                {'parallel.internal.settings.ValueType'}, ...
                                {'scalar'} );
            obj.checkPropertyExists( name );
            obj.PropLevel( name ) = valuetype;
        end

        function addFactoryValue( obj, name, factoryVal )
        % Add a new "factory" value
            import parallel.internal.settings.ValueType;
            obj.checkPropertyDoesNotExist( name );
            obj.PropMap( name )   = factoryVal;
            obj.PropLevel( name ) = ValueType.Factory;
        end

        function setProfileValue( obj, name, val )
        % Set a value from a profile
            import parallel.internal.settings.ValueType;
            obj.checkPropertyExists( name );
            obj.PropMap( name )   = val;
            obj.PropLevel( name ) = ValueType.Profile;
        end

        function setUserValue( obj, name, val )
            import parallel.internal.settings.ValueType;
            obj.checkPropertyExists( name );
            obj.PropMap( name )   = val;
            obj.PropLevel( name ) = ValueType.User;
        end
        
        function setUserModified( obj, name )
            import parallel.internal.settings.ValueType;
            obj.checkPropertyExists( name );
            obj.PropLevel( name ) = ValueType.User;
        end

        function v = getValue( obj, name )
            obj.checkPropertyExists( name );
            v = obj.PropMap( name );
        end


        function [names, vals] = getProfileProperties( obj )
        % Return the names and values of all properties that are in the "Profile" state
            import parallel.internal.settings.ValueType;
            [names, vals] = obj.getPropertiesForLevel( ValueType.Profile );
        end
        
        function [names, vals] = getUserProperties( obj )
        % Return the names and values of all properties that are in "User" state.
            import parallel.internal.settings.ValueType;
            [names, vals] = obj.getPropertiesForLevel( ValueType.User );
        end

        function setNonFactoryAsProfile( obj )
        % Set all non-factory values to be profile values
            import parallel.internal.settings.ValueType;
            allNames   = obj.PropLevel.keys();
            states     = obj.PropLevel.values();
            nonFactory = ~cellfun( @(x) x == ValueType.Factory, states );
            names      = allNames( nonFactory );
            vals       = cellfun( @(x) obj.PropMap(x), names, 'UniformOutput', false );

            cellfun( @(x, y) obj.setProfileValue( x, y ), names, vals );
        end
    end

    methods ( Access = private )
        function [names, vals] = getPropertiesForLevel( obj, valueType )
            import parallel.internal.settings.ValueType;
            allNames      = obj.PropLevel.keys();
            states        = obj.PropLevel.values();
            isCorrectType = cellfun( @(x) x == valueType, states );
            names         = allNames( isCorrectType );
            vals          = cellfun( @(x) obj.PropMap(x), names, 'UniformOutput', false );
        end
    end
end
