function layer = convolution1dLayer(filterSize, numFilters, NameValueArgs)
% convolution1dLayer   1-D convolution layer 
%
%   layer = convolution1dLayer(filterSize, numFilters) creates a layer
%   for 1-D convolution. filterSize is a scalar specifying the length of
%   the filters. numFilters specifies the number of filters.
% 
%   layer = convolution1dLayer(filterSize, numFilters, '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. The default is
%                                   1.
%       'DilationFactor'          - Upsampling factor of the filter, 
%                                   specified as a scalar. It corresponds
%                                   to an effective filter size of filterSize
%                                   + (filterSize-1) .* (dilationFactor-1). 
%                                   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.
%                                     - 'causal' - Apply left padding to
%                                       the input, equal to (filterSize -
%                                       1) .* dilationFactor.
%                                     - 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 filter size. The default is 0.
%       'PaddingValue'            - Value used to pad the input along the
%                                   edges, specified as one of the
%                                   following:
%                                   - Scalar value - Pad using the  
%                                     specified value. 
%                                   - 'symmetric-include-edge' - Pad using 
%                                     mirrored layer input, including the 
%                                     edge values.
%                                   - 'symmetric-exclude-edge' - Pad using 
%                                     mirrored layer input, excluding the 
%                                     edge values.
%                                   - 'replicate' - Pad using repeated 
%                                     layer input border elements.
%                                   The default value is 0.
%       'NumChannels'             - Number of channels for each filter,
%                                   specified as a positive integer or 
%                                   'auto'. If 'NumChannels' is 'auto', 
%                                   then the software automatically 
%                                   determines the number of channels at 
%                                   training time. The default value is 
%                                   'auto'.
%       'Weights'                 - Layer weights, specified as a
%                                   filterSize-by-numChannels-by-numFilters 
%                                   array or []. The default is []. 
%       'Bias'                    - Layer biases, specified as a
%                                   1-by-numFilters array or []. The
%                                   default is [].
%       'WeightLearnRateFactor'   - Multiplier for the learning rate of the 
%                                   weights. The default is 1.
%       'BiasLearnRateFactor'     - Multiplier for the learning rate of the 
%                                   biases. The default is 1.
%       'WeightL2Factor'          - Multiplier for the L2 weight regulariser 
%                                   for the weights. The default is 1.
%       'BiasL2Factor'            - Multiplier for the L2 weight regulariser 
%                                   for the biases. The default is 0.
%       'WeightsInitializer'      - Function to initialize the weights,
%                                   specified as 'glorot', 'he',
%                                   'narrow-normal', 'zeros', 'ones' or a
%                                   function handle. The default is
%                                   'glorot'.
%       'BiasInitializer'         - Function to initialize the bias,
%                                   specified as 'narrow-normal', 'zeros',
%                                   'ones' or a function handle. The
%                                   default is 'zeros'.
%       'Name'                    - Name for the layer. The default is
%                                   ''.
%
%   Example 1:
%       % Create a convolutional layer with 64 filters that have a size of
%       % 11, and use a stride of 4.
%
%       layer = convolution1dLayer(11, 64, 'Stride', 4);
%
%   Example 2:
%       % Create a convolutional layer with 32 filters that have a size of
%       % 5. Pad the input with 2 pixels on either side. Set the learning
%       % rate factor for the bias to 2. Manually initialize the weights
%       % from a Gaussian with standard deviation 0.01.
%
%       layer = convolution1dLayer(5, 32, 'Padding', 2, ...
%           'BiasLearnRateFactor', 2, 'Weights', randn([5 3 32])*0.01);
%
%   Example 3:
%       % Create a convolutional layer with 32 filters that have a size of
%       % 3. Set the padding to 'causal'.
%
%       layer = convolution1dLayer(3, 32, 'Padding', 'causal');
%
%   See also nnet.cnn.layer.Convolution1DLayer, maxPooling1dLayer, 
%   averagePooling1dLayer.
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2020-2023 The MathWorks, Inc.

arguments
    filterSize                           {iAssertValidFilterSize}
    numFilters                           {iAssertValidNumFilters}
    NameValueArgs.Name                   {iAssertValidLayerName} = ''
    NameValueArgs.Stride                 {iAssertValidStride} = 1
    NameValueArgs.DilationFactor         {iAssertValidDilationFactor} = 1
    NameValueArgs.Padding                {iAssertValidPadding} = 0
    NameValueArgs.PaddingValue           {iAssertValidPaddingValue} = 0
    NameValueArgs.NumChannels            {iAssertValidNumChannels} = 'auto'
    NameValueArgs.WeightLearnRateFactor  {iAssertValidFactor} = 1
    NameValueArgs.BiasLearnRateFactor    {iAssertValidFactor} = 1
    NameValueArgs.WeightsInitializer     = 'glorot'
    NameValueArgs.BiasInitializer        = 'zeros'
    NameValueArgs.WeightL2Factor         {iAssertValidFactor} = 1
    NameValueArgs.BiasL2Factor           {iAssertValidFactor} = 0
    NameValueArgs.Weights                = []
    NameValueArgs.Bias                   = []
end

% Store all input arguments in one struct
args = NameValueArgs;
args.FilterSize = filterSize;
args.NumFilters = numFilters;

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

% Create an internal representation of a 1-D convolutional layer.
internalLayer = nnet.internal.cnn.layer.Convolution1D(args.Name, ...
                                                      args.FilterSize, ...
                                                      args.NumChannels, ...
                                                      args.NumFilters, ...
                                                      args.Stride, ...
                                                      args.DilationFactor, ...
                                                      args.PaddingMode, ...
                                                      args.PaddingSize, ...
                                                      args.PaddingValue);

internalLayer.Weights.L2Factor = args.WeightL2Factor;
internalLayer.Weights.LearnRateFactor = args.WeightLearnRateFactor;

internalLayer.Bias.L2Factor = args.BiasL2Factor;
internalLayer.Bias.LearnRateFactor = args.BiasLearnRateFactor;

% Pass the internal layer to a function to construct a user visible
% convolutional layer.
layer = nnet.cnn.layer.Convolution1DLayer(internalLayer);
layer.WeightsInitializer = args.WeightsInitializer;
layer.BiasInitializer = args.BiasInitializer;
layer.Weights = args.Weights;
layer.Bias = args.Bias;
end

function iAssertValidFilterSize(value)
dims = 1;
nnet.internal.cnn.layer.paramvalidation.validateSizeParameter(value, 'FilterSize', dims);
end

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

function iAssertValidStride(value)
dims = 1;
nnet.internal.cnn.layer.paramvalidation.validateSizeParameter(value, 'Stride', dims);
end

function iAssertValidDilationFactor(value)
dims = 1;
nnet.internal.cnn.layer.paramvalidation.validateSizeParameter(value, 'DilationFactor', dims);
end

function iAssertValidPadding(value)
nnet.internal.cnn.layer.paramvalidation.validatePaddingFor1DConvolution(value);
end

function iAssertValidPaddingValue(value)
nnet.internal.cnn.layer.padding.assertAndReturnValidPaddingValue(value);
end

function iAssertValidNumChannels(value)
nnet.internal.cnn.layer.paramvalidation.validateNumChannels(value);
end

function iAssertValidFactor(value)
nnet.internal.cnn.layer.paramvalidation.validateLearnFactor(value);
end

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

function inputArguments = iConvertToCanonicalForm(params)
% Make sure integral values are converted to double and strings to char vectors
inputArguments = struct;
inputArguments.FilterSize = iConvertToDouble( params.FilterSize );
inputArguments.NumFilters = iConvertToDouble( params.NumFilters );
inputArguments.Stride = iConvertToDouble( params.Stride );
inputArguments.DilationFactor = iConvertToDouble( params.DilationFactor );
inputArguments.PaddingMode = iCalculatePaddingMode(params.Padding);
inputArguments.PaddingSize = iConvertToDouble( iCalculatePaddingSize(params.Padding, ...
    inputArguments.FilterSize, inputArguments.DilationFactor, inputArguments.PaddingMode) );
inputArguments.NumChannels = iConvertToDouble( iConvertToEmptyIfAuto(params.NumChannels) );
inputArguments.PaddingValue = iConvertPaddingValue(params.PaddingValue);
inputArguments.WeightLearnRateFactor = params.WeightLearnRateFactor;
inputArguments.BiasLearnRateFactor = params.BiasLearnRateFactor;
inputArguments.WeightsInitializer = params.WeightsInitializer;
inputArguments.BiasInitializer = params.BiasInitializer;
inputArguments.WeightL2Factor = params.WeightL2Factor;
inputArguments.BiasL2Factor = params.BiasL2Factor;
inputArguments.Weights = params.Weights;
inputArguments.Bias = params.Bias;
inputArguments.Name = char(params.Name);
end

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

function paddingSize = iCalculatePaddingSize(padding, filterSize, dilation, paddingMode)
isCausalPadding = strcmp(paddingMode, 'causal');
if isCausalPadding
    paddingSize = nnet.internal.cnn.layer.padding.calculateCausalPaddingSize(filterSize, dilation);
else
    paddingSize = nnet.internal.cnn.layer.padding.calculatePaddingSize(padding, 1);
end
end

function value = iConvertPaddingValue(value)
value = nnet.internal.cnn.layer.padding.assertAndReturnValidPaddingValue(value);
end

function y = iConvertToEmptyIfAuto(x)
if(iIsAutoString(x))
    y = [];
else
    y = x;
end
end

function tf = iIsAutoString(x)
tf = strcmp(x, 'auto');
end

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