function y = eml_mtimes_helper(a,b,TRANSA,TRANSB)
%MATLAB Code Generation Library Function

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

coder.internal.allowHalfInputs;
coder.internal.allowEnumInputs;
coder.internal.userReadableName('mtimes');
eml_invariant(nargin > 1,'MATLAB:minrhs');
if nargin < 3
    TRANSA = 'N';
end
if nargin < 4
    TRANSB = 'N';
end
coder.internal.prefer_const(TRANSA,TRANSB);
TRANSA = coder.const(TRANSA);
TRANSB = coder.const(TRANSB);
SCALARA = coder.internal.isConstTrue(isscalar(a));
SCALARB = coder.internal.isConstTrue(isscalar(b));
ISHALF = isa(a,'half') || isa(b,'half');
HASTRANS = coder.const(coder.internal.hasTranspose(TRANSA,TRANSB));
transOpA = coder.internal.getTransposeOp(TRANSA);
transOpB = coder.internal.getTransposeOp(TRANSB);
if ~isa(a,class(b))
    % This optimization is required to get integer-only code with HDL
    % for the case of integer-valued scalar double * integer array.
    if SCALARA
        coder.internal.prefer_const(a);
    end
    if SCALARB
        coder.internal.prefer_const(b);
    end
end
DYNAMIC_SCALAR_EXPANSION = coder.internal.get_eml_option('EnableDynamicScalarExpansionInMtimes');
if DYNAMIC_SCALAR_EXPANSION
    isScalarA = isscalar(a);
    isScalarB = isscalar(b);
    if isScalarA && ~isScalarB
        y = times(transOpA(a(1)),transOpB(b));
        return
    elseif ~isScalarA && isScalarB
        y = times(transOpA(a),transOpB(b(1)));
        return
    end
end
common_checks(a, b);
eml_property('associative');
if (eml_is_const(isscalar(a)) && isscalar(a)) || ...
        (eml_is_const(isscalar(b)) && isscalar(b))
    eml_property('commutative');
end
if coder.const(TRANSA == 'N')
    innerDimA = coder.internal.indexInt(size(a,2));
else
    innerDimA = coder.internal.indexInt(size(a,1));
end
if coder.const(TRANSB == 'N')
    innerDimB = coder.internal.indexInt(size(b,1));
else
    innerDimB = coder.internal.indexInt(size(b,2));
end
if isinteger(a) || isinteger(b)
    integer_only_checks(a,b);
elseif SCALARA || SCALARB
    % The scalar case, no dimension matching is needed.
else
    dynamic_size_checks(a, b, innerDimA, innerDimB);
end
if SCALARA || SCALARB
    coder.inline('always');
    if HASTRANS
        y = times(transOpA(a),transOpB(b));
    else
        y = times(a,b);
    end
elseif ISHALF
    y = coder.internal.blas.mtimes(a,b,TRANSA,TRANSB);
elseif eml_is_constant_folding || ...
        eml_ambiguous_types || ...
        use_eml_mtimes(a,b,TRANSA,TRANSB)
    coder.inline('always');
    if HASTRANS
        % Translates to CGIR #mmul node. Scalarization lowers #mmul to for loops.
        y = eml_mtimes(transOpA(a),transOpB(b));
    else
        y = eml_mtimes(a,b);
    end
else
    % Translates to one of many matrix multiplication algorithms

    % This is where MATLAB Coder cfg.CustomBLASCallback = 'classname'; or calling BLAS library in MEX occurs
    y = coder.internal.blas.mtimes(a,b,TRANSA,TRANSB);
end

% The functions below are extracted from the main body to speed up inference.
% The body of mtimes() contains eml_prefer_const so it is re-inferred very frequently.
% These small functions, on the contrary, do not depend on input values,
% so inference prepares them once and reuses during subsequent inference requests.

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

function common_checks(a, b)
eml_invariant((coder.internal.isBuiltInNumeric(a) && ~isenum(a)) || ...
    isa(a, 'half') || islogical(a) || ischar(a), ...
    'Coder:toolbox:unsupportedClass','mtimes',class(a));
