function b = repmat(a,varargin)
%MATLAB Code Generation Library Function

% Limitations:
%   1. Size arguments must be fixed-size

%   Copyright 2002-2019 The MathWorks, Inc.
%#codegen
%#ok<*EMCA>

coder.internal.allowHalfInputs;
coder.internal.allowEnumInputs;
narginchk(2,Inf);
coder.internal.prefer_const(a);
coder.internal.prefer_const(varargin);
hasObj = isobject(a) && ~isenum(a) && ~isa(a, 'half');
coder.internal.errorIf(hasObj && ~eml_option('ArraysOfObjects'), ...
                       'Coder:builtins:MCOSNonscalar',class(a));
if hasObj
  b = coder.internal.ObjectArray.c_repmat(a, varargin{:});
  return;
end
coder.internal.errorIf(isa(a,'function_handle'), ...
    'Coder:toolbox:unsupportedClass', ...
    mfilename, class(a));

%% Compute the output size.
coder.internal.assertValidSizeArg(varargin{:});
% We don't need the tile array values, but we will need size(tile,k) and
% for Coder to keep track of size upper bounds.
tile = false(varargin{:});
ONE = coder.internal.indexInt(1);
ZERO = coder.internal.indexInt(0);
ndimsb = eml_max( ...
    coder.internal.indexInt(eml_ndims(a)), ...
    coder.internal.indexInt(eml_ndims(tile)));
outsize = ones(1,ndimsb,coder.internal.indexIntClass);
for k = coder.unroll(ONE:ndimsb)
    % Try hard to keep a constant 0 dimension if one exists
    if (coder.internal.isConst(size(a,k)) && size(a,k) == 0) || ...
            (coder.internal.isConst(size(tile,k)) && size(tile,k) == 0)
        outsize(k) = ZERO;
    else
        outsize(k) = coder.internal.indexInt(size(a,k)) * ...
            coder.internal.indexInt(size(tile,k));
    end
    assert(outsize(k) == size(a,k)*size(tile,k),'MATLAB:pmaxsize');
end

%% Handle Non-cell inputs.
if ~iscell(a)
    if coder.internal.isConst(size(a)) && isscalar(a)
        b = eml_expand(a,outsize);
    elseif coder.const( ...
            eml_ndims(a) == 2 && ~coder.internal.avoidArrayFlattening)
        % Efficient 2D column-oriented algorithm using linear indexing.
        % Note that HDL requires some of the hand-optimization in this
        % algorithm. See g1666135.
        b = coder.nullcopy(eml_expand(coder.internal.scalarEg(a),outsize));
        nrows = coder.internal.indexInt(size(a,1));
        ncols = coder.internal.indexInt(size(a,2));
        ntilerows = coder.internal.indexInt(size(tile,1));
        ntilecols = coder.internal.prodsize(tile,'above',ONE);
        for jtilecol = ONE:ntilecols
            ibtile = (jtilecol - ONE)*(nrows*ntilerows*ncols);
            for jcol = ONE:ncols
                iacol = (jcol - ONE)*nrows;
                ibmat = ibtile + (jcol - ONE)*(nrows*ntilerows);
                for itilerow = ONE:ntilerows
                    ibcol = ibmat + (itilerow - ONE)*nrows;
                    for k = ONE:nrows
                        b(ibcol + k) = a(iacol + k);
                    end
                end
            end
        end
    else
        b = coder.nullcopy(eml_expand(coder.internal.scalarEg(a),outsize));
        if isempty(b)
        elseif coder.isRowMajor
            b = repmatLooper(b,a,tile,ONE);
        else
            b = repmatLooper(b,a,tile,ndimsb);
        end
    end
    return
end

%% Handle cell array inputs.

% g1134060 don't use eml_expand on scalar cells yet
b = coder.nullcopy(cell(outsize));
if ~isempty(coder.target)
    t = a;
    if coder.internal.isHomogeneousCell(t)
        coder.internal.tryMakeHomogeneousCell(b);
    end
end

% Call locDimsAreEmpty rather than isempty(b) to prevent locking b.
if ~isempty(a) && ~locDimsAreEmpty(outsize)
    
    CONSTSIZE = coder.const(coder.internal.isConst(outsize));
    
    % Checks for homogeneity are last since they may have side effects
    if ~CONSTSIZE && ~coder.internal.isHomogeneousCell(a)
        % We have a variable-size output so we must be able to homogenize
        % it. The input a has already been locked at this point. Test that
        % we can homogenize a temporary copy of a to ensure success.
        t = a;
        eml_invariant(coder.internal.tryMakeHomogeneousCell(t), ...
            'Coder:toolbox:HeterogeneousCellConstSize',mfilename, ...
            'IfNotConst','Fail');
    end
    
    % At this point it needs to be fixed size if it heterogenous
    if CONSTSIZE && (coder.target('MATLAB') || ...
            ~coder.internal.isHomogeneousCell(a))
        bNumel = prod(outsize);
        coder.internal.assert(bNumel < eml_option('UnrollLimit'), ...
            'Coder:toolbox:RepmatCellHeterogeneousLimit', ...
            eml_option('UnrollLimit'),bNumel);
        % This is an optimized path for cell-arrays and constant sizes.
        % It works by computing the index vector as a constant for the
        % relationship between B (output) and A (input) and then
        % perform one unrolled loop that copies A elements into B using
        % that index vector.
        aind = coder.const(getindices(bNumel,ndimsb, ...
            coder.internal.indexInt(size(a)), outsize));
        for k = coder.unroll(ONE:bNumel)
            b{k} = a{aind(k)};
        end
    elseif eml_ndims(a) == 2
        % Efficient column-oriented algorithm.
        nrows = coder.internal.indexInt(size(a,1));
        ncols = coder.internal.indexInt(size(a,2));
        ntilerows = coder.internal.indexInt(size(tile,1));
        ntilecols = coder.internal.prodsize(tile,'above',ONE);
        for jtilecol = ONE:ntilecols
            ibtile = (jtilecol - ONE)*(nrows*ntilerows*ncols);
            for jcol = ONE:ncols
                iacol = (jcol - ONE)*nrows;
                ibmat = ibtile + (jcol - ONE)*(nrows*ntilerows);
                for itilerow = ONE:ntilerows
                    ibcol = ibmat + (itilerow - ONE)*nrows;
                    for k = ONE:nrows
                        b{ibcol + k} = a{iacol + k};
                    end
                end
            end
        end
    else
        % N-D to N-D case.
        db = ones(1,ndimsb,coder.internal.indexIntClass);
        da = db;
        for k = ONE:ndimsb-ONE
            da(k+1) = da(k)*coder.internal.indexInt(size(a,k));
            db(k+1) = db(k)*coder.internal.indexInt(size(b,k));
        end
        for ib = ONE:numel(b)
            % Compute ia.  Basically ind2sub on ib, followed by an
            % adjustment to convert to a subscript vector for a,
            % followed by sub2ind to convert to a linear index of a.
            % The difference is that the ind2sub and sub2ind parts are
            % merged so that no temporary subscript vector is needed.
            ia = ONE;
            ibtmp = ib;
            for k = ndimsb:-1:ONE
                vk = ONE + idxrem(ibtmp - 1,db(k));
                iatmp = da(k)*idxrem( ...
                    idxdiv(ibtmp - vk,db(k)), ...
                    coder.internal.indexInt(size(a,k)));
                ia = ia + iatmp;
                ibtmp = vk;
            end
            % Copy appropriate element of a.
            b{ib} = a{ia};
        end
    end
end

%% Helper Functions

%--------------------------------------------------------------------------

function q = idxdiv(a,b)
coder.inline('always');
q = eml_rdivide(a,b,coder.internal.indexIntClass,'floor','spill');

%--------------------------------------------------------------------------

function r = idxrem(a,b)
coder.inline('always');
r = a - idxdiv(a,b)*b;

%--------------------------------------------------------------------------

function p = locDimsAreEmpty(sz)
coder.inline('always');
coder.internal.prefer_const(sz);
for k = coder.unroll(1:coder.internal.indexInt(numel(sz)))
    if sz(k) == 0
        p = true;
        return
    end
end
p = false;

%--------------------------------------------------------------------------

function aind = getindices(bNumel, ndimsb, asize, bsize)
% This function computes the index map for the relationship between
% output and input. Here we assume B is output and A is input, so
% bsize refers to size(B) and asize refers to size(A).
aind = zeros(1, bNumel, coder.internal.indexIntClass);
ONE = coder.internal.indexInt(1);
db = ones(1,ndimsb,coder.internal.indexIntClass);
da = db;
for k = ONE:ndimsb-ONE
    if k <= numel(asize)
        asizek = asize(k);
    else
        asizek = ONE;
    end
    da(k+1) = da(k)*asizek;
    db(k+1) = db(k)*bsize(k);
end
for ib = ONE:bNumel
    % Compute ia.  Basically ind2sub on ib, followed by an adjustment
    % to convert to a subscript vector for a, followed by sub2ind to
    % convert to a linear index of a.  The difference is that the
    % ind2sub and sub2ind parts are merged so that no temporary
    % subscript vector is needed.
    ia = ONE;
    ibtmp = ib;
    for k = ndimsb:-1:ONE
        vk = ONE + idxrem(ibtmp - 1,db(k));
        asizek = ONE;
        if k <= numel(asize)
            asizek = asize(k);
        end
        iatmp = da(k)*idxrem( ...
            idxdiv(ibtmp - vk,db(k)), ...
            coder.internal.indexInt(asizek));
        ia = ia + iatmp;
        ibtmp = vk;
    end
    % Copy appropriate element of a.
    aind(ib) = ia;
end

%--------------------------------------------------------------------------

function b = repmatLooper(b,a,rep,dim,varargin)
% Generate loops for general REPMAT.
% Does not support cell arrays.
coder.internal.allowEnumInputs;
coder.inline('always');
coder.internal.prefer_const(dim);
ND = coder.internal.indexInt(eml_ndims(b));
ZERO = coder.internal.indexInt(0);
ONE = coder.internal.indexInt(1);
if dim >= ONE && dim <= ND
    na = coder.internal.indexInt(size(a,dim));
    nt = coder.internal.indexInt(size(rep,dim));
    for t = ZERO:nt - 1
        offset = t*na;
        for k = ONE:na
            if coder.isRowMajor
                b = repmatLooper(b,a,rep,dim + 1,varargin{:},offset,k);
            else
                b = repmatLooper(b,a,rep,dim - 1,offset,k,varargin{:});
            end
        end
    end
else
    % varargin is {offset1,subsa1,offset2,subsa2,...,offsetND,subsaND}
    subsa = cell(1,ND);
    subsb = cell(1,ND);
    for k = coder.unroll(1:ND)
        subsa{k} = varargin{2*k};
        subsb{k} = varargin{2*k - 1} + varargin{2*k};
    end
    % Here we could theoretically handle cell input with
    %     if iscell(b), b{subsb{:}} = a{subsa{:}}; else, ...
    % but we cannot reach this point with a cell array because this
    % function passes the cell array to recursive calls without assigning
    % all the elements. For now just handle the non-cell case.
    b(subsb{:}) = a(subsa{:});
end

%--------------------------------------------------------------------------
