function [n,c,s] = normalize(a,varargin)
%MATLAB Code Generation Library Function

%   Limitations:
%   When the method types for 'center' and 'scale' are both tables and
%   'DataVariables' is not provided, then the method types must have table
%   variable names in the same order.

%   Copyright 2018-2023 The MathWorks,Inc.
%#codegen

narginchk(1,inf);
ISTABULAR = isa(a,'table') || isa(a,'timetable');
ONE = coder.internal.indexInt(1);
dimProvided = false;
methodProvided = false;
method2Provided = false;
nextArg = 1;
% Validate inputs
if nargin > 1
    if isnumeric(varargin{1})
        coder.internal.assert(~ISTABULAR,'MATLAB:normalize:TableDIM');
        coder.internal.assertValidDim(varargin{1});
        dim = coder.internal.indexInt(varargin{1});
        dimProvided = true;
        nextArg = 2;
    end
    nargs = length(varargin);
    
    if nargs >= nextArg
        coder.internal.assert(coder.internal.isTextRow(varargin{nextArg}) || nargs > nextArg,'MATLAB:normalize:InvalidMethod');
        coder.internal.assert(coder.internal.isTextRow(varargin{nextArg}) || ISTABULAR,'MATLAB:normalize:DataVariablesArray');
        coder.internal.assert(coder.internal.isTextRow(varargin{nextArg}),'MATLAB:normalize:ParseFlags');
        len = coder.internal.indexInt(strlength(varargin{nextArg}));
        if ~strncmpi(varargin{nextArg},'DataVariables',len) ...
                && ~strncmpi(varargin{nextArg},'ReplaceValues',len)
            method = coder.const(getMethod(varargin{nextArg},nextArg+1));
            if nargs > nextArg && ~strncmpi(varargin{nextArg+1},'DataVariables',len) ...
                    && ~strncmpi(varargin{nextArg+1},'ReplaceValues',len) ...
                    && ~(strcmp(method,{'center'}) && strncmpi(varargin{nextArg+1},'scale',len)) ...
                    && ~(strcmp(method,{'scale'}) && strncmpi(varargin{nextArg+1},'center',len))
                mtValue = varargin{nextArg + 1};
                methodType = coder.const(getMethodType(method,mtValue,nextArg+2,ISTABULAR));
                nextArg = nextArg+1;
            elseif strcmp(method,'zscore')
                methodType = 'std';
                mtValue = 0;
            elseif strcmp(method,'norm')
                methodType = 'value';
                mtValue = 2;
            elseif strcmp(method,'center')
                methodType = 'mean';
                mtValue = 0;
            elseif strcmp(method,'scale')
                methodType = 'std';
                mtValue = 0;
            elseif strcmp(method,'range')
                methodType = 'value';
                mtValue = [0,1];
            elseif strcmp(method,'medianiqr')
                methodType = 'none';
                mtValue = 0;
            end
            nextArg = nextArg+1;
            methodProvided = true;
        end
        % Parse second method if first is 'center' or 'scale'
        if nargs >= nextArg && ~strncmpi(varargin{nextArg},'DataVariables',len) ...
                && ~strncmpi(varargin{nextArg},'ReplaceValues',len) ...
                && any(strcmp(method,{'center','scale'}))
            methodList = {'center','scale'};
            idx = ~strcmp(method,methodList);
            validMethod = {methodList{idx}};
            method2 = coder.const(getMethod(varargin{nextArg},nextArg+1,validMethod));
            if nargs > nextArg && ~strncmpi(varargin{nextArg+1},'DataVariables',len) ...
                    && ~strncmpi(varargin{nextArg+1},'ReplaceValues',len)
                mtValue2 = varargin{nextArg + 1};
                methodType2 = coder.const(getMethodType(method2,mtValue2,nextArg+2,ISTABULAR));
                nextArg = nextArg+1;
            elseif strcmp(method2,'center')
                methodType2 = 'mean';
                mtValue2 = 0;
            elseif strcmp(method2,'scale')
                methodType2 = 'std';
                mtValue2 = 0;
            end
            nextArg = nextArg+1;
            method2Provided = true;
        end
    end
end

if ~methodProvided
    method = 'zscore';
    methodType = 'std';
    mtValue = 0;
end

