function layer = maxPooling1dLayer(poolSize, NameValueArgs)
% maxPooling1dLayer   1-D max pooling layer
%
%   layer = maxPooling1dLayer(poolSize) creates a layer that performs 1-D
%   max pooling. A max pooling layer divides the input into rectangular
%   pooling regions, and outputs the maximum 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 = maxPooling1dLayer(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.
%       'Name'                    - A name for the layer. The default is
%                                   ''.
%
%   Example 1:
%       % Create a 1-D max pooling layer with nonoverlapping pooling
%       % regions, which downsamples by a factor of 2.
%
%       layer = maxPooling1dLayer(2, 'Stride', 2);
%
%   Example 2:
%       % Create a 1-D max pooling layer with overlapping pooling regions
%       % and padding for the left of the input.
%
%       layer = maxPooling1dLayer(2, 'Padding', [1 0]);
%
%   See also nnet.cnn.layer.MaxPooling1DLayer, averagePooling1dLayer,
%   convolution1dLayer, nnet.cnn.layer.MaxPooling2DLayer,
%   convolution2dLayer, averagePooling2dLayer.
%
%   <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
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 a max pooling layer.
internalLayer = nnet.internal.cnn.layer.MaxPooling1D( ...
    args.Name, ...
    args.PoolSize, ...
    args.Stride, ...
    args.PaddingMode, ...
    args.PaddingSize );

% Pass the internal layer to a function to construct a user visible
% max pooling layer
layer = nnet.cnn.layer.MaxPooling1DLayer(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.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:MaxPooling1DLayer: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
