function layer = averagePooling3dLayer(poolSize, NameValueArgs)
% averagePooling3dLayer   3-D average pooling layer
%
%   layer = averagePooling3dLayer(poolSize) creates a layer that performs
%   3-D average pooling. A 3-D average pooling layer divides the input into
%   cuboidal pooling regions, and outputs the average of each region.
%   poolSize specifies the width, height and depth of a pooling region. It
%   can be a scalar, in which case the pooling regions will have the same
%   width, height and depth, or a vector [h w d] where h specifies the
%   height, w specifies the width and d specifies the depth. Note that if
%   the 'Stride' dimensions are less than the respective pool dimensions,
%   then the pooling regions will overlap.
%
%   layer = averagePooling3dLayer(poolSize, 'PARAM1', VAL1, 'PARAM2', VAL2, ...)
%   specifies optional parameter name/value pairs for creating the layer:
%
%       'Stride'                  - The step size for traversing the input
%                                   vertically, horizontally and in the
%                                   depth direction. This can be a scalar,
%                                   in which case the same value is used
%                                   for all three dimensions, or it can be
%                                   a vector [u v w] where u is the
%                                   vertical stride, and v is the
%                                   horizontal stride and w is the depth
%                                   stride. Values that are greater than 1
%                                   can be used to down-sample the input.
%                                   The default is [1 1 1].
%       'Padding'                 - The padding applied to the input
%                                   along the edges. This can be:
%                                     - the character array 'same'. Padding
%                                       is set so that the output size is
%                                       the same as the input size when the
%                                       stride is 1. More generally, the
%                                       output size is
%                                       ceil(inputSize/stride), where
%                                       inputSize is the height, width and
%                                       depth of the input.
%                                     - a scalar, in which case the same
%                                       padding is applied vertically
%                                       horizontally and in the depth
%                                       direction.
%                                     - a vector [a b c] where a is the
%                                       padding applied to the top and
%                                       bottom of the input, b is the
%                                       padding applied to the left and
%                                       right, and c is the padding applied
%                                       to the front and back.
%                                     - a 2-by-3 matrix [t l f; b r k]
%                                       where t is the padding applied to
%                                       the top, l is the padding applied
%                                       to the left, f is the padding
%                                       applied to the front, b is the
%                                       padding applied to the bottom, r is
%                                       the padding applied to the right
%                                       and k is the padding applied to the
%                                       back.
%                                   Note that the padding dimensions must
%                                   be less than the pooling region
%                                   dimensions. The default is 0.
%       'PaddingValue'            - Value used to pad the input along the
%                                   edges, specified as one of the 
%                                   following values:
%                                   - 0 - Pad the input edges with zeros.
%                                     These values are included in the  
%                                     average pooling operation.
%                                   - 'mean' - Pad the input edges with the
%                                     mean value of the pooling region. 
%                                     This is equivalent to excluding the 
%                                     padding values from the average 
%                                     pooling operation. 
%                                   The default value is 0.
%       'Name'                    - A name for the layer. The default is
%                                   ''.
%
%   Example 1:
%      % Create a 3-D average pooling layer with non-overlapping pooling
%      % regions, which downsamples by a factor of 2:
%
%       layer = averagePooling3dLayer(2, 'Stride', 2);
%
%   Example 2:
%      % Create a 3-D average pooling layer with overlapping pooling
%      % regions and padding for the top and bottom of the input:
%
%       layer = averagePooling3dLayer(3, 'Stride', 2, 'Padding', [1 0 0]);
%
%   Example 3:
%       % Create an average pooling layer with non-overlapping pooling
%       % regions of size 2, padding for the top and bottom of the input, 
%       % and exclude the padding from the average operation: 
%
%       layer = averagePooling3dLayer(2, 'Stride', 2, ...
%           'Padding', [1 0 0], 'PaddingValue', 'mean');
%
%   See also nnet.cnn.layer.AveragePooling3DLayer, convolution2dLayer,
%   convolution3dLayer, maxPooling2dLayer, maxPooling3dLayer,
%   maxUnpooling2dLayer, averagePooling2dLayer,
%   globalAveragePooling3dLayer.
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2018-2023 The MathWorks, Inc.

arguments
    poolSize                             {iAssertValidPoolSize}
    NameValueArgs.Name                   {iAssertValidLayerName} = ''
    NameValueArgs.Stride                 {iAssertValidStride} = 1
    NameValueArgs.Padding                {iAssertValidPadding} = 0
    NameValueArgs.PaddingValue           = 0
