function [dX, dW, dB] = internal_dltranspconvBackward(dZ, X, W, permX, permW, croppingSize, stride, dilation, numGroups, ~, flags)
% This function is undocumented and will change in a future release.

% Backward method for d-dimensional transposed convolution on the CPU.
%
% Inputs:
%   dZ          = O1 x O2 x...x Od x Cout x N array.
%   X           = N1 x N2 x...x Nd x Cin x N array.
%   W           = M1 x M2 x...x Md x K x C x G array. G is the
%               number of groups. Cout=K*G and Cin=C*G.
%   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.
%   croppingSize = [L1,H1,...,Ld,Hd] where Li and Hi are the start and end
%                 cropping sizes in the ith dimension.
%   stride      = [s1,...,sd] where si is stride in the ith dimension.
%   dilation    = [c1,...,cd] where ci is dilation in the ith dimension.
%   numGroups   = number of groups G.
%   outputSizeOffset = [o1,...,od] where oi is the offset in the ith
%                      dimension.
%   flags       = logical vector indicating which outputs to compute.
%
% Outputs:
%   dX          = N1 x N2 x...x Nd x Cin  x N array.
%   dW          = M1 x M2 x...x Md x K x C x G array.
%   dB          = 1  x 1  x...x 1  x Cout array.

%   Copyright 2019-2022 The MathWorks, Inc.

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

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

dXNeeded = flags(1);
dWNeeded = flags(2);
dBNeeded = flags(3);

dX = [];
dW = [];
dB = [];

% Ensure all inputs have the same data type
[dZ, X, W] = nnet.internal.cnnhost.castToSuperiorFloat(dZ, X, W);

[cropStart, cropEnd] = deep.internal.dlarray.numeric.convertBorderToHostFormat(croppingSize);
croppingVec = [cropStart, cropEnd];

numConvDims = numel(stride);

isPermWNonEmpty = ~isempty(permW);
if isPermWNonEmpty
    numFilters = size(W,permW(numConvDims+2));
else
    numFilters = size(W,numConvDims+2);
end
% Convert numGroups to host API format, formatting filter grouping
% information for the builtin
filterGrouping = repmat(numFilters,1,numGroups);

% Keep a copy of the original permutation of X
originalPermX = permX;
isPermXNonEmpty = ~isempty(permX);
isOriginalPermXNonEmpty = isPermXNonEmpty;

% Compute gradient of weights and bias
if dWNeeded
    % Call to builtin convolution backward with reversed inputs X and dZ
    if isPermWNonEmpty
        sizeW = size(W, permW(1:numConvDims));
    else
        sizeW = size(W, 1:numConvDims);
    end
    if isPermXNonEmpty
        X = permute(X,permX);
        dZ = permute(dZ,permX);
        permX = 1:0;
        isPermXNonEmpty = false;
    end
    dW = builtin('_batchconvBackwardFilter', ...
        sizeW, X, dZ, croppingVec, stride, dilation, filterGrouping);
    if numGroups > 1
        dW = deep.internal.dlarray.numeric.ungroupConvolutionFilters(dW,numConvDims,numGroups);
    end
    if isPermWNonEmpty
        dW = ipermute(dW,permW);
    end
end

if dBNeeded
    if isPermXNonEmpty
        dB = sum(dZ, permX([1:numConvDims numConvDims+2]));
    else
        dB = sum(dZ, [1:numConvDims numConvDims+2]);
    end
end

% Gradient of the input (call to convolution builtin on the loss)
if dXNeeded
    % For non-standard formats we need to standardize group dimensions in weights
    if isPermWNonEmpty
        [W, permW] = deep.internal.dlarray.numeric.convertWeightsToConvolutionHostAPIformat(W, permW, numGroups);
    end

    % Call to the builtin. Note that X returned by the builtin has format
    % 'CBS*S' and permXout gives the permutation vector w.r.t the 'S*SCB'
    % dimension order.
    [dX, permXout] = builtin('_batchconv', {dZ, permX}, {W, permW}, croppingVec, stride, dilation, [], filterGrouping);
    
    % originalPermX and permXout are permutation vectors that describe how
    % to convert the input and output X respectively into the same dimension
    % order (which happens to be 'S*SCB'), i.e., the conversion permutation
    % from X_out to X_in, followed by originalPermX must return the same
    % permutation as permXout. Here, we want to return the data with
    % the originalPermX. Combining two permutations is simply a reordering
    % of the first with the second, which gives us the formula
    % permOutToIn(originalPermX) = permXout that satisfies the equality.
    if isOriginalPermXNonEmpty
        permOutToIn(originalPermX) = permXout;
    else
        permOutToIn = permXout;
    end
    dX = permute(dX,permOutToIn);
end

end