function varargout = create( name, varargin )
%CREATE  Create multiple independent GPU random number streams
%
%   [S1,S2,...] = parallel.gpu.RandStream.create('GENTYPE',...)
%   creates one or more GPU random number streams. The syntax is the same
%   as RandStream.create with the following restrictions:
%    * Only the "combRecursive" (MRG32K3A), "Philox" and "Threefry" 
%      generators are supported.
%    * The "Inversion" normal transform is supported for all generators.
%    * The 'BoxMuller' normal transform is supported only for the
%      "Philox" and "Threefry" generators.
%    * Setting the substream is not allowed.
%
%   Examples:
%
%   % Create three independent streams:
%   [s1,s2,s3] = parallel.gpu.RandStream.create("mrg32k3a","NumStreams",3);
%   r1 = rand(s1,100000,1); 
%   r2 = rand(s2,100000,1); 
%   r3 = rand(s3,100000,1);
%   corrcoef(gather([r1,r2,r3]))
%
%   % Create only one stream from a set of three independent streams, and
%   % designate it as the current global stream:
%   s2 = parallel.gpu.RandStream.create("threefry", ...
%                                       "NumStreams",3,"StreamIndices",2);
%   parallel.gpu.RandStream.setGlobalStream(s2);
%
%   See also randfun/RandStream/create
%            parallel.gpu.RandStream
%            parallel.gpu.RandStream/RandStream
%            parallel.gpu.RandStream/list
%            parallel.gpu.RandStream/rand
%            parallel.gpu.RandStream/randi 
%            parallel.gpu.RandStream/randn 
%            parallel.gpu.RandStream/getGlobalStream
%            parallel.gpu.RandStream/setGlobalStream

%   Copyright 2011-2018 The MathWorks, Inc.

if nargin < 1
    error( message( "MATLAB:RandStream:TooFewInputs" ) );
end
name = convertStringsToChars( name );
if ~ischar( name )
    error( message( "MATLAB:RandStream:create:InvalidRNGType" ) );
end
% For backwards compatibility, treat "-" in the name as "_"
name = strrep(name, "-", "_");
algName = RandStream.algName( name );
if ~any( strcmpi( parallel.gpu.RandStream.ValidGenerators(:,1), algName ) )
    error(message("parallel:gpu:randstream:CreateUnknownRNGType", name));
end

if nargin==1
    % Simple case, just build one
    if nargout > 1
        error( message("MATLAB:RandStream:create:TooManyOutputs") );
    end
    varargout{1} = parallel.gpu.RandStream( algName );
else
    % More inputs, so extract any parameters we're interested in
    [numStreams,streamIndices,cellOutput,otherArgs] = iParseInputArgs( varargin );
    
    % Check that the expected outputs match the parameters
    numOut = iCheckNumOutputs( streamIndices, nargout, cellOutput );
    
    % Use a helper function (below) to create the streams
    streams = iCreateStreams( algName, numOut, streamIndices, numStreams, otherArgs );
    
    % If requested, convert the output to a single cell-array
    if cellOutput
        varargout = {streams};
    else
        varargout = streams;
    end
end
end % create


function [numStreams,streamIndices,cellOutput,otherArgs] = iParseInputArgs( inputArgs )
% Parse the user-supplied inputs and extract the parameters for "create"

% Create a parser for the properties we care about, leaving the rest
parser = inputParser();
parser.FunctionName = "parallel.gpu.RandStream.create";
parser.KeepUnmatched = true;
parser.CaseSensitive = false;

parser.addParameter( "CellOutput", false, ...
    @(x) validateattributes( x, {'logical'}, {'scalar'} ) );
parser.addParameter( "NumStreams", 1, ...
    @(x) validateattributes( x, {'numeric'}, {'scalar','positive','integer'} ) );
parser.addParameter( "StreamIndices", [], ...
    @(x) validateattributes( x, {'numeric'}, {'positive','integer'} ) );

% Parse the inputs!
parser.parse( inputArgs{:} );

% Extract the results
numStreams    = parser.Results.NumStreams;
streamIndices = parser.Results.StreamIndices;
cellOutput    = parser.Results.CellOutput;
otherArgs     = parser.Unmatched;

% If streamIndices was not set, add a default
if isempty( streamIndices )
    streamIndices = 1:numStreams;
else
    % Check that none are out of range
    if any( streamIndices > numStreams )
        error( message( "MATLAB:RandStream:create:BadStreamIndex" ) );
    end
end

% Expand the list of remaining arguments back into a cell array
if isempty( otherArgs )
    otherArgs = {};
else
    % Check that optional arguments are valid property names
    params = fieldnames( otherArgs );
    parallel.gpu.RandStream.validateParamName( params )
    % Convert to cell array
    otherArgs = [params, struct2cell( otherArgs )]';
    otherArgs = otherArgs(:)';
end

end % iParseInputArgs


function numOut = iCheckNumOutputs( streamIndices, numUserOutputs, cellOutput )
% Check that the number of outputs requested matches the number available

% If no output requested, create at least one (i.e. "ans").
if numUserOutputs == 0
    numUserOutputs = 1;
end
% If cell outputs requested we can return any number, otherwise check how
% many the user will be capturing and only create ones that can be used.
if cellOutput
    numOut = numel( streamIndices );
else
    numOut = min( numUserOutputs, numel( streamIndices ) );
end
% Check that the user hasn't asked for more than we've got.
if numUserOutputs > numOut
    if (cellOutput && (numUserOutputs > 1)) || (numUserOutputs > numOut)
        error(message("MATLAB:RandStream:create:TooManyOutputs"));
    end
end
end % iCreateStreams


function streams = iCreateStreams( name, numOut, streamIndices, numStreams, otherArgs )
% Build the random number streams
streams = cell( 1, numOut );
for ii=1:numOut
    streams{ii} = parallel.gpu.RandStream( name, otherArgs{:} );
    % Set read-only parameters afterwards as only public params can be set
    % during construction
    streams{ii}.StreamIndex = streamIndices(ii);
    streams{ii}.NumStreams = numStreams;
end
end % iCreateStreams
