function [dX,dweights,dbias] = internal_dlconvBackward(dZ,X,weights,permX,permW,stride,dilation,paddingSize,paddingStrategy,numGroups,flags)
% This function is undocumented and will change in a future release.

% Backward method for d-dimensional convolution on the CPU.
%
% Inputs:
%   dZ          = O1 x O2 x...x Od x  K  x N array.
%   X           = N1 x N2 x...x Nd x  C  x N array.
%   weights     = M1 x M2 x...x Md x(C/G)x (K/G) x G array. G is the number
%                 of groups and G must divide C and K.
%   permX       = 1 x d+2 array giving the data to view permutation of X
%                 or 1 x 0 if trivial.
%   permW       = 1 x d+3 array giving the data to view permutation of W
%                 or 1 x 0 if trivial.
%   stride      = [s1,...,sd] where si is stride in the ith dimension.
%   dilation    = [c1,...,cd] where ci is dilation in the ith dimension.
%   paddingSize = [L1,H1,...,Ld,Hd] where Li and Hi are the start and end
%                 padding sizes in the ith dimension.
%   paddingStrategy = strategy for padding the input array.
%   numGroups   = number of groups G.
%   flags       = logical vector indicating which outputs to compute.
%
% Outputs:
%   dX          = N1 x N2 x...x Nd x  C  x N array.
%   dweights    = M1 x M2 x...x Md x (C/G) x (K/G) x G array.
%   dbias       = 1  x 1  x...x 1  x  K array.

%   Copyright 2019-2022 The MathWorks, Inc.

if nargin < 11
    flags = true(3,1);
end

if ~isreal(dZ)
    error(message('deep:dlarray:ComplexGradientOutput', 'dlconv'));
end

dXNeeded = flags(1);
dweightsNeeded = flags(2);
dbiasNeeded = flags(3);

dX = [];
dweights = [];
dbias = [];

% Mixed precision is not supported by builtin.
[dZ, X, weights] = nnet.internal.cnnhost.castToSuperiorFloat(dZ, X, weights);

numConvDims = numel(stride);

% Permute weights to the canonical order
isPermWNonEmpty = ~isempty(permW);
if isPermWNonEmpty
    weights = permute(weights,permW);
end
numFilters = size(weights,numConvDims+2);
% Convert numGroups to host API format
filterGrouping = repmat(numFilters,1,numGroups);

% Permute input data and loss
isPermXNonEmpty = ~isempty(permX);
if isPermXNonEmpty
    X = permute(X,permX);
    dZ = permute(dZ,permX);
end
% We assume the convolution dimensions to be ordered.
convDimsIdx = 1:numConvDims;

[paddingStart, paddingEnd] = deep.internal.dlarray.numeric.convertBorderToHostFormat(paddingStrategy.remainingPaddingSize(paddingSize));

if dXNeeded
    if size(weights,numConvDims+3) == numGroups && numGroups > 1
        weights = deep.internal.dlarray.numeric.groupConvolutionFilters(weights,numConvDims,numGroups);
    end
    imageSize = paddingStrategy.getPaddedDimsSize(X,paddingSize,convDimsIdx);
    dX = builtin('_batchconvBackwardData', ...
        imageSize, dZ, weights, ...
        [paddingStart, paddingEnd], ...
        stride, dilation, filterGrouping);

    dX = paddingStrategy.backwardPad(dX,paddingSize);
    if isPermXNonEmpty
        dX = ipermute(dX,permX);
    end
end

if dweightsNeeded
    weightsSize = size(weights,convDimsIdx);
    
    dweights = builtin('_batchconvBackwardFilter', ...
        weightsSize, dZ, paddingStrategy.forwardPad(X,paddingSize,convDimsIdx), ...
        [paddingStart, paddingEnd],...
        stride, dilation, filterGrouping);
    if numGroups > 1
        dweights = deep.internal.dlarray.numeric.ungroupConvolutionFilters(dweights,numConvDims,numGroups);
    end
    if isPermWNonEmpty
        dweights = ipermute(dweights,permW);
    end
end

if dbiasNeeded
    dbias = sum(dZ, [1:numConvDims numConvDims+2]);
end
end
