function Y = sum(X, varargin)     %#codegen
    % MATLAB Code Generation Library function for fixed point sum
    %
    % Limitations:
    % 1) Dimensions of X or the second input 'dim' must be less than or equal to 2.

    % $INCLUDE(DOC) toolbox/eml/lib/fixedpoint/@embedded/@fi/sum.m $
    % Copyright 2002-2023 The MathWorks, Inc.

    % The third input is used to determine the output type ('native' or
    % 'double'). The fourth input (nanflag) is ignored but has to be checked
    % for the wrong value.

    narginchk(1, 4);

    coder.extrinsic('emlGetNTypeForSum');

    Tx = eml_typeof(X);
    Fx = eml_fimath(X);
    szX = size(X);

    % We now need to find out what our output type is; we should figure
    % out the dimension to sum against while we are at it.
    switch nargin
        case 1
            cls = 'native';
            dim = getDims(X);
        case 2
            in = varargin{1};
            if isnumeric(in)
                cls = 'native';
                dim = parseDims(in);
            else
                dim = getDims(X);
                coder.internal.prefer_const(in);
                cls = checkArgForFlags(in);
            end
        case 3
            in1 = varargin{1};
            in2 = varargin{2};
            if isnumeric(in1)
                dim = parseDims(in1);
                cls = checkArgForFlags(in2);
            else
                dim = getDims(X);
                coder.internal.prefer_const(in1);
                coder.internal.prefer_const(in2);
                cls = checkArgsSeparately(in1, in2);
            end
        case 4
            dim = parseDims(varargin{1});
            coder.internal.prefer_const(varargin{2});
            coder.internal.prefer_const(varargin{3});
            cls = checkArgsSeparately(varargin{2}, varargin{3});
    end

    % If the outtype is 'double', call the native sum and return early. For
    % anything else ('native', 'default' or any nanflag) proceed normally.
    if strncmpi('double', cls, length(cls))
        Y = sum(double(X), dim);
        return;
    end

    if isscaledtype(X)
        % Fixed FI

        % Check to see if scaling is not slope bias. Error out if it is.
        bias = coder.const(get(Tx,'Bias'));
        eml_invariant(bias==0, 'fixed:fi:binaryPointOnlyMath');

        fullPrecSum = coder.const(strcmpi(get(Fx,'SumMode'),'FullPrecision'));
        cb4sum      = coder.const(get(Fx,'CastBeforeSum'));
        if ~(fullPrecSum || cb4sum)
            eml_invariant(false, ...
                'fixed:coder:fiMathOpsFullPrecisionCastBeforeSum');
        end
        maxWL = getFixedPointWidthLimit();
        if eml_is_const(szX)
            Ty = coder.const(emlGetNTypeForSum(Tx,Fx,szX,true,double(dim),maxWL));
        else
            % When input sizes can change at run-time, we only allow SumModes
            % 'KeepLSB' and 'SpecifyPrecision'; For these two modes the output
            % NumericType does not depend on the input size; we pass
            % ones(1,coder.internal.ndims(X)) as a dummy size argument.

            % If the Sum dim is >> ndims(X) the output type == input type
            if dim > coder.internal.ndims(X)
                Ty = Tx;
            else
                dummySizeX = ones(1,coder.internal.ndims(X));
                Ty = coder.const(emlGetNTypeForSum(Tx,Fx,dummySizeX,false,double(dim),maxWL));
            end
        end

        if eml_is_const(size(X,double(dim))) && size(X,double(dim)) == 1
            Y = eml_fimathislocal(eml_cast(X,Ty,Fx),eml_fimathislocal(X));
        elseif isempty(X)
            Y = fiSumAlloc(X,Ty,Fx,double(dim));
        elseif eml_is_const(isvector(X)) && isvector(X)
            Y = eml_fimathislocal(eml_cast(X(1),Ty,Fx),eml_fimathislocal(X));
            for k = 2:numel(X)
                Y(1) = eml_plus(Y(1),X(k),Ty,Fx);
            end
        else
            Y = coder.nullcopy(fiSumAlloc(X,Ty,Fx,double(dim)));
            vlen = size(X,double(dim));
            vstride = coder.internal.prodsize(X,'below',double(dim));
            npages = coder.internal.prodsize(X,'above',double(dim));
            ix = zeros(coder.internal.indexIntClass);
            iy = zeros(coder.internal.indexIntClass);
            for i = 1:npages
                ixstart = ix;
                for j = 1:vstride
                    ixstart = coder.internal.indexPlus(ixstart,1);
                    ix = ixstart;
                    s = eml_cast(X(ix),Ty,Fx);
                    for k = 2:vlen
                        ix = coder.internal.indexPlus(ix,vstride);
                        s(1) = eml_plus(s(1),X(ix),Ty,Fx);
                    end
                    iy = coder.internal.indexPlus(iy,1);
                    Y(iy) = s;
                end
            end
        end

    elseif isfloat(X)
        % True Double or True Single FI
        
        xTemp = eml_cast(X,eml_fi_getDType(X));
        xSum  = sum(xTemp,double(dim));
        Y     = eml_fimathislocal(eml_cast(xSum,Tx,Fx),eml_fimathislocal(X));

    else
        % FI datatype not supported
        eml_fi_assert_dataTypeNotSupported('SUM');
    end

end

%-----------------------SUB-FUNCTIONS--------------------------------------

function Y = fiSumAlloc(X,Ty,Fx,dim)
% Initialize Y to the proper size and complexity
% This function is never called with dim > ndims(X).
    sz = size(X);
    sz(double(dim)) = 1;
    ZERO = eml_fimathislocal(eml_cast(0,Ty,Fx),eml_fimathislocal(X));
    if isreal(X)
        Y = eml_expand(ZERO,sz);
    else
        Y = eml_expand(complex(ZERO,ZERO),sz);
    end
end

function d = parseDims(in)
% If the dimension argument was passed in, then check it and then cast it
% appropriately.
    coder.internal.assert(isnumeric(in), 'fixed:fi:invalidDimInput');
    coder.internal.prefer_const(in);
    eml_invariant(eml_is_const(in), ...
        'fixed:coder:dimArgMustBeAConstant');
    coder.internal.assertValidDim(double(in));
    d = cast(in,coder.internal.indexIntClass);
end

function d = getDims(X)
% If the dimension argument was not passed in, then figure out what would
% be the best bet.
    d = coder.internal.constNonSingletonDim(X);
    eml_invariant((eml_is_const(size(X,double(d))) || isscalar(X) || size(X,double(d)) ~= 1), ...
        'fixed:coder:autoDimIncompatibility');
end

function y = validateFlag(in)
% A sub-function that validates an argument that is presumed to be a flag.
% This function is called if isnumeric(in) returns false, so we are sure
% that the argument isn't a number.
    y = false;
    if (isstring(in) || ischar(in))
        y = true;
    end
end

function y = checkOutType(in)
% A sub-function to verify that a given argument is an outtype.
    y = false;
    l = length(in);
    if (strncmpi('double', in, l) || strncmpi('default', in, l) || strncmpi('native', in, l))
        if ~(l == 1 && in == 'd')
            y = true;
        end
    end
end

function y = checkNanFlag(in)
% A sub-function to verify that a given argument is a nanflag.
    y = false;
    l = length(in);
    if (strncmpi('omitnan', in, l) || strncmpi('includenan', in, l))
        y = true;
    end
end

function cls = checkArgForFlags(in)
% A sub-function to check if an arg is outtype or nanflag; error out if
% neither.
    coder.internal.assert(coder.internal.isConst(in), 'Coder:toolbox:FlagMustBeConst');
    coder.internal.assert(validateFlag(in), 'fixed:fi:sumUnknownFlag');
    l = lower(char(in));
    c = checkOutType(l) || checkNanFlag(l);
    coder.internal.assert(c, 'fixed:fi:sumUnknownFlag');
    cls = in;
end

function cls = checkArgsSeparately(in1, in2)
% A sub-function to check that both args supplied are the correct
% outtype/nanflag; error out if not. (in1,in2) can be (outtype,nanflag) or
% (nanflag,outtype).
    coder.internal.assert(coder.internal.isConst(in1), 'Coder:toolbox:FlagMustBeConst');
    coder.internal.assert(coder.internal.isConst(in2), 'Coder:toolbox:FlagMustBeConst');
    coder.internal.assert(validateFlag(in1), 'fixed:fi:sumUnknownFlag');
    coder.internal.assert(validateFlag(in2), 'fixed:fi:sumUnknownFlag');

    l1 = lower(char(in1));
    l2 = lower(char(in2));

    out1 = checkOutType(l1);
    out2 = checkOutType(l2);
    nan1 = checkNanFlag(l1);
    nan2 = checkNanFlag(l2);

    coder.internal.assert( ~(out1 && out2), 'MATLAB:sum:repeatedFlagOutType');
    coder.internal.assert( ~(nan1 && nan2), 'fixed:fi:sumRepeatedFlagNaN');
    coder.internal.assert( (out1 && nan2) || (out2 && nan1),...
                                           'fixed:fi:sumUnknownFlag');

    if (out1 && nan2)
        cls = in1;
    else
        cls = in2;
    end 
end

% LocalWords: AConstant
