function settings = gpurng(arg1,arg2)
%GPURNG Control the random number generator used on the GPU.
%   GPURNG(SD) seeds the random number generator using the non-negative
%   integer SD so that RAND, RANDI, and RANDN produce a predictable
%   sequence of numbers.
%
%   GPURNG("shuffle") seeds the random number generator based on the
%   current time so that RAND, RANDI, and RANDN produce a different
%   sequence of numbers after each time you call GPURNG.
%
%   GPURNG(SD,GENERATOR) and GPURNG("shuffle",GENERATOR) additionally
%   specify the type of the random number generator used by RAND, RANDI,
%   and RANDN. GENERATOR is one of:
%
%       Generator              Description
%      ------------------------------------------------------------------
%      "combRecursive"         Combined Multiple Recursive
%      "philox"                Philox 4x32 with 10 rounds
%      "threefry"              Threefry 4x64 with 20 rounds
%
%   GPURNG("default") puts the settings of the random number generator used
%   by RAND, RANDI, and RANDN to their default values so that they produce
%   the same random numbers as if you restarted MATLAB. In this release,
%   the default settings are the Combined Multiple Recursive Generator with
%   seed 0.
%
%   S = GPURNG returns the current settings of the random number generator
%   used by RAND, RANDI, and RANDN. The settings are returned in a
%   structure S with fields 'Type', 'Seed', and 'State'.
%    
%   GPURNG(S) restores the settings of the random number generator used by
%   RAND, RANDI, and RANDN back to the values captured previously by
%   S = GPURNG.
%
%   S = GPURNG(...) additionally returns the previous settings of the
%   random number generator used by RAND, RANDI, and RANDN before changing
%   the seed, generator type or the settings.
%
%      Example:
%         s = gpurng             % get the current generator settings
%         x = gpuArray.rand(1,5) % RAND generates some values
%         gpurng(s)              % restore the generator settings
%         y = gpuArray.rand(1,5) % generate the same values so x and y are equal
%
%      Example:
%         gpurng(1234)      % Seed the generator
%         gpurng("shuffle") % Seed the generator using the current time
%
%      Example:
%         rng( gpurng() ) % Set the MATLAB generator settings to be the same as the MATLAB GPU generator
%
%   See also gpuArray.rand, 
%            gpuArray.randi, 
%            gpuArray.randn, 
%            parallel.gpu.RandStream.


%   See <a href="https://www.mathworks.com/access/helpdesk/help/techdoc/math/brn4ixh.html#brvku_2">Choosing a Random Number Generator</a> for details on these generators.

%   Copyright 2010-2018 The MathWorks, Inc.

if nargin == 0 || nargout > 0
    % With no inputs, settings will be returned even when there's no outputs
    s = parallel.gpu.RandStream.getGlobalStream();
    settings = struct('Type',RandStream.compatName(s.Type),'Seed',s.Seed,'State',{s.State});
end

if nargin > 0
    if isstruct(arg1) && isscalar(arg1)
        inSettings = arg1;
        if nargin > 1
            error(message('MATLAB:rng:maxrhs'));
        elseif ~isempty(setxor(fieldnames(inSettings),{'Type','Seed','State'}))
            throw(badSettingsException);
        end
        % Create a new stream as specified, then set its state
        try
            s = parallel.gpu.RandStream(inSettings.Type,'Seed',inSettings.Seed);
            s.State = inSettings.State;
        catch me
            % Explicitly trap a valid generator that the GPU doesn't
            % support so that we throw the correct error. All other errors
            % do the same as built-in RNG.
            if strcmp(me.identifier,'parallel:gpu:randstream:CreateUnknownRNGType')
                throw(me);
            else
                throw(badSettingsException);
            end
        end
        parallel.gpu.RandStream.setGlobalStream(s);
    else
        if isnumeric(arg1) && isscalar(arg1) % rng(seed) or rng(seed,gentype)
            seed = arg1;
            if nargin == 1
                gentype = getCurrentType();
            else
                gentype = arg2;
                if ~isScalarText(gentype)
                    error(message('MATLAB:rng:badSecondOpt'));
                elseif strcmpi(gentype,'legacy')
                    errorLegacyGenType();
                end
            end
        elseif isScalarText(arg1)
            if strcmpi(arg1,"shuffle") % rng('shuffle') or rng('shuffle',gentype)
                seed = RandStream.shuffleSeed();
                if nargin == 1
                    gentype = getCurrentType();
                else
                    gentype = arg2;
                    if ~isScalarText(gentype)
                        error(message('MATLAB:rng:badSecondOpt'));
                    elseif strcmpi(gentype,'legacy')
                        errorLegacyGenType();
                    end
                end
            elseif strcmpi(arg1,"default") % rng("default")
                if nargin > 1
                    error(message('MATLAB:rng:maxrhs'));
                end
                seed = 0;
                gentype = parallel.gpu.RandStream.DefaultGenerator;
            else % possibly rng(gentype) or rng(gentype,seed)
                error(message('parallel:gpu:rng:BadFirstOpt'));
            end
        else
            error(message('parallel:gpu:rng:BadFirstOpt'));
        end
        
        % Create a new stream using the specified seed
        try
            s = parallel.gpu.RandStream(gentype,'Seed',seed); % RandStream handles the compatibility names
        catch me
            if strcmp(me.identifier,'MATLAB:RandStream:create:UnknownRNGType')
                error(message('MATLAB:rng:unknownRNGType',gentype));
            elseif strcmp(me.identifier,'MATLAB:RandStream:BadSeed')
                error(message('MATLAB:rng:badSeed'));
            else
                throw(me);
            end
        end
        parallel.gpu.RandStream.setGlobalStream(s);
    end
end


function e = badSettingsException
e = MException(message('parallel:gpu:rng:BadSettings'));


function gentype = getCurrentType()
curr = parallel.gpu.RandStream.getGlobalStream();
gentype = curr.Type;


function errorLegacyGenType
% Disallow specifying 'legacy' as a generator type, even with a zero seed
suggestion = RandStream.compatName(RandStream.DefaultStartupType);
throwAsCaller(MException(message('parallel:gpu:rng:CreateLegacy',suggestion)));

function tf = isScalarText(arg)
% Check that an input is a char vector or a scalar string
tf = ischar(arg) || (isstring(arg) && isscalar(arg));
