function layer = averagePooling1dLayer(poolSize, NameValueArgs)
% averagePooling1dLayer   1-D average pooling layer
%
%   layer = averagePooling1dLayer(poolSize) creates a layer that performs 1-D
%   average pooling. An average pooling layer divides the input into
%   rectangular pooling regions, and outputs the average of each region.
%   poolSize is a scalar that specifies the length of the pooling region.
%   Note that if the stride is smaller than the pool size, then the pooling 
%   regions will overlap.
%
%   layer = averagePooling1dLayer(poolSize, 'PARAM1', VAL1, 'PARAM2', VAL2, ...)
%   specifies optional parameter name/value pairs for creating the layer:
%
%       'Stride'                  - Step size for traversing the input,
%                                   specified as a scalar. Values that are
%                                   greater than 1 can be used to
%                                   down-sample the input. The default is
%                                   1.
%       'Padding'                 - Padding applied to the input, specified
%                                   as one of the following:
%                                     - 'same' - Apply padding such 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 length of the 
%                                       input.
%                                     - scalar - Apply padding with the 
%                                       specified size on both sides of the
%                                       input.
%                                     - vector [l r] - Apply padding with 
%                                       size l and r on the left and right 
%                                       sides of the input, respectively.
%                                   The size of the padding must be less 
%                                   than the size of the pooling regions. 
%                                   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 1-D average pooling layer with nonoverlapping pooling
%       % regions, which downsamples by a factor of 2.
%
%       layer = averagePooling1dLayer(2, 'Stride', 2);
%
%   Example 2:
%       % Create a 1-D average pooling layer with overlapping pooling
%       % regions and padding for the left of the input.
%
%       layer = averagePooling1dLayer(2, 'Padding', [1 0]);
%
%   Example 3:
%       % Create a 1-D average pooling layer with nonoverlapping pooling
%       % regions of size 2, padding for the left and right of the input, 
%       % and exclude the padding from the average operation.
%
%       layer = averagePooling1dLayer(3, 'Stride', 3, ...
%           'Padding', [1 2], 'PaddingValue', 'mean');
%
%   See also nnet.cnn.layer.AveragePooling1DLayer, maxPooling1dLayer,
%   convolution1dLayer.
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2021-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.AveragePooling1D( ...
    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.AveragePooling1DLayer(internalLayer);
end

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

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

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

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

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

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

function paddingSize = iCalculatePaddingSize(padding)
dims = 1;
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:AveragePooling1DLayer: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
