function A = accumarray(subs,val,sz,fun,fillval,IsSparse)
%MATLAB Code Generation Library Function

%   Limitations:
%   1. Requires variable-sizing in most cases.
%   2. When FUN produces a cell array:
%      * The fillval input is required, must be a cell array, and must have
%        a consistent type.
%      * The order of accumulated values may not match MATLAB.
%   3. If supplied, FUN must return a full (non-sparse) scalar value, even when
%      the output A is sparse.
%   4. Does not support enumeration inputs. This may be possible once
%      MATLAB Coder supports empty arrays of enumerations.

%   Copyright 1984-2020 The MathWorks, Inc.
%#codegen

narginchk(2,6);
coder.internal.prefer_const(subs,val);
if nargin >= 3
    coder.internal.prefer_const(sz);
    if nargin >= 4
        coder.internal.prefer_const(fun);
        if nargin >= 5
            coder.internal.prefer_const(fillval);
            if nargin >= 6
                coder.internal.prefer_const(IsSparse);
            end
        end
    end
end
if nargin < 6
    IsSparse = issparse(val);
end
coder.internal.assert(coder.internal.isConst(IsSparse), ...
    'Coder:toolbox:SparseOptionMustBeConstant');
if iscell(subs)
    s = cellSubsToMatrixSubs(subs);
else
    s = subs;
end
assertValidSubsVal(s,val);
if coder.const(nargin < 3 || (coder.internal.isConst(sz) && isequal(sz,[])))
    SZ = computeSizeFromSubs(s);
else
    coder.internal.assert(coder.internal.isConst(size(sz)), ...
        'Coder:toolbox:SizeSZMustBeConstant');
    coder.internal.assert(~issparse(sz) && isrow(sz) && ...
        (size(s,2) >= 2 || (size(sz,2) == 2 && sz(2) == 1)), ...
        'MATLAB:accumarray:badSzInputVecInd');
    if size(s,2) >= 2
        coder.internal.assert((size(sz,2) == size(s,2)), ...
            'MATLAB:accumarray:badSzInputMatInd');
    end
    coder.internal.assertValidSizeArg(sz);
    MINSZ = computeSizeFromSubs(s);
    SZ = coder.internal.indexInt(sz);
    coder.internal.assert(sizeInRange(SZ,MINSZ), ...
        'MATLAB:accumarray:subsValuesTooLarge');
end
if coder.const(nargin < 4 || (coder.internal.isConst(fun) && isequal(fun,[])))
    FUN = @sum;
else
    coder.internal.assert(isa(fun,'function_handle'), ...
        'MATLAB:accumarray:badFunInput');
    FUN = fun;
end
atype = coder.internal.outputScalarEg(FUN,val);
if eml_ambiguous_types && ~iscell(atype)
    A = zeros(SZ);
    return
end
coder.internal.assert(isscalar(atype), ...
    'MATLAB:accumarray:nonscalarFunOutput',func2str(FUN));
coder.internal.assert(isnumeric(atype) || islogical(atype) || ...
    ischar(atype) || iscell(atype), ...
    'MATLAB:accumarray:badFunOutputClass',func2str(FUN));
if coder.const(nargin < 5 || ...
        (coder.internal.isConst(fillval) && isequal(size(fillval),[0,0])))
    coder.internal.assert(~iscell(atype),'Coder:toolbox:FillValueRequired');
    fv = atype;
elseif coder.const(isnumeric(fillval) && isreal(fillval) && ~isreal(atype))
    fv = complex(fillval);
else
    fv = fillval;
end
coder.internal.assert(isa(fv,class(atype)), ...
    'MATLAB:accumarray:inconsistentFillvalAndFunOutput',func2str(FUN));
coder.internal.assert(~issparse(fv) && ...
    coder.internal.isConstTrue(@isscalar,fv), ...
    'MATLAB:accumarray:nonscalarOrSparseFillvalInput');
if iscell(atype)
    coder.internal.infer_or_error(tryfv(fv,atype), ...
        'Coder:toolbox:InconsistentCellArrays');
end
if nargin == 6 || IsSparse
    SEMPTY = coder.const(isequal(IsSparse,[]));
    STRUE = coder.const(isequal(IsSparse,true));
    SFALSE = coder.const(isequal(IsSparse,false));
    coder.internal.assert(SEMPTY || STRUE || SFALSE, ...
        'MATLAB:accumarray:badIsSparseInput');
    if STRUE
        A = AccumArraySparse(s,full(val),SZ,FUN,fv,atype);
        return
    end
    coder.internal.assert(~issparse(val),'MATLAB:accumarray:badClassValInput');
end
A = AccumArrayFull(s,val,SZ,FUN,fv,atype);

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

function A = AccumArrayFull(s,val,sz,fun,fv,atype)
coder.internal.prefer_const(s,val,sz,fun,fv,atype);
if isscalar(val)
    A = AccumArrayScalarVal(s,val(1),sz,fun,fv,atype);
