%AdditionalProperties Store user defined properties and values
%   AdditionalProperties is a data structure whose properties can by dynamically
%   added and removed. The values of these properties is restricted to be of
%   type character vector, cell array of character vectors, logical scalar, or
%   numerical scalar.
%
%   To create a new property or modify an existing property use dot notation.
%
%   Example:
%   % Add the property named 'MyProp' with value 'val'
%   myCluster = parcluster('GenericProfile');
%   myCluster.AdditionalProperties.MyProp = 'val';
%
%   % Change the value of 'MyProp' to the new value 'new'
%   myCluster.AdditionalProperties.MyProp = 'new';
%
%   % List all additional properties
%   definedProperties = properties(myCluster.AdditionalProperties);
%
%   % Remove the 'MyProp' property
%   remove(myCluster.AdditionalProperties, 'MyProp');
%
%   parallel.cluster.AdditionalProperties methods:
%      remove             - Remove an existing property

% Copyright 2016-2023 The MathWorks, Inc.

classdef AdditionalProperties < dynamicprops & matlab.mixin.CustomDisplay

    events (Hidden, ListenAccess = public, NotifyAccess = private)
        Modified
    end
    
    properties (Hidden, Constant)
        ModifiedEventName = 'Modified'
    end
    
    methods
        function remove(obj, fields)
            %REMOVE Remove an existing property
            %    REMOVE(FIELD) Removes the property, FIELD, if it already
            %    exists.
            %
            %    Example:
            %    myCluster = parcluster('GenericProfile');
            %    % Add the property 'MyProp' with value 'val'
            %    myCluster.AdditionalProperties.MyProp = 'val';
            %    % Remove the property named 'MyProp'.
            %    remove(myCluster.AdditionalProperties, 'MyProp');
            arguments
                obj
                fields {mustBeNonzeroLengthText}
            end
            fields = convertCharsToStrings(fields);

            for field = fields(:)'
                if isprop(obj, field)
                    ad = findprop(obj, field);
                    delete(ad);
                end
            end
        end
        
        function obj = subsasgn(obj, s, value)
            %SUBSASGN Subscripted assignment to an AdditionalProperties
            %object
            %
            %   A.PROPERTYNAME = P assigns the value P to the property
            %   PROPERTYNAME of the AdditionalProperties object A. If the
            %   property does not already exist it is created.
            %
            %   A(I,J) = B assigns an element of an AdditionalProperties 
            %   array.
            %
            %   A(I,J).PROPERTYNAME = P assigns the value P to the property
            %   PROPERTYNAME for an element of an AdditionalProperties
            %   array.
            
            switch s(1).type
                case '.'
                    if length(s) == 1
                        % Implement obj.PropertyName = value;
                        prop = s(1).subs;
                        obj.setProperty(prop, value);
                        return;
                    end
                case '()'
                    if length(s) == 1 && isa(value, 'parallel.cluster.AdditionalProperties')
                        % Implement obj(indices) = value;
                        obj(s(1).subs{:}) = value;
                        return;
                    elseif length(s) == 2 && strcmp(s(2).type, '.')
                        % Implement obj(indices).PropertyName = value;
                        prop = s(2).subs;
                        obj(s(1).subs{:}).setProperty(prop, value);
                        return;
                    end
            end
            error(message('parallel:cluster:AdditionalPropertiesUnsupportedIndexing'));
        end

        function tf = isequal(obj1, obj2)
            tf = isequal(obj1.getAsStruct, obj2.getAsStruct);
        end
    end

    methods (Hidden)
        function output = getAsStruct(obj)
            % Load the data into a struct
            props = properties(obj);
            props = sort(props);
            vals = cellfun(@(property) obj.(property), props, ...
                'UniformOutput', false);
            output = cell2struct(vals, props, 1);
        end
    end 

    methods (Hidden, Static)
        function obj = createFromStruct(structData)
            obj = parallel.cluster.AdditionalProperties();
            obj.setDataFromStruct(structData);
        end
    end
    
    methods (Access = protected)        
        function setProperty(obj, name, value)
            value = convertStringsToChars(value);
            if iscellstr(value) && iscolumn(value)
                value = value.'; % make row
            end
            try
                iValidateSupportedValue(name, value);
                if ~isprop(obj, name)
                    % If property does not already exist we add it
                    obj.addProperty(name);
                end
            catch err
                % Strip error stack from message
                throw(err);
            end
            obj.(name) = value;
            notify(obj, obj.ModifiedEventName);
        end
    end
    
    methods (Access = private)
        function addProperty(obj, name)
            obj.validateFieldName(name);
            p = obj.addprop(name);
            p.Access = 'public';
        end
        
        function setDataFromStruct(obj, structData)
            paramNames  = fieldnames(structData);
            paramValues = struct2cell(structData);
            for idx = 1:numel(paramNames)
                obj.setProperty(paramNames{idx}, paramValues{idx});
            end
        end
        
        function validateFieldName(obj, name)
            % Verify user defined name is a valid Matlab variable
            if ~isvarname(name)
                error(message('parallel:cluster:InvalidFieldName', name));
            end
            
            % Verify user defined name is not an existing method
            objMethods = {metaclass(obj).MethodList.Name};
            if ismember(name, objMethods)
                error(message('parallel:cluster:AdditionalPropertiesMethodExists', name));
            end
        end
    end
    
    methods (Hidden, Sealed)
        function link = hGetLink(obj, displayValue)
            serializedMemento = serialize(parallel.internal.display.AdditionalPropertiesMemento(obj));
            matlabFunction = sprintf('%s(''%s'')', ...
                parallel.internal.display.Displayer.DisplayObjectFunction, serializedMemento);
            link = parallel.internal.display.HTMLDisplayType(displayValue, matlabFunction);
        end
        
        function val = formatDispatcher(obj, displayHelper, valDisplayLength, formatter)
            % Format objects of this type for display
            val = formatter(displayHelper, obj, valDisplayLength);
        end
    end
    
    methods (Access = protected)
        % Customize order of property display to match order sent to java
        function propgrp = getPropertyGroups(obj)
            props = properties(obj);
            [~, order] = sort(props);
            props = props(order);
            propgrp = matlab.mixin.util.PropertyGroup(props);
        end
    end
end

function iValidateSupportedValue(name, value)
% Verify user defined value is a supported Matlab type
    parallel.internal.types.AdditionalPropertyTypes.fromValue(name, value);
end
