function layer = inputLayer(inputSize,inputFormat,nameValueArgs)
% inputLayer    Input Layer
%
%   layer = inputLayer(inputSize, inputFormat) creates an input layer for
%   input data of size inputSize. inputSize must be a row vector of
%   positive integers or NaN, where NaN elements correspond to sizes not
%   required for network initialization (for example, sequence lengths or
%   batch sizes that can vary between mini-batches). inputFormat is a
%   string of dimension labels that indicate the dimension type. Each
%   character in inputFormat must be one of the following labels
%
%       - "S" - spatial
%       - "C" - channel
%       - "B" - batch
%       - "T" - time
%       - "U" - unspecified
%
%   The number of labels in inputFormat must be equal to the number of
%   elements of inputSize.
%
%   layer = inputLayer(inputSize, inputFormat, Name=name) additionally
%   specifies a name for the layer.
%
%   Example 1:
%       % Create an input layer with input size [224 3 NaN] and input
%       % format "SCB":
%
%       layer = inputLayer([224 3 NaN], "SCB");
%
%   See also nnet.cnn.layer.InputLayer, imageInputLayer, image3dInputLayer,
%   sequenceInputLayer, featureInputLayer
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2022-2023 The MathWorks, Inc.

arguments
    inputSize {iAssertValidInputSize}
    inputFormat {iAssertValidDimsFormat}
    nameValueArgs.Name {iAssertValidLayerName} = '';
end

inputSize = nnet.internal.cnn.layer.util.gatherParametersToCPU(inputSize);
nameValueArgs = nnet.internal.cnn.layer.util.gatherParametersToCPU(nameValueArgs);
[inputSize,inputFormat,nameValueArgs] = iConvertToCanonicalForm(inputSize,inputFormat,nameValueArgs);

iAssertInputSizeMatchesInputFormat(inputSize, inputFormat)
iAssertNoSpatialChannelOrUnspecifiedNanSizes(inputSize, inputFormat)
iAssertNoTrailingSingletonUnspecifiedDimsAfterPermute(inputSize, inputFormat)

% Create an internal representation of an input layer.
internalLayer = nnet.internal.cnn.layer.Input(...
    nameValueArgs.Name, ...
    inputSize, ...
    inputFormat);

% Pass the internal layer to a function to construct a user visible input
% layer.
layer = nnet.cnn.layer.InputLayer(internalLayer);
end

function iAssertValidInputSize(sz)
% Input size cannot be dlarray
if isdlarray(sz)
    error(message('deep:networkDataLayout:DlarraySize'));
end
% Input size must be a row vector with at least 2 elements
if ~isnumeric(sz) || ~isrow(sz) || numel(sz) < 2
    error(message('deep:networkDataLayout:InvalidSize'));
end
% Input size must be positive integers or NaN 
sz = sz(~isnan(sz));
if ~isreal(sz) || any(sz < 1) || any(sz ~= fix(sz)) || any(~isfinite(sz))
    error(message('nnet_cnn:layer:GenericInputLayer:InvalidSizeValues'));
end
end

function iAssertValidDimsFormat(fmt)
deep.internal.dlarray.mustBeDataFormatType(fmt, true);
fmt = char(fmt);
deep.internal.format.validateDataFormat(fmt);
end

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

function [inputSize,inputFormat,args] = iConvertToCanonicalForm(inputSize,inputFormat,params)
args = struct;
inputSize = iConvertToDouble(inputSize);
inputFormat = char(inputFormat);
args.Name = char(params.Name);
end

function iAssertInputSizeMatchesInputFormat(sz, fmt)
if numel(sz) ~= numel(fmt)
    error(message('deep:networkDataLayout:MismatchedLengths'))
end
end

function iAssertNoSpatialChannelOrUnspecifiedNanSizes(sz, fmt)
idxSCU = ismember(fmt, 'SCU');
if any(isnan(sz(idxSCU)))
    error(message('nnet_cnn:layer:GenericInputLayer:NanValueSpecifiedForSCUDim'))
end
end

function iAssertNoTrailingSingletonUnspecifiedDimsAfterPermute(sz, fmt)
% Permute size and formats to dlarray format ordering to check that it
% doesn't result in any trailing singleton U dims
[orderedFmt, permIdx] = deep.internal.format.orderFormat(fmt);
orderedSz = sz(permIdx);
if strcmp(orderedFmt(end), 'U') && orderedSz(end) == 1 && numel(orderedSz) > 2
    error(message('nnet_cnn:layer:GenericInputLayer:LayerCreatedWithTrailingSingletonUDim'))
end
end

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