%ExperimentalVersions - Switches for experimental code which can be
%switched on/off both on the client and any workers

% Copyright 2020-2021 The MathWorks, Inc.

classdef (Hidden) ExperimentalVersions < handle
    
    properties (Constant, Hidden)
        Java = 'Java';
        Spf = 'Spf';
        Cpp = 'Cpp';

        RCAEnvironmentVar = 'PARALLEL_SERVER_EXPERIMENTAL_RCA_VER';
        DefaultRCAVersion = parallel.internal.apishared.ExperimentalVersions.Cpp;
        
        MJSEnvironmentVar = 'PARALLEL_SERVER_EXPERIMENTAL_MJS_VER';
        DefaultMJSVersion = parallel.internal.apishared.ExperimentalVersions.Java;
        
        PLREnvironmentVar = 'PARALLEL_SERVER_EXPERIMENTAL_PLR_VER';
        DefaultPLRVersion = parallel.internal.apishared.ExperimentalVersions.Java;
        
        DecafEnvironmentVar = 'Decaf';
        
        ExperimentalEnvVars = {parallel.internal.apishared.ExperimentalVersions.RCAEnvironmentVar, ...
            parallel.internal.apishared.ExperimentalVersions.MJSEnvironmentVar, ...
            parallel.internal.apishared.ExperimentalVersions.PLREnvironmentVar, ...
            parallel.internal.apishared.ExperimentalVersions.DecafEnvironmentVar};
    end
    
    properties
        RCAVersion = parallel.internal.apishared.ExperimentalVersions.DefaultRCAVersion;
        MJSVersion = parallel.internal.apishared.ExperimentalVersions.DefaultMJSVersion;
        PLRVersion = parallel.internal.apishared.ExperimentalVersions.DefaultPLRVersion;
    end
    
    methods
        function set.RCAVersion(obj, val)
            % Set RCAVersion and corresponding environment variable
            if strcmp(val, obj.DefaultRCAVersion)
                setenv(obj.RCAEnvironmentVar, '');
            else
                validatestring(val, {obj.Java, obj.Cpp});
                setenv(obj.RCAEnvironmentVar, val);
            end
            obj.RCAVersion = val;
        end
        
        function restore = tempSetRCAVersion(obj, val)
            % Helper for temporary switch of version
            currentValue = obj.RCAVersion;
            function doSet(val)
                obj.RCAVersion = val;
            end
            restore = onCleanup(@() doSet(currentValue));
            obj.RCAVersion = val;
        end
        
        function set.MJSVersion(obj, val)
            % Set MJSVersion and corresponding environment variable
            if strcmp(val, obj.DefaultMJSVersion)
                setenv(obj.MJSEnvironmentVar, '');
            else
                validatestring(val, {obj.Java, obj.Spf});
                setenv(obj.MJSEnvironmentVar, val);
            end
            obj.MJSVersion = val;
        end
        
        function restore = tempSetMJSVersion(obj, val)
            % Helper for temporary switch of version
            currentValue = obj.MJSVersion;
            function doSet(val)
                obj.MJSVersion = val;
            end
            restore = onCleanup(@() doSet(currentValue));
            obj.MJSVersion = val;
        end
        
        function set.PLRVersion(obj, val)
            % Set PLRVersion and corresponding environment variable
            if strcmp(val, obj.DefaultPLRVersion)
                setenv(obj.PLREnvironmentVar, '');
            else
                validatestring(val, {obj.Java, obj.Spf});
                setenv(obj.PLREnvironmentVar, val);
            end
            obj.PLRVersion = val;
        end
        
        function restore = tempSetPLRVersion(obj, val)
            % Helper for temporary switch of version
            currentValue = obj.PLRVersion;
            function doSet(val)
                obj.PLRVersion = val;
            end
            restore = onCleanup(@() doSet(currentValue));
            obj.PLRVersion = val;
        end
        
        function load(obj)
            import parallel.internal.apishared.ExperimentalVersions
            
            % If we're in 'Decaf' mode, set all versions to their Java-free
            % implementations
            if strcmpi(getenv(obj.DecafEnvironmentVar), 'true')
                obj.RCAVersion = ExperimentalVersions.Cpp;
                obj.MJSVersion = ExperimentalVersions.Spf;
                obj.PLRVersion = ExperimentalVersions.Spf;
                return
            end
            
            % Load version from corresponding environment variables if set
            rcaVersionEnv = getenv(obj.RCAEnvironmentVar);
            if isempty(rcaVersionEnv)
                obj.RCAVersion = obj.DefaultRCAVersion;
            else
                obj.RCAVersion = rcaVersionEnv;
            end
            
            mjsVersionEnv = getenv(obj.MJSEnvironmentVar);
            if isempty(mjsVersionEnv)
                obj.MJSVersion = obj.DefaultMJSVersion;
            else
                obj.MJSVersion = mjsVersionEnv;
            end
            
            plrVersionEnv = getenv(obj.PLREnvironmentVar);
            if isempty(plrVersionEnv)
                obj.PLRVersion = obj.DefaultPLRVersion;
            else
                obj.PLRVersion = plrVersionEnv;
            end
        end
        
        function tf = requireJavaLogging(obj)
            % Return true if any version switch is backed by Java
            % infrastructure and hence Java logging should be enabled
            tf = any(strcmp({obj.RCAVersion, obj.MJSVersion, obj.PLRVersion}, parallel.internal.apishared.ExperimentalVersions.Java));
        end
        
        function tf = requireCppLogging(obj)
            % Return true if any version switch is backed by C++
            % infrastructure and hence C++ logging should be enabled
            tf = strcmp(obj.RCAVersion, parallel.internal.apishared.ExperimentalVersions.Cpp) || ...
                strcmp(obj.MJSVersion, parallel.internal.apishared.ExperimentalVersions.Spf) || ...
                strcmp(obj.PLRVersion, parallel.internal.apishared.ExperimentalVersions.Spf);
        end
    end

    methods (Static)
        function instance = get()
            persistent INSTANCE
            if isempty(INSTANCE)
                INSTANCE = parallel.internal.apishared.ExperimentalVersions;
            end
            INSTANCE.load();
            
            instance = INSTANCE;
        end
    end
    
    methods (Access = private)
        function obj = ExperimentalVersions()
            % Private constructor for singleton
        end
    end
end