% Parse DataVariables and ReplaceValues
parms = {'DataVariables','ReplaceValues'};
poptions = struct( ...
    'CaseSensitivity',false, ...
    'PartialMatching','unique', ...
    'StructExpand',false, ...
    'IgnoreNulls',false);
pstruct = coder.internal.parseParameterInputs( ...
    parms,poptions,varargin{nextArg:end});
coder.internal.assert(ISTABULAR || pstruct.DataVariables == 0,'MATLAB:normalize:DataVariablesArray');
coder.internal.assert(ISTABULAR || pstruct.ReplaceValues == 0,'MATLAB:normalize:ReplaceValuesArray');
replaceValues = coder.internal.getParameterValue(pstruct.ReplaceValues,true,varargin{nextArg:end});
coder.internal.assert(coder.internal.isConst(replaceValues), ...
        'Coder:toolbox:OptionInputsMustBeConstant','ReplaceValues');
validateattributes(replaceValues,{'logical'}, ...
    {'scalar'},mfilename,'ReplaceValues',pstruct.ReplaceValues + nextArg + 1);

if ISTABULAR
    % Update dataVars if NV pair is not provided but methodType/methodType2 is table
    if pstruct.DataVariables == 0
        if strcmp(methodType,'table')
            dataVarsTmp = mtValue.Properties.VariableNames;
            if method2Provided && strcmp(methodType2,'table')
                vars = mtValue2.Properties.VariableNames;
                % This introdues a limitation that when method types are
                % both tables, then the table variables must be in the same
                % order. The things we are comparing are cell arrays and we
                % can't use setxor or sort so we don't have a good way to
                % see if they have the same names but in a different order.
                coder.internal.assert(isequal(dataVarsTmp,vars),'MATLAB:normalize:InvalidCenterScaleType');
            end
        elseif method2Provided && strcmp(methodType2,'table')
            dataVarsTmp = mtValue2.Properties.VariableNames;
        else
            dataVarsTmp = coder.internal.getParameterValue( ...
                pstruct.DataVariables,1:width(a),varargin{nextArg:end});
        end
    else
        dataVarsTmp = coder.internal.getParameterValue( ...
            pstruct.DataVariables,1:width(a),varargin{nextArg:end});
    end
    % Check dataVars if methodType or methodType2 is a table
    dataVars = validateDataVariables(a,dataVarsTmp,'normalize');
    % dataVars has to be validated separately from applyToTableVariables
    % because its value will be used before applyToTableVariables is called
    if pstruct.DataVariables ~= 0
        aVarNames = a.Properties.VariableNames;
        if islogical(dataVars) || isnumeric(dataVars)
            varNames = {aVarNames{dataVars}};
        else
            varNames = dataVars;
        end
        dataVarsInMethodType = true;
        dataVarsInMethodType2 = true;
        if strcmp(methodType,'table')
            tf = coder.const(feval('ismember',mtValue.Properties.VariableNames,varNames));
            dataVarsInMethodType = coder.const(feval('all',tf));
        end
        if method2Provided && strcmp(methodType2,'table')
            tf = coder.const(feval('ismember',mtValue2.Properties.VariableNames,varNames));
            dataVarsInMethodType2 = coder.const(feval('all',tf));
        end
        coder.internal.assert(dataVarsInMethodType && dataVarsInMethodType2,...
            'MATLAB:normalize:InvalidCenterScaleTypeWithDataVars');
    end
end

if ~dimProvided
    if ISTABULAR
        dim = ONE;
    else
        dim = coder.internal.constNonSingletonDim(a);
        coder.internal.assert(isscalar(a) || size(a,dim) ~= 1, ...
            'Coder:toolbox:autoDimIncompatibility');
    end
end

if ISTABULAR
    if method2Provided
        METHODTYPEISTABLE = strcmp(methodType,'table');
        METHODTYPE2ISTABLE = strcmp(methodType2,'table');
        if METHODTYPEISTABLE && ~METHODTYPE2ISTABLE
            normalizeFun = @(x,y) normalizeArray(x,method,methodType,y,dim,method2,methodType2,mtValue2,false);
            inputs = {mtValue};
        elseif ~METHODTYPEISTABLE && METHODTYPE2ISTABLE
            normalizeFun = @(x,y) normalizeArray(x,method,methodType,mtValue,dim,method2,methodType2,y,false);
            inputs = {mtValue2};
        elseif METHODTYPEISTABLE && METHODTYPE2ISTABLE
            normalizeFun = @(x,y,z) normalizeArray(x,method,methodType,y,dim,method2,methodType2,z,false);
            inputs = {mtValue,mtValue2};
        else
            normalizeFun = @(x) normalizeArray(x,method,methodType,mtValue,dim,method2,methodType2,mtValue2,false);
            inputs = {};
        end
    else
        if strcmp(methodType,'table')
            normalizeFun = @(x,y) normalizeArray(x,method,methodType,y,dim,[],[],[],false);
            inputs = {mtValue};
        else
            normalizeFun = @(x) normalizeArray(x,method,methodType,mtValue,dim,[],[],[],false);
            inputs = {};
        end
    end

    [nTmp,cTmp,sTmp] = coder.internal.applyToTableVariables(a, ...
        normalizeFun,dataVars,[true true true],[true false false], ...
        [replaceValues,true,true],'normalized', ...
        'MATLAB:normalize:DataVariablesTableSubscript',inputs{:});
    if isempty(nTmp) && height(nTmp) ~= 0
        c = array2table(zeros(1,0),'VariableNames',{});
        s = array2table(zeros(1,0),'VariableNames',{});
    else
        c = changeDimensionNames(cTmp,{'Row','Variables'});
        s = changeDimensionNames(sTmp,{'Row','Variables'});
    end
    if istimetable(a)
        n = table2timetableWithDimNames(nTmp,a.Properties.RowTimes,a.Properties.DimensionNames);
    else
        n = nTmp;
    end
else
    if method2Provided
        [n,c,s] = normalizeArray(a,method,methodType,mtValue,dim,method2,methodType2,mtValue2,false);
    else
        [n,c,s] = normalizeArray(a,method,methodType,mtValue,dim,[],[],[],false);
    end
end
end

%--------------------------------------------------------------------------

function method = getMethod(arg,argIndex,validMethods)
if nargin < 3
    validMethods = {'zscore','norm','center','scale','range','medianiqr'};
end
coder.internal.prefer_const(arg,argIndex);
coder.internal.assert(coder.internal.isConst(arg), ...
    'Coder:toolbox:OptionStringsMustBeConstant');
method = validatestring(arg,validMethods, mfilename,'Method',argIndex);
end

%--------------------------------------------------------------------------

function methodType = getMethodType(method,arg,argIndex,AisTabular)
coder.internal.prefer_const(method,arg,argIndex)
if coder.internal.isTextRow(arg)
    coder.internal.assert(coder.internal.isConst(arg), ...
        'Coder:toolbox:OptionStringsMustBeConstant');
end
if strcmp(method,'zscore')
    methodType = validatestring(arg,{'std','robust'},mfilename,'method type',argIndex);
elseif strcmp(method,'norm')
    if coder.internal.isTextRow(arg)
        methodType = validatestring(arg,{'inf'},mfilename,'method type',argIndex);
    else
        coder.internal.assert(isnumeric(arg) && isreal(arg) && ...
            coder.internal.isConstTrue(isscalar(arg)), ...
            'MATLAB:normalize:InvalidNormType');
        methodType = 'value';
    end
elseif strcmp(method,'center')
    if coder.internal.isTextRow(arg)
        methodType = validatestring(arg,{'mean','median'},mfilename,'method type',argIndex);
    elseif AisTabular && isa(arg,'tabular')
        methodType = 'table';
    else
        coder.internal.assert(isnumeric(arg),'MATLAB:normalize:InvalidCenterType');
        methodType = 'value';
    end
elseif strcmp(method,'scale')
    if coder.internal.isTextRow(arg)
        methodType = validatestring(arg,{'std','mad','first','iqr'},mfilename,'method type',argIndex);
    elseif AisTabular && isa(arg,'tabular')
        methodType = 'table';
    else
        coder.internal.assert(isnumeric(arg),'MATLAB:normalize:InvalidScaleType');
        methodType = 'value';
    end
elseif strcmp(method,'range')
    coder.internal.assert(isnumeric(arg) && isreal(arg) && ...
        coder.internal.isConst(size(arg)) && isvector(arg) && ...
        length(arg) == 2 && diff(arg) >= 0, ...
        'MATLAB:normalize:InvalidRangeType');
    methodType = 'value';
elseif strcmp(method,'medianiqr')
    coder.internal.assert(false,'MATLAB:normalize:IncorrectNumInputsArray');
end
end