elseif coder.const(coder.internal.isConst(fun) && ...
        (isequal(fun,@min) || isequal(fun,@max) || ...
        (isequal(fun,@sum) && isa(val,'double')) || ...
        (isequal(fun,@prod) && isa(val,'double'))))
    A = AccumArrayBinaryReduction(s,val,sz,fun,fv,atype);
else
    A = AccumArrayNonScalarVal(s,val,sz,fun,fv,atype);
end

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

function A = AccumArraySparse(s,val,sz,fun,fv,atype)
coder.internal.allowEnumInputs;
coder.internal.prefer_const(s,val,sz,fun,fv,atype);
coder.internal.assert(isa(atype,'double'), ...
    'MATLAB:sparsfcn:nondoubleFunctionOutput');
coder.internal.assert(isa(val,'double'), ...
    'MATLAB:accumarray:nondoubleValues');
coder.internal.assert(isa(fv,'double') && isscalar(fv) && fv(1) == 0, ...
    'MATLAB:accumarray:nonzeroFillvalSparseOutput');
coder.internal.assert(size(s,2) <= 2, ...
    'MATLAB:accumarray:nonmatrixSparseOutput');
[ss,~,idx] = unique(s,'rows','stable');
nidx = coder.internal.indexInt(size(ss,1));
Afull = AccumArrayFull(idx,val,[nidx,1],fun,fv,atype);
if size(s,2) == 1
    A = sparse(ss(:,1),1,Afull,sz(1),sz(2));
else
    A = sparse(ss(:,1),ss(:,2),Afull,sz(1),sz(2));
end

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

function A = AccumArrayScalarVal(subs,val,sz,fun,fv,atype)
% Handle the scalar val case more efficiently.
coder.internal.prefer_const(subs,val,sz,fun,fv,atype);
SUMFUN = coder.const( ...
    coder.internal.isConst(isequal(fun,@sum)) && isequal(fun,@sum));
PRODFUN = coder.const( ...
    coder.internal.isConst(isequal(fun,@prod)) && isequal(fun,@prod));
CELLOUT = iscell(atype);
nsubs = coder.internal.indexInt(size(subs,1));
nd = coder.internal.indexInt(size(subs,2));
counts = zeros(sz,coder.internal.indexIntClass);
for k = 1:nsubs
    s = cell(1,nd);
    for j = 1:nd
        s{j} = subs(k,j);
    end
    counts(s{:}) = counts(s{:}) + 1;
end
if CELLOUT
    A = cell(sz);
else
    A = coder.nullcopy(eml_expand(fv,sz));
end
for k = 1:numel(A)
    if counts(k) == 0
        if CELLOUT
            A{k} = fv{1};
        else
            A(k) = fv;
        end
    elseif SUMFUN
        A(k) = double(counts(k))*double(val(1));
    elseif PRODFUN
        A(k) = power(double(val(1)),double(counts(k)));
    elseif CELLOUT
        A{k} = evaluateFunction(fun,repmat(val(1),counts(k),1),fv{1});
    else
        A(k) = evaluateFunction(fun,repmat(val(1),counts(k),1),fv);
    end
end

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

function A = AccumArrayNonScalarVal(subs,val,sz,fun,fv,atype)
coder.internal.prefer_const(subs,val,sz,fun,fv,atype);
coder.varsize('v',[],[1,0]);
% Dynamically create subvectors. Initially allocate as empty columns.
nsubs = coder.internal.indexInt(size(subs,1));
nd = coder.internal.indexInt(size(subs,2));
vtype = coder.internal.scalarEg(val);
v = eml_expand(vtype,[nsubs,1]); %#ok<NASGU>
v = eml_expand(vtype,[0,1]);
vectors = repmat({v},sz);
for k = 1:nsubs
    s = cell(1,nd);
    for j = 1:nd
        s{j} = subs(k,j);
    end
    % This doesn't work:
    % vectors{s{:}} = [vectors{s{:}};val(k)];
    % So apply this kludge:
    idx = sub2ind(sz,s{:});
    vectors{idx} = [vectors{idx};val(k)];
end
if iscell(atype)
    A = cell(sz);
    for k = 1:numel(A)
        if isempty(vectors{k})
            A{k} = fv{1};
        else
            A{k} = evaluateFunction(fun,vectors{k},fv{1});
        end
    end
else
    A = coder.nullcopy(eml_expand(fv,sz));
    nA = coder.internal.indexInt(numel(A));
    for k = 1:nA
        if isempty(vectors{k})
            A(k) = fv;
        else
            A(k) = evaluateFunction(fun,vectors{k},fv);
        end
    end
end

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