eml_invariant((coder.internal.isBuiltInNumeric(b) && ~isenum(b)) || ...
    isa(b, 'half') || islogical(b) || ischar(b), ...
    'Coder:toolbox:unsupportedClass','mtimes',class(b));

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

function integer_only_checks(a, b)
eml_invariant(isa(a,class(b)) || ...
    (isscalar(a) && isa(a,'double')) || ...
    (isscalar(b) && isa(b,'double')), ...
    'Coder:MATLAB:mixedClasses');
eml_invariant(coder.internal.isConstTrue(isscalar(a)) || ...
    coder.internal.isConstTrue(isscalar(b)), ...
    'Coder:MATLAB:oneOperandMustBeScalar');
eml_invariant(isreal(a) && isreal(b), ...
    'Coder:MATLAB:complexInts');

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

function dynamic_size_checks(a,b,innerDimA,innerDimB)
coder.internal.prefer_const(innerDimA,innerDimB);
ndimOk = ndims(a) <= 2 && ndims(b) <= 2;
innerDimOk = innerDimA == innerDimB;
if ~ndimOk || ~innerDimOk
    if isscalar(a) || isscalar(b)
        eml_invariant(ndimOk && innerDimOk, ...
            'Coder:toolbox:mtimes_noDynamicScalarExpansion');
    else
        eml_invariant(ndimOk, ...
            'Coder:MATLAB:mtimes_inputsMustBe2D');
        eml_invariant(innerDimOk, ...
            'MATLAB:innerdim');
    end
end

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

function p = use_eml_mtimes(a,b,TRANSA,TRANSB)
coder.internal.prefer_const(TRANSA,TRANSB);
USECGIRMTIMES = coder.internal.get_eml_option('UseCGIRMtimes');
if ~USECGIRMTIMES && coder.const(TRANSA ~= 'N' && TRANSB ~= 'N')
    p = false;
    return
end
ma = coder.internal.indexInt(size(a,1));
na = coder.internal.indexInt(size(a,2));
mb = coder.internal.indexInt(size(b,1));
nb = coder.internal.indexInt(size(b,2));
STATICEMPTYINPUT = coder.const( ...
    coder.internal.isConstTrue(ma == 0) || ...
    coder.internal.isConstTrue(na == 0) || ...
    coder.internal.isConstTrue(mb == 0) || ...
    coder.internal.isConstTrue(nb == 0));
MIXEDREALIMAG = coder.const(isreal(a) ~= isreal(b));
MIXEDCLASS = coder.const(~isa(a,class(b)));
[mc,nc,k] = coder.internal.blas.mtimesDims(ma,na,mb,nb,TRANSA,TRANSB);
OUTERPROD = coder.internal.isConstTrue(k == 1);
SCALARC = coder.const( ...
    coder.internal.isConstTrue(mc == 1) && ...
    coder.internal.isConstTrue(nc == 1));
urb = coder.internal.blas.use_refblas;
USEREFBLAS = coder.internal.isConstTrue(urb);
CONSTSIZE = coder.const( ...
    coder.internal.isConst(mc) && ...
    coder.internal.isConst(nc) && ...
    coder.internal.isConst(k));
if ~USEREFBLAS && CONSTSIZE
    THRESH = coder.const( ...
        coder.internal.blas.threshold('xgemm',class(a),isreal(a)));
    UNDERTHRESH = coder.const(double(mc)*double(nc)*double(k) <= THRESH);
else
    UNDERTHRESH = false;
end
SKIPBLAS = coder.const( ...
    MIXEDCLASS || ...
    MIXEDREALIMAG || ...
    (USEREFBLAS && SCALARC) || ...
    ((USEREFBLAS || UNDERTHRESH) && CONSTSIZE));
NDA = coder.internal.ndims(a);
NDB = coder.internal.ndims(b);
if USECGIRMTIMES
    p = USEREFBLAS || UNDERTHRESH;
else
    p = coder.const( ...
        strcmp(coder.target(),'hdl') || ...
        ~isa(a,'float') || ...
        STATICEMPTYINPUT || ...
        OUTERPROD || ...
        NDA > 2 || ...
        NDB > 2 || ...
        SKIPBLAS);
end

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