function X = huber(X, T, W, options)
%

%   Copyright 2020-2023 The MathWorks, Inc.

arguments
    X
    T
    W {deep.internal.dlarray.mustBeNumericDLData} = 1
    options.TransitionPoint (1,1) {mustBeNumeric, mustBeReal, mustBePositive}
    options.Mask {deep.internal.dlarray.mustBeBinaryMask}
    options.Reduction {mustBeMember(options.Reduction,["sum", "none"])}
    options.NormalizationFactor {mustBeMember(options.NormalizationFactor, ...
        ["batch-size", "all-elements", "mask-included", "none"])}
    options.DataFormat {deep.internal.dlarray.mustBeDataFormatType}
    options.WeightsFormat {deep.internal.dlarray.mustBeDataFormatType}
end

% Get default NVP arguments
[args, defaults] = iGetDefaultArguments(options);

% Extract transition point value if specified as dlarray
[args.TransitionPoint,X] = extractUnderlyingConstant(args.TransitionPoint,X,{'deep:dlarray:AcceleratedTransitionPointNotSupported'});

% Validate and extract input data
[X, xfd, xdata, tdata, wdata, obsDim] = validateAndExtractLossInputData(X, T, W, args.DataFormat, args.WeightsFormat);

% Validate mask and normalization factor
[mask, normFactor] = deep.internal.dlarray.validateLossNVPArguments(xdata, xfd, args, defaults);
% Gather transition point value to host
args.TransitionPoint = gather(args.TransitionPoint);

% Convert normalization factor values to their numeric equivalent 
normFactor = deep.internal.dlarray.convertNormalizationFactor(xdata, mask, normFactor, obsDim);

% Compute loss
loss = internal_huber(xdata, tdata, wdata, mask, normFactor, args.TransitionPoint);

% Insert data
X = deep.internal.dlarray.fromNumeric(loss, xfd, X);

% Remove dimensions from the dlarray
X = stripdims(X);

end

function [options, defaults] = iGetDefaultArguments(options)
% Default values
defaultTransitionPoint = 1;
defaultMask = 1;
defaultReduction = "sum";
defaultNormalizationFactor = "batch-size";
defaultDataFormat = '';
defaultWeightsFormat = '';

% Initialize defaults flag to true
defaults.TransitionPoint = true;
defaults.Mask = true;
defaults.Reduction = true;
defaults.NormalizationFactor = true;
defaults.DataFormat = true;
defaults.WeightsFormat = true;

if isempty(fieldnames(options))
    % Assign default values if no NVP argument is specified
    options.TransitionPoint = defaultTransitionPoint;
    options.Mask = defaultMask;
    options.Reduction = defaultReduction;
    options.NormalizationFactor = defaultNormalizationFactor;
    options.DataFormat = defaultDataFormat;
    options.WeightsFormat = defaultWeightsFormat;
else
    nvpNames = fieldnames(defaults);
    for i = 1:numel(nvpNames)
        name = nvpNames{i};
        if ~isfield(options, name)

            switch name
                case 'TransitionPoint'
                    options.TransitionPoint = defaultTransitionPoint;

                case 'Mask'
                    options.Mask = defaultMask;

                case 'Reduction'
                    options.Reduction = defaultReduction;

                case 'NormalizationFactor'
                    options.NormalizationFactor = defaultNormalizationFactor;

                case 'DataFormat'
                    options.DataFormat = defaultDataFormat;

                case 'WeightsFormat'
                    options.WeightsFormat = defaultWeightsFormat;
            end
        else
            defaults.(name) = false;
        end
    end
end

end