end

% Store all input arguments in one struct
args = NameValueArgs;
args.PoolSize = poolSize;

% Gather arguments to CPU and convert them to canonical form
args = nnet.internal.cnn.layer.util.gatherParametersToCPU(args);
args = iConvertToCanonicalForm(args);

% Check that the size of the pooling region is larger than the size of the
% padding 
iAssertPoolSizeIsGreaterThanPaddingSize(args);

% Create an internal representation of an average pooling layer.
internalLayer = nnet.internal.cnn.layer.AveragePooling3D( ...
    args.Name, ...
    args.PoolSize, ...
    args.Stride, ...
    args.PaddingMode, ...
    args.PaddingSize, ...
    args.PaddingValue);

% Pass the internal layer to a function to construct a user visible
% average pooling layer
layer = nnet.cnn.layer.AveragePooling3DLayer(internalLayer);

end

function inputArguments = iConvertToCanonicalForm(params)
inputArguments = struct;
% make sure strings get converted to char vectors and integer parameters to
% double
inputArguments.PoolSize = iConvertToDouble(iMakeIntoRowVectorOfThree(params.PoolSize));
inputArguments.Stride = iConvertToDouble(iMakeIntoRowVectorOfThree(params.Stride));
inputArguments.PaddingMode = iCalculatePaddingMode(params.Padding);
inputArguments.PaddingSize = iConvertToDouble(iCalculatePaddingSize(params.Padding));
inputArguments.PaddingValue = nnet.internal.cnn.layer.paramvalidation. ...
    validatePaddingValue(params.PaddingValue,"averagePooling3dLayer");
inputArguments.Name = char(params.Name); 
end

function iAssertValidLayerName(name)
nnet.internal.cnn.layer.paramvalidation.validateLayerName(name);
end

function iAssertValidPoolSize(value)
validateattributes(value, {'numeric'}, ...
    {'positive', 'real', 'integer', 'nonempty'});
iAssertScalarOrRowVectorOfThree(value,'poolSize');
end

function iAssertValidStride(value)
validateattributes(value, {'numeric'}, ...
    {'positive', 'real', 'integer', 'nonempty'});
iAssertScalarOrRowVectorOfThree(value,'Stride');
end

function iAssertValidPadding(value)
dims = 3;
nnet.internal.cnn.layer.paramvalidation.validatePadding(value,dims);
end

function iAssertScalarOrRowVectorOfThree(value,name)
if ~(isscalar(value) || iIsRowVectorOfThree(value))
    error(message('nnet_cnn:layer:Layer:ParamMustBeScalarOrTriple',name));
end
end

function rowVectorOfThree = iMakeIntoRowVectorOfThree(scalarOrRowVectorOfThree)
if(iIsRowVectorOfThree(scalarOrRowVectorOfThree))
    rowVectorOfThree = scalarOrRowVectorOfThree;
else
    rowVectorOfThree = [scalarOrRowVectorOfThree scalarOrRowVectorOfThree scalarOrRowVectorOfThree];
end
end

function tf = iIsRowVectorOfThree(x)
tf = isrow(x) && numel(x)==3;
end

function paddingMode = iCalculatePaddingMode(padding)
paddingMode = nnet.internal.cnn.layer.padding.calculatePaddingMode(padding);
end

function paddingSize = iCalculatePaddingSize(padding)
dims = 3;
paddingSize = nnet.internal.cnn.layer.padding.calculatePaddingSize(padding,dims);
end

function iAssertPoolSizeIsGreaterThanPaddingSize(inputArguments)
if(~iPoolSizeIsGreaterThanPaddingSize(inputArguments.PoolSize, inputArguments.PaddingSize))
    exception = MException(message('nnet_cnn:layer:AveragePooling3DLayer:PaddingSizeLargerThanOrEqualToPoolSize'));
    throwAsCaller(exception);
end
end

function tf = iPoolSizeIsGreaterThanPaddingSize(poolSize, paddingSize)
tf = nnet.internal.cnn.layer.padding.poolOrFilterSizeIsGreaterThanPaddingSize(poolSize, paddingSize);
end

function value = iConvertToDouble(value)
value = nnet.internal.cnn.layer.util.convertToDouble(value);
end