function A = AccumArrayBinaryReduction(subs,val,sz,fun,fv,atype)
% Handle cases like MAX and MIN where a = fun(v) can be computed iteratively by:
% a = fun(v(1));
% for k = 2:length(v)
%     a = fun(a,v(k));
% end
coder.internal.allowEnumInputs;
coder.internal.prefer_const(subs,val,sz,fun,fv,atype);
nsubs = coder.internal.indexInt(size(subs,1));
nd = coder.internal.indexInt(size(subs,2));
filled = true(sz);
A = eml_expand(fv,sz);
for k = 1:nsubs
    s = cell(1,nd);
    for j = 1:nd
        s{j} = subs(k,j);
    end
    if filled(s{:})
        filled(s{:}) = false;
        A(s{:}) = fun(val(k));
    else
        A(s{:}) = fun([A(s{:});val(k)]);
    end
end

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

function sz = computeSizeFromSubs(subs)
coder.internal.prefer_const(subs);
TWO = coder.internal.indexInt(2);
nd = coder.internal.indexInt(size(subs,2));
nsubs = coder.internal.indexInt(size(subs,1));
sz = zeros(1,max(nd,TWO),coder.internal.indexIntClass);
if nd == 1
    sz(2) = 1;
end
if nsubs >= 1
    for j = 1:nd
        sz(j) = subs(1,j);
    end
    for k = TWO:nsubs
        for j = 1:nd
            if subs(k,j) > sz(j)
                sz(j) = subs(k,j);
            end
        end
    end
end

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

function p = sizeInRange(sz,minsz)
coder.internal.prefer_const(sz,minsz);
lensz = coder.internal.indexInt(length(sz));
lenminsz = coder.internal.indexInt(length(minsz));
p = true;
if lensz < lenminsz
    for k = 1:lensz
        p = p && (sz(k) >= minsz(k));
    end
    for k = lensz+1:lenminsz
        p = p && (minsz(k) <= 1);
    end
else
    for k = 1:lenminsz
        p = p && (sz(k) >= minsz(k));
    end
    for k = lensz+1:lensz
        p = p && (sz(k) >= 1);
    end
end

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

function assertValidSubsVal(subs,val)
coder.internal.prefer_const(subs,val);
coder.internal.assert(isreal(subs) && ismatrix(subs) && ~issparse(subs) && ...
    coder.internal.isBuiltInNumeric(subs), ...
    'MATLAB:accumarray:badIndInput');
coder.internal.assert(coder.internal.isConst(size(subs,2)), ...
    'Coder:toolbox:SizeSubs2MustBeConstant');
coder.internal.assert(size(subs,2) >= 1, ...
    'MATLAB:accumarray:emptyNumericIndInput');
coder.internal.assert(allPosInt(subs), ...
    'MATLAB:accumarray:nonPosIntIndValues');
coder.internal.assert(coder.internal.isConst(isvector(val)) && isvector(val), ...
    'MATLAB:accumarray:badSizeValInputMatInd');
coder.internal.assert(isscalar(val) || length(val) == size(subs,1), ...
    'MATLAB:accumarray:badSizeValInputMatInd');
coder.internal.assert(coder.internal.isBuiltInNumeric(val) || ...
    islogical(val) || ischar(val), ...
    'MATLAB:accumarray:badClassValInput');

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

function p = allPosInt(x)
p = true;
for k = 1:numel(x)
    p = p && (x(k) >= 1) && (floor(x(k)) == x(k)) && ...
        (x(k) <= intmax(coder.internal.indexIntClass));
end

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

function matsubs = cellSubsToMatrixSubs(cellsubs)
coder.internal.assert(isvector(cellsubs) && ~isempty(cellsubs), ...
    'MATLAB:accumarray:badIndInput');
m = coder.internal.indexInt(length(cellsubs{1}));
n = coder.internal.indexInt(length(cellsubs));
matsubs = zeros(m,n,coder.internal.indexIntClass);
for k = 1:n
    coder.internal.assert(isnumeric(cellsubs{k}) && isreal(cellsubs{k}) && ...
        coder.internal.isConst(isvector(cellsubs{k})) && ...
        isvector(cellsubs{k}) && length(cellsubs{k}) == m, ...
        'MATLAB:accumarray:badCellIndInput');
    matsubs(:,k) = cellsubs{k};
end

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

function atype = tryfv(fv,atype)
atype{1} = fv{1};

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

function y = evaluateFunction(fun,x,fv)
% Evaluate y = fun(x). Throws an error if y is non-scalar. On targets with
% error checking stripped out, returns y(1) for non-scalar y that is
% non-empty, otherwise fv.
coder.inline('always');
coder.internal.prefer_const(fun,fv);
tmp = fun(x);
coder.internal.assert(isscalar(tmp), ...
    'MATLAB:accumarray:nonscalarFunOutput',func2str(fun));
coder.internal.assert(~issparse(tmp), ...
    'MATLAB:accumarray:sparseFunOutput',func2str(fun));
if isempty(tmp)
    y = fv;
elseif iscell(tmp)
    y = tmp{1};
else
    y = tmp(1);
end

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