%FutureCreation - resolve Future creation args
%   Instances of this class encapsulate the resolved arguments for
%   creation of a parallel.internal.queue.Future.

% Copyright 2013-2021 The MathWorks, Inc.
classdef (Sealed, Hidden) FutureCreation

    properties (SetAccess = immutable)
        RequestFcn
        NumArgsOut
        ArgsIn
    end

    methods (Access = private)
        function obj = FutureCreation(requestFcn, numArgsOut, argsIn)
        % Must be built by one of the static build methods below.
            obj.RequestFcn = requestFcn;
            obj.NumArgsOut = numArgsOut;
            obj.ArgsIn = argsIn;
        end
    end

    methods (Static, Access = private)
        function argsIn = resolveInputs(isVectorized, argsIn)
        % rationalize input args, ensure in 'vector' form
            ok = true;
            if isVectorized
                if ~all(cellfun(@isvector, argsIn(:)) | cellfun(@isempty, argsIn(:)))
                    ok = false;
                end
            else
                if ~(isvector(argsIn) || isempty(argsIn))
                    ok = false;
                end
                % Convert to a 1 x 1 cell array
                argsIn = {argsIn};
            end
            if ~ok
                error(message('MATLAB:parallel:future:CreateFutureInputArgsCells'));
            end
        end

        function requestFcn = resolveFcn(argsSz, requestFcn)
        % resolve function, return a cell array
            if ~iscell(requestFcn)
                classOk = iOneFunctionOk(requestFcn);
                if prod(argsSz) > 1
                    requestFcn = repmat({requestFcn}, argsSz);
                else
                    requestFcn = {requestFcn};
                end
            else
                if ~isequal(argsSz, size(requestFcn))
                    error(message('MATLAB:parallel:future:CreateFutureFunctionBadSize'));
                end
                classOk = all(cellfun(@iOneFunctionOk, requestFcn));
            end
            if ~classOk
                error(message('MATLAB:parallel:future:CreateFutureFunctionConsistency'))
            end
        end

        function numArgsOut = resolveNumArgsOut(argsInSize, numArgsOut)
        % Ensure that numArgsOut is sized as per argsIn, also check the
        % contents.
            if ~isnumeric(numArgsOut) || isempty(numArgsOut) || ~isreal(numArgsOut) ...
                    || any(round(numArgsOut) ~= numArgsOut, 'all')
                error(message('MATLAB:parallel:future:CreateFutureInvalidNumOutputs'));
            end
            numArgsOut = double(numArgsOut);
            if numel(numArgsOut) == 1
                % Expand
                numArgsOut = repmat(numArgsOut, argsInSize);
            else
                % Need to check that numArgsOut is of the correct size.
                % We issue a different error for non-scalar argsIn because
                % that is the vectorized creation case.
                if isequal(argsInSize, 1)
                    error(message('MATLAB:parallel:future:CreateFutureInvalidNumOutputs'));
                elseif ~isequal(size(numArgsOut), argsInSize)
                    error(message('MATLAB:parallel:future:CreateFutureNumOutputsBadSize'));
                end
            end
            if any(numArgsOut(:) < 0)
                error(message('MATLAB:parallel:future:CreateFutureNegativeNumOutputArgs'));
            end
        end
    end

    methods (Static, Access = {?parallel.internal.queue.JavaFevalQueue, ...
                            ?parallel.internal.queue.CppFevalQueue, ...
                            ?parallel.internal.queue.FevalOnAllFuture, ...
                            ?parallel.internal.queue.CppFevalOnAllFuture})
        function tf = isVectorizedCreation(argsIn)
            tf = ~isempty(argsIn);
            for idx = 1:numel(argsIn)
                if ~iscell(argsIn{idx})
                    tf = false;
                    break
                end
            end
        end
    end
    
    methods (Static, Access = {?parallel.internal.queue.JavaFevalQueue, ?parallel.internal.queue.CppFevalQueue})
        function obj = resolveScalarCreationArgs(requestFcn, numArgsOut, argsIn)
        % resolve arguments when it's known that the call site is definitely
        % attempting to build a scalar Future. (I.e. parfeval(OnAll)).
            import parallel.internal.queue.FutureCreation

            if nargin < 2 || nargin > 3
                error(message('MATLAB:parallel:future:CreateFutureNumArgs'));
            elseif nargin < 3
                argsIn = {};
            end

            isVectorized     = false;
            numTasksToCreate = 1;
            argsIn           = FutureCreation.resolveInputs(isVectorized, argsIn);
            requestFcn       = FutureCreation.resolveFcn(numTasksToCreate, requestFcn);
            numArgsOut       = FutureCreation.resolveNumArgsOut(numTasksToCreate, numArgsOut);
            obj              = FutureCreation(requestFcn, numArgsOut, argsIn);
        end
    end
    methods (Static, Access = ?parallel.internal.queue.Future)
        function obj = resolveArgs(requestFcn, numArgsOut, argsIn, varargin)
        %resolveArgs - check arguments for parallel.internal.queue.Future creation.
        %   - rationalize vectorized request creation
        %   - check all value types.

            import parallel.internal.queue.FutureCreation

            if nargin < 2 || nargin > 3
                error(message('MATLAB:parallel:future:CreateFutureNumArgs'));
            elseif nargin < 3
                argsIn = {};
            end

            if ~iscell(argsIn)
                error(message('MATLAB:parallel:future:CreateFutureInputArgsCells'));
            end
            % Switch 'isVectorized' based solely on input args.
            isVectorized = FutureCreation.isVectorizedCreation(argsIn);
            argsIn       = FutureCreation.resolveInputs(isVectorized, argsIn);
            requestFcn   = FutureCreation.resolveFcn(size(argsIn), requestFcn);
            numArgsOut   = FutureCreation.resolveNumArgsOut(size(argsIn), numArgsOut);
            obj          = FutureCreation(requestFcn, numArgsOut, argsIn);
        end
    end
end

function tf = iOneFunctionOk(fcn)
    tf = isa(fcn, 'function_handle') || ischar(fcn);
end
