function ctrl = control(name, varargin)
%CONTROL Create a persistence service controller.
%
%   CTRL = mps.cache.control(CONNECTION, PROVIDER) creates a persistence
%   service controller that links persistence service PROVIDER to the
%   current MATLAB session using connection CONNECTION. Returns a
%   mps.cache.Controller object.
%
%   The controller must be started and attached to the MATLAB session
%   before it can be used. 
%
%   mps.cache.control cannot be deployed to a MATLAB Production Server
%   instance or using MATLAB Compiler or MATLAB Compiler SDK. It can only
%   be used in interactive MATLAB sessions.
%
%   Examples:
%
%   Use the MatlabTest persistence service to test cache-enabled code in a
%   MATLAB session:
%
%       ctrl = mps.cache.control('DataStorage','MatlabTest');
%       start(ctrl);
%
%   Use the Redis persistence service to test cache-enabled code in a 
%   MATLAB session (requires MATLAB Compiler SDK). Start Redis on 
%   localhost at port 8735:
%   
%       ctrl = mps.cache.control('DataStorage','Redis','Port',8735);
%       start(ctrl);
%
%   Stop an active persistence service: 
% 
%       stop(ctrl);
%
%   Depending on the configuration of the persistence service, cached 
%   data may be either deleted or preserved.
% 
%   See Also connect, DataCache, Connection, Controller, mps.sync.mutex

% Copyright 2020 The MathWorks, Inc.

    % Check for optional Provider argument. If present, insert
    % provider-specific defaults, where necessary.
    if nargin > 1
        provider = varargin{1};
        try
            % Insert default arguments for providers we know about.
            % Calls provider-specific local functions. Errors if
            % provider is unknown or we don't have any defaults to
            % insert. Catch errors and ignore those caused by missing
            % functions -- those occur by design. (This allows us to
            % add default insertion for a provider simply by adding a
            % local function, and avoids writing a switch statement.)
            %
            varargin = insertProviderFieldDefaults(provider,varargin);
            
        catch me
            % Ignore "no such function" errors. Report all others.
            if ~(strcmp(me.identifier, 'MATLAB:UndefinedFunction') || ...
                 strcmp(me.identifier, 'MATLAB:feval:invalidFunctionName'))
                rethrow(me);
            end
        end
    end
    
    % Currently, there's only one kind of Controller. We expect more in the
    % future.
    try
        ctrl = mps.cache.Controller(name, varargin{:});
    catch me
        error(me.identifier, me.message);
    end
end

function argList = insertProviderFieldDefaults(provider, argList)
    r = mps.cache.ProviderRegistry.getInstance();
    schema = r.ProviderSchema;
    p = r.get(provider);
    % Do we know about any Service fields for this provider?
    if isfield(p,'ServiceFields')
        fields = strtrim(split(p.ServiceFields,','));
        for k=1:numel(fields)
            f = fields{k};
            % Does the argument list already specify a value for this
            % field?
            pos = findNamedParam(f,argList);
            if isempty(pos)
                % If we have no specified value in the argument list, check
                % the schema to see if there's a default.
                if isfield(schema,f)
                    fSchema = schema.(f);
                    if isstruct(fSchema) && isfield(fSchema,'default')
                        % Found a default; call MATLAB function to
                        % determine the value.
                        expr = strtrim(split(fSchema.default, ','));
                        str = expr{1};
                        if str(1) == '@'
                            fcn = str(2:end);  % Chop off @
                            args = {};
                            if numel(expr) > 1
                                args = expr(2:end);
                            end
                            default = feval(fcn, args{:});
                        else
                            default = fcn;
                        end
                        % Cast the default value, if necessary.
                        default = castToType(default, f, fSchema);
                        
                        % Add the new default to the returned argument
                        % list.
                        argList = [ argList, { f, default } ];  %#ok
                    end
                end
            end
        end
    end
    
end

function pos = findNamedParam(name,paramList)
% Return location in paramList of string name.

    % strcmpi is extremely robust with respect to input argument type.
    % If any of the inputs is not a string, it simply returns false.
    % strcmpi(@sin,17) is false, for example, rather than an error.
    found = cellfun(@(p)strcmpi(name,p),paramList);
    pos = find(found);
end
