function [tf, P] = isLocalExtrema(A, isMaxSearch, varargin)
%ISLOCALEXTREMA Mark local minimum or maximum values in an array.
%
%   FOR INTERNAL USE ONLY -- This feature is intentionally undocumented.
%   Its behavior may change, or it may be removed in a future release.
%

%   Copyright 2017-2021 The MathWorks, Inc.

    [dim, maxNumExt, minSep, minProm, flatType, x, dataVars, pwin] = ...
        parseInputs(A, varargin{:});

    returnProminence = nargout > 1;
    
    if isa(A, 'tabular')
        [tf,P] = isLocalExtremaTabular(A, dataVars, maxNumExt, minSep, ...
            minProm, flatType, x, isMaxSearch, returnProminence, pwin);
    else
        if (dim > ndims(A))
            % There are no local extrema in scalar dimensions.
            tf = false(size(A));
            if returnProminence
                P = zeros(size(A), getProminenceType(A));
            end
        else
            % Operate along rows.
            if (dim ~= 1)
                permvec = [dim, 1:(dim-1), (dim+1):ndims(A)];
                A = permute(A, permvec);
            end
            
            [tf, P] = isLocalExtremaArray(A, maxNumExt, minSep, minProm,...
                flatType, x, isMaxSearch, returnProminence, pwin);
            
            % Reverse permutation.
            if (dim ~= 1)
                tf = ipermute(tf, permvec);
                if returnProminence
                    P = ipermute(P, permvec);
                end
            end
        end
        % Reconvert to sparse if the input was sparse.
        if issparse(A)
            tf = sparse(tf);
            if returnProminence
                P = sparse(P);
            end
        end
    end

end

%--------------------------------------------------------------------------
function [tf, P] = isLocalExtremaTabular(A, dataVars, maxNumExt, minSep,...
    minProm, flatType, x, isMaxSearch, returnProminence, pwin)
% Search for local extrema in the variables of a table.

    % Check for valid data variables.
    dvValid = varfun(@validDataVariableType, A, 'InputVariables',...
        dataVars, 'OutputFormat', 'uniform');
    if ~all(dvValid)
        if any(varfun(@(x) isnumeric(x) && ~isreal(x), A, ...
            'InputVariables', dataVars, 'OutputFormat', 'uniform'))
            error(message("MATLAB:isLocalExtrema:ComplexInputArray"));
        end
        error(message("MATLAB:isLocalExtrema:NonNumericTableVar"));
    end
    
    % All variables must be column vectors or empties.
    varsAreColumns = varfun(@(x) iscolumn(x) || (isempty(x) && ...
        ismatrix(x) && all(size(x, 1:2) <= [0 1])), A, ...
        'InputVariables', dataVars, 'OutputFormat', 'uniform');
    if ~all(varsAreColumns)
        error(message("MATLAB:isLocalExtrema:NonVectorTableVariable"));
    end
    
    % The data can be processed as one large array if all of the variables
    % are the same type and if they are all columnar data.
    vtypes = varfun(@class, A, 'InputVariables', dataVars, ...
        'OutputFormat', 'cell');
    vtypes = unique(string(vtypes));
    tf = false(height(A), width(A));
    P = A(:, dataVars);
    if (numel(vtypes) == 1)
        % Process all table variables at once.
        [tf(:,dataVars), P{:,1:end}] = isLocalExtremaArray(...
            A{:,dataVars}, maxNumExt, minSep, minProm, flatType, x, ...
            isMaxSearch, returnProminence, pwin);
    else
        % Process table variables one at a time.
        for i = 1:length(dataVars)
            [tf(:,dataVars(i)), P.(i)] = isLocalExtremaArray(...
                A.(dataVars(i)), maxNumExt, minSep, minProm, flatType, ...
                x, isMaxSearch, returnProminence, pwin);
        end
    end
end

%--------------------------------------------------------------------------
function [tf, P] = isLocalExtremaArray(A, maxNumExt, minSep, minProm, ...
    flatType, x, isMaxSearch, returnProminence, pwin)
% Search for local extrema in an array.

    if isempty(A)
        tf = false(size(A));
        P = zeros(size(A), getProminenceType(A));
        return;
    end

    % Avoid allocating a lot of memory for a sparse logical array.
    sparseLogicalMin = islogical(A) && issparse(A) && ~isMaxSearch;
    if isa(A, 'integer')
        cls = class(A);
        % Convert unsigned types to signed types to preserve diff sign. 
        % For integer types, prominence is always be an unsigned type.
        if cls(1) == 'u'
            scls = cls(2:end);
            imax = cast(intmax(scls), cls);
            B = zeros(size(A), scls);
            idx = A <= imax;
            B(idx) = cast(A(idx), scls) - intmax(scls) - 1;
            B(~idx) = cast(A(~idx) - imax - 1, scls);
            A = B;
        end
        % Reverse the sign of A, and then correct for the elements
        % that were saturated from intmin to intmax.
        if ~isMaxSearch
            satIdx = A == intmin(class(A));
            A = -A;
            A(~satIdx) = A(~satIdx)-1;
        end
    else
        if ~isMaxSearch
            if islogical(A) && ~issparse(A)
                A = ~A;
            else
                A = -A;
            end
        end
    end

    % Cast prominence threshold to the correct class.
    minProm = cast(minProm, getProminenceType(A));
    if sparseLogicalMin
        minProm = double(minProm ~= 0);
    end

    % Reshape A so that we only have 2 dimensions.
    sz = size(A);
    A = reshape(A, size(A,1), []);
    
    if isfloat(A) % single and double
        if issparse(A)
            P = zeros(size(A), 'like', A);
            tf = sparse([], [], false(0,0), size(A,1), size(A,2), nnz(A));
        else
            tf = false(size(A));
            P = zeros(size(A), getProminenceType(A));
        end
        % Skip columns that have no non-zeros.
        nontrivialColumns = any(A);
        % Do all columns that have no NaN values.
        nanColumns = any(isnan(A));
        batchCols = ~nanColumns & nontrivialColumns;
        if any(batchCols)
            [tf(:, batchCols), P(:, batchCols)] = doLocalMaxSearch(...
                A(:, batchCols), maxNumExt, minSep,  minProm, flatType, ...
                x, returnProminence, pwin);
        end
        % For each remaining column, remove the points that are NaN.
        nanColumns = find(nanColumns);
        if ~isempty(nanColumns)
            for j = 1:length(nanColumns)
                jdx = nanColumns(j);
                if issparse(A)
                    % Extract the non-NaN rows and compute the result
                    nanRows = find(isnan(A(:,jdx)));
                    [Asub, xsub] = subsampleInputColumnAndSamplePoints(...
                        A, x, jdx, nanRows, minSep, pwin);
                    [tfsub, Psub] = doLocalMaxSearch(Asub, maxNumExt, ...
                        minSep, minProm, flatType, xsub, ...
                        returnProminence, pwin);
                    % Convert the results to fit in the output array.
                    [tf(:,jdx), P(:,jdx)] = resampleResults(tfsub, Psub,...
                        size(A,1), nanRows);
                else
                    % Subsample the input and output.
                    idx = ~isnan(A(:,jdx));
                    if isempty(x)
                        xsub = find(idx);
                    else
                        xsub = x(idx);
                    end
                    [tf(idx,jdx), P(idx,jdx)] = doLocalMaxSearch(...
                        A(idx,jdx), maxNumExt, minSep, minProm, ...
                        flatType, xsub, returnProminence, pwin);
                end
            end
        end
    else % Integer types
        [tf, P] = doLocalMaxSearch(A, maxNumExt, minSep, minProm, ...
            flatType, x, returnProminence, pwin);
    end

    % Reshape the result to the original size of A.
    tf = reshape(tf, sz);
    if returnProminence
        P = reshape(P, sz);
        if sparseLogicalMin
            % Ensure the correct class for the output.
            P = logical(P);
        end
    end

end

%--------------------------------------------------------------------------
function [maxVals, P] = doLocalMaxSearch(A, maxNumExt, minSep, minProm, ...
    flatType, x, returnProminence, pwin)
% Search for local maxima in an array without NaN.

    if issparse(A)
        P = zeros(size(A), 'like', A);
    else
        P = zeros(size(A), getProminenceType(A));
    end
    if size(A,1) < 3
        maxVals = false(size(A));
        return;
    end

    % Replace all positive infinites with NaN temporarily.
    if isfloat(A)
        infMaxVals = isinf(A) & (A > 0);
        A(infMaxVals) = NaN;
    end

    % Get the local maxima and inflection points.
    flatTypeIsLast = strcmp(flatType, 'last');
    if flatTypeIsLast
        % Flip the array for the search, then revert the flip.
        [maxVals, inflectionPts] = getAllLocalMax(flip(A, 1));
        maxVals = flip(maxVals, 1);
        inflectionPts = flip(inflectionPts, 1);
    else
        [maxVals, inflectionPts] = getAllLocalMax(A);
    end

    % Recombine the finite and infinite local maxima.
    if isfloat(A)
        maxVals = maxVals | infMaxVals;
        A(infMaxVals) = Inf;
    end
    
    % Calculate the prominence and filter local maxima based on the minumum
    % prominence criteria.
    filterByMaxNum = any(maxNumExt < sum(maxVals));
    calculateProminence = returnProminence || filterByMaxNum || (minSep > 0);
    if (minProm > 0) || calculateProminence
        if ~isempty(pwin) && isdatetime(x)
            xs = datetimeToComplex(x);
        elseif ~isempty(pwin) && isduration(x)
            xs = milliseconds(x);
        else
            xs = x;
        end
        if isduration(pwin)
            pwin = milliseconds(pwin);
        end
        if calculateProminence
            [maxVals,P] = matlab.internal.math.getProminence(A, maxVals, ...
                inflectionPts, minProm, xs, pwin);
        else
             maxVals = matlab.internal.math.getProminence(A, maxVals, ...
                inflectionPts, minProm, xs, pwin);
        end
    end

    % Filter local maxima based on distance.
    if (minSep > 0)
        % This will also restrict to the top N most prominent maxima.
        maxVals = filterByDistance(A, P, maxVals, x, minSep, ...
            flatTypeIsLast);
    end
    
    % Restrict to the top N most prominent local maxima.
    if any(maxNumExt < sum(maxVals))
        maxVals = restrictNumberOfExtrema(A, maxVals, maxNumExt, P);
    end

    % Adjust results in flat regions.
    if ~((strcmp(flatType, 'first') || strcmp(flatType, 'last')) && ...
            ~returnProminence)
        [maxVals, P] = adjustFlatRegions(A, maxVals, flatType, P, ...
            returnProminence);
    end

end

%--------------------------------------------------------------------------
function [maxVals, inflectionPts] = getAllLocalMax(A)
% Find all local maxima along the rows of A.

    if issparse(A)
        [maxVals, inflectionPts] = matlab.internal.math.getAllLocalMaxSparse(A);
        return;
    end

    % Find local maxima.
    s = sign(diff(A));

    nanMask = isnan(A);
    if any(nanMask(:))
        % Non-finites in A represent +Inf entries, and the sign mask must
        % reflect the behavior of +Inf.
        s(nanMask(1:(end-1),:)) = -1;
        s(~nanMask(1:(end-1),:) & nanMask(2:end,:)) = 1;
    end
    
    % Correct for repeated points.  To have the first local maxima of a
    % plateau marked as true, the sign of subsequent repeated values should
    % be set to the sign of the next non-zero difference.  This ensures
    % that the second order difference has the correct sign.
    if ~all(s(:))
        if isinteger(A)
            s = single(s);
        end
        s(s == 0) = NaN;
        s = fillmissing(s, 'next', 'EndValues', 'nearest');
    end

    % Find points where the second order difference is negative and pad the
    % result to be of the correct size.
    pad = true(1, size(A,2));
    maxVals = [~pad; diff(s) < 0; ~pad];

    % Ignore repeated values.
    uniquePts = [pad; (A(2:end,:) ~= A(1:(end-1),:))];
    if isfloat(A) && any(isnan(A(:)))
        uniquePts = uniquePts & ...
            [pad; ~(isnan(A(2:end,:)) & isnan(A(1:(end-1),:)))];
    end

    % Get the inflection points: every place where the first order
    % difference of A changes sign.  Remove duplicate points.  Consider end
    % points inflection points.
    inflectionPts = [pad; (s(1:(end-1),:) ~= s(2:end, :)) & ...
        uniquePts(2:(end-1),:); pad];
end

%--------------------------------------------------------------------------
function maxVals = filterByDistance(A, P, maxVals, x, minSep, ...
    flatTypeIsLast)
% Remove local maxima that are too close to a larger local maxima.

    % Create a linear index array for each column.
    m = size(A,1);
    if ~issparse(A)
        idx = 1:m;
    end

    if issparse(x)
        % Convert implicit sample points to a function handle.
        x = @(k) subsampleImplicitSamplePoints(full(x), k);
    elseif isempty(x)
        x = @(k) k;
    end
    
    for j = 1:size(A,2)
        
        % Get the linear indices of all local maxima in this column.
        if ~issparse(A)
            locMaxima = idx(maxVals(:,j));
        else
            locMaxima = find(maxVals(:,j));
        end
        d = find(diff(A(:,j)));
        n = length(locMaxima);
        
        % Get the left and right indices for each local maxima in this
        % column.  These indices dictate the extent of each local maxima.
        leftIndices = x(locMaxima);
        rightIndices = leftIndices;
        if flatTypeIsLast
            % When 'FlatSelection' is 'last', each flat region is marked by
            % its rightmost point, so we need to extend the left side.
            for i = 1:n
                % Index of the current local maximum
                k = locMaxima(i);
                % Find how many elements it repeats to the left.
                if isfinite(A(k,j))
                    leftIdx = find(d < k, 1, 'last');
                    assert(~isempty(leftIdx));
                    leftIndices(i) = x(d(leftIdx));
                end
            end
        else
            % For all other 'FlatSelection' types, each flat region is
            % marked by the rightmost point, so we need to extend the
            % right side.
            for i = 1:n
                % Index of the current local maximum
                k = locMaxima(i);
                % Find how many elements it repeats to the right.
                if isfinite(A(k,j))
                    rightIdx = find(d >= k, 1, 'first');
                    assert(~isempty(rightIdx));
                    rightIndices(i) = x(d(rightIdx));
                end
            end
        end
        
        % Iterate through each local maxima.
        left = 1;
        right = 1;
        for i = 1:n
            % Find those maxima to the left that are still within range.
            while ((leftIndices(i) - rightIndices(left)) >= minSep)
                left = left + 1;
            end
            % Find those maxima to the right that are still within range.
            right = max(i, right);
            while ((right <= (n-1)) && ...
                    ((leftIndices(right+1) - rightIndices(i)) < minSep))
                right = right + 1;
            end
            % If this local maxima is Inf, move on.
            if ~isfinite(A(locMaxima(i),j))
                continue;
            end
            % Otherwise, find all values we have to compare against.
            leftIdx = locMaxima(left:(i-1));
            % Remove local maxima we already filtered out.
            leftIdx(~maxVals(locMaxima(left:(i-1)),j)) = [];
            leftMax = max(P(leftIdx,j));
            rightMax = max(P(locMaxima((i+1):right), j));
            % Remove this local maxima if there is another to the left
            % or right within range that is larger.
            if ~isempty(leftMax) && (leftMax >= P(locMaxima(i),j))
                maxVals(locMaxima(i),j) = false;
            elseif ~isempty(rightMax) && (rightMax > P(locMaxima(i),j))
                maxVals(locMaxima(i),j) = false;
            end
        end
    end

end

%--------------------------------------------------------------------------
function maxVals = restrictNumberOfExtrema(A, maxVals, maxNumExt, P)
% Keep only the N largest local maxima in each column

    idx = 1:size(A,1);
    for j = 1:size(A,2)
        % Get the linear indices of all local maxima in this column.
        locMaxima = idx(maxVals(:,j));
        % Get the values of all local maxima.
        [~, sortedIdx] = sort(P(locMaxima,j), 'descend');
        maxVals(locMaxima(sortedIdx((maxNumExt+1):end)), j) = false;
    end
end

%---------------------------------------------------------------------------
function [left, right] = findUniqueRanges(v)
    d = diff(v);
    left = [1; 1 + find(d)];
    right = [left(2:end)-1; numel(v)];
end

%---------------------------------------------------------------------------
function [maxVals, P] = adjustFlatRegions(A, maxVals, flatType, P, ...
    returnProminence)
% Compensate for flat regions.

    flatTypeValue = find(strcmp(flatType, ...
        { 'center', 'first', 'last', 'all' }));
    if returnProminence
        % If we are returning the prominence, we must set it correctly
        % for flat regions.
        colRng = find(any(logical(P) | maxVals,1));
    else
        % Otherwise just evaluate the regions for the final maxima
        % list.
        colRng = find(any(maxVals,1));
    end
    
    % Go through the columns.
    for j = colRng
        % Find the locations of all unique leading values.
        [leftRange, rightRange] = findUniqueRanges(A(:,j));
        flatRegion = rightRange > leftRange;
        % Nothing to do for columns that have no flat regions.
        if ~any(flatRegion)
            continue;
        end
        % Adjust the local maxima - if flatType is 'first' or 'last', they
        % are already correct.
        if (flatTypeValue == 1) || (flatTypeValue == 4)
            % Determine which unique leading values are maxima.
            ismax = maxVals(leftRange,j) & flatRegion;
            if any(ismax)
                ranges = [leftRange(ismax), rightRange(ismax)];
                if (flatTypeValue == 1)
                    maxVals(ranges(:,1), j) = false;
                    maxVals(floor(mean(ranges,2)), j) = true;
                else %flatTypeValue == 4
                    for k = 1:size(ranges,1)
                        maxVals(ranges(k,1):ranges(k,2), j) = true;
                    end
                end
            end
        end
        % Adjust the prominences.
        if ~returnProminence
            continue;
        end
        % Pull the prominence from whichever side it has been stored on.
        if flatTypeValue ~= 3
            % For everything except 'last', this is the left edge.
            Pvals = P(leftRange, j);
        else
            % For 'last', it is the right edge.
            Pvals = P(rightRange, j);
        end
        % Only consider points with non-zero prominence that are flat
        % regions.
        hasprom = (Pvals > 0) & flatRegion;
        if any(hasprom)
            % Subset the values and get the ranges.
            Pvals = Pvals(hasprom);
            ranges = [leftRange(hasprom), rightRange(hasprom)];
            for k = 1:size(ranges,1)
                P(ranges(k,1):ranges(k,2), j) = Pvals(k);
            end
        end
    end
    

end

%--------------------------------------------------------------------------
function tf = isRealFiniteNonNegativeScalar(A, strictlyPositive)
% Determine if an input is a real, finite, non-negative scalar.

    tf = true;
    if ~isduration(A)
        tf = isreal(A);
    end
    if strictlyPositive
        tf = tf && isscalar(A) && (A > 0) && isfinite(A);
    else
        tf = tf && isscalar(A) && (A >= 0) && isfinite(A);
    end

end

%--------------------------------------------------------------------------
function tf = checkWindowBounds(A, x, pwin, dim)
% Determines if the prominence window is large enough that it can be
% ignored
    if isempty(x)
        xbnds = [1 size(A,dim)];
    else
        xbnds = [x(1), x(size(A,dim))];
    end
    if isscalar(pwin)
        tf = (pwin/2) > (xbnds(2) - xbnds(1));
    else
        tf = all(pwin > (xbnds(2)-xbnds(1)));
    end
end

%--------------------------------------------------------------------------
function [dim, maxNumExtrema, minSep, minProm, flatType, x, dataVars, pwin] = ...
    parseInputs(A, varargin)
% Parse input arguments.

    % Validate input array type.
    if isnumeric(A) || islogical(A)
        if ~isreal(A)
            error(message('MATLAB:isLocalExtrema:ComplexInputArray'));
        end
        AisTabular = false;
    elseif isa(A, 'table')  || isa(A, 'timetable')
        AisTabular = true;
    else
        error(message('MATLAB:isLocalExtrema:FirstInputInvalid'));
    end

    % Set default parameters.
    if AisTabular
        dim = 1;
        dataVars = 1:width(A);
    else
        dim = find(size(A) ~= 1,1); % default to first non-singleton dim
        if isempty(dim)
            dim = 2; % dim = 2 for scalar and empty A
        end
        dataVars = []; % not supported for arrays
    end
    minSep = 0;
    minProm = 0;
    flatType = 'center';
    x = [];
    userGaveMinSep = false;
    pwin = [];

    % Parse dim input.
    argIdx = 1;
    if (nargin > 1) && (isnumeric(varargin{argIdx}) || ...
            islogical(varargin{argIdx}))
        if AisTabular
            error(message('MATLAB:isLocalExtrema:DimensionTable'));
        end
        dim = varargin{argIdx};
        argIdx = 2;
        if ~isRealFiniteNonNegativeScalar(dim, true) || (fix(dim) ~= dim)
            error(message('MATLAB:isLocalExtrema:DimensionInvalid'));
        end
    end
    maxNumExtrema = size(A, dim);

    % Parse Name-Value Pairs.
    numRemainingArguments = nargin-1-(argIdx-1);
    if rem(numRemainingArguments, 2) ~= 0
        error(message('MATLAB:isLocalExtrema:NameValuePairs'));
    end

    nameOptions = [ "MaxNumExtrema", "MinSeparation", "MinProminence", ...
        "FlatSelection", "SamplePoints", "DataVariables", ...
        "ProminenceWindow"];
    spvar = [];
    for i = 1:2:numRemainingArguments
        if ~((ischar(varargin{argIdx}) && isrow(varargin{argIdx})) || ...
             (isstring(varargin{argIdx}) && isscalar(varargin{argIdx})))
            if AisTabular
                error(message('MATLAB:isLocalExtrema:InvalidNameTable'));
            else
                error(message('MATLAB:isLocalExtrema:InvalidNameArray'));
            end
        end
        optionMatches = startsWith(nameOptions, varargin{argIdx}, ...
            'IgnoreCase', true);
        % Multiple or no matches.
        if sum(optionMatches) ~= 1
            if AisTabular
                error(message('MATLAB:isLocalExtrema:InvalidNameTable'));
            else
                error(message('MATLAB:isLocalExtrema:InvalidNameArray'));
            end
        end
        if optionMatches(1) % MaxNumExtrema
            maxNumExtrema = varargin{argIdx+1};
            if ~(isnumeric(maxNumExtrema) || islogical(maxNumExtrema)) || ...
               ~isscalar(maxNumExtrema) || ~isreal(maxNumExtrema) || ...
               (maxNumExtrema <= 0) || (fix(maxNumExtrema) ~= maxNumExtrema)
                error(message('MATLAB:isLocalExtrema:MaxNumInvalid'));
            end
        elseif optionMatches(2) % MinSeparation
            minSep = varargin{argIdx+1};
            if ~isRealFiniteNonNegativeScalar(minSep, false)
                error(message('MATLAB:isLocalExtrema:MinSeparationInvalid'));
            end
            userGaveMinSep = true;
        elseif optionMatches(3) % MinProminence
            minProm = varargin{argIdx+1};
            if ~(isnumeric(minProm) || islogical(minProm)) || ...
               ~isRealFiniteNonNegativeScalar(minProm, false)
                error(message('MATLAB:isLocalExtrema:MinProminenceInvalid'));
            end
        elseif optionMatches(4) % FlatSelection
            flatOptions = [ "all", "first", "center", "last"];
            opt = varargin{argIdx+1};
            if ~((ischar(opt) && isrow(opt)) || ...
                 (isstring(opt) && isscalar(opt)))
                error(message('MATLAB:isLocalExtrema:FlatSelectionInvalid'));
            else
                tf = startsWith(flatOptions, varargin{argIdx+1}, ...
                    'IgnoreCase', true);
                if sum(tf) ~= 1 % No or multiple matches.
                    error(message('MATLAB:isLocalExtrema:FlatSelectionInvalid'));
                end
                flatType = flatOptions(tf);
            end
        elseif optionMatches(5) % SamplePoints
            if isa(A,'timetable')
                error(message('MATLAB:samplePoints:SamplePointsTimeTable'));
            end
            x = varargin{argIdx+1};
            [x,spvar] = matlab.internal.math.checkSamplePoints(x,A,AisTabular,false,dim);
            if ~(isfloat(x) || isduration(x) || isdatetime(x))
                error(message('MATLAB:samplePoints:SamplePointsInvalidDatatype'));
            end
            % If A is empty and x is a valid varspec, everything flows through
            % checkSamplePoints but will not flow through checkWindowBounds
            % unless we set x = []
            if isempty(A) && ~isempty(x)
                x = [];
            end
        elseif optionMatches(6) % DataVariables
            if AisTabular
                dataVars = matlab.internal.math.checkDataVariables(A,...
                    varargin{argIdx+1},'isLocalExtrema');
            else
                error(message('MATLAB:isLocalExtrema:DataVariablesArray'));
            end
        else % ProminenceWindow
            pwin = varargin{argIdx+1};
            validPwin = (isnumeric(pwin) && isreal(pwin)) || ...
                isduration(pwin);
            if validPwin
                if isscalar(pwin)
                    validPwin = ~isnan(pwin) && (pwin > 0);
                else
                    validPwin = all(~isnan(pwin(:))) && all(pwin(:) >= 0) && ...
                        isvector(pwin) && (numel(pwin) == 2);
                end
            end
            if ~validPwin
                error(message('MATLAB:isLocalExtrema:ProminenceWindowLengthInvalid'));
            end
                
        end
        argIdx = argIdx + 2;
    end
    if ~isempty(spvar)
        dataVars(dataVars == spvar) = [];
    end
    % Set default sample points.
    if isempty(x)
        if isa(A, 'timetable')
            x = matlab.internal.math.checkSamplePoints(A.Properties.RowTimes,A,false,true,dim);
        else
            if ~issparse(A)
                % We don't want to explicitly construct row indices for sparse.
                x = 1:size(A,dim);
            end
        end
    end

    % Check that minimum separation is in the correct units.
    if isa(x, 'duration') || isa(x, 'datetime')
        if ~isa(minSep, 'duration')
            % Error if user gave a non-duration minimum separation.
            if userGaveMinSep
                if isa(A, 'timetable')
                    error(message('MATLAB:isLocalExtrema:SeparationMustBeDurationTimetable'));
                else
                    error(message('MATLAB:isLocalExtrema:SeparationMustBeDuration'));
                end
            end
            minSep = milliseconds(0);
        end
        if ~isempty(pwin) && ~isa(pwin, 'duration')
            if isa(A, 'timetable')
                error(message('MATLAB:isLocalExtrema:ProminenceWindowMustBeDurationTimetable'));
            else
                error(message('MATLAB:isLocalExtrema:ProminenceWindowMustBeDuration'));
            end
        end
    else
        if isa(minSep, 'duration')
            % Error if user gave a duration minimum separation.
            error(message('MATLAB:isLocalExtrema:SeparationCannotBeDuration'));
        end
        if ~isempty(pwin) && isa(pwin, 'duration')
            % Error if user gave a duration prominence window.
            error(message('MATLAB:isLocalExtrema:ProminenceWindowCannotBeDuration'));
        end
    end
    
    % See if we can ignore the prominence window.
    if checkWindowBounds(A, x, pwin, dim)
        pwin = [];
    end
    % Convert the prominence window from sparse to full.
    pwin = full(pwin);

    % For efficiency, we can check if we can just totally ignore sample
    % points and minimum separation.
    if (numel(x) < 2)
        md = 1;
    else
        md = min(diff(x));
    end
    if (minSep < md) && isempty(pwin)
        minSep = 0;
        x = [];
    end

end % parseInputs

%--------------------------------------------------------------------------
function [Asub, xsub] = subsampleInputColumnAndSamplePoints(A, x, j, ...
    nanRows, minSep, pwin)
% Subsample the array and sample points.
    % Create a NaN-free sparse column.
    Asub = A(:,j);
    Asub(nanRows) = [];
    if (minSep == 0) && isempty(pwin)
        % Don't have to bother with sample points if we won't use them.
        xsub = [];
    else
        if isempty(x)
            % Place the indices to be filtered in a sparse array.
            xsub = sparse(nanRows(:));
        else
            % Make explicit sample points
            xsub = x;
            xsub(nanRows) = [];
        end
    end
end

%--------------------------------------------------------------------------
function [tf, P] = resampleResults(tfsub, Psub, m, nanRows)
% Resampled the subsampled results for the answer.
    tfsub = find(tfsub);
    for i = 1:numel(tfsub)
        tfsub(i) = subsampleImplicitSamplePoints(nanRows, tfsub(i));
    end
    PsubIdx = find(Psub);
    for i = 1:numel(PsubIdx)
        PsubIdx(i) = subsampleImplicitSamplePoints(nanRows, PsubIdx(i));
    end
    % Assign the columns.
    tf = sparse(tfsub, 1, true, m, 1);
    P = sparse(PsubIdx, 1, nonzeros(Psub), m, 1);
end

%--------------------------------------------------------------------------
function k = subsampleImplicitSamplePoints(removedIndices, k)
% Subsample the implicit sampling points
    m = numel(removedIndices);
    k = k(:)' + (0:m)';
    k(k >= [removedIndices(:); inf]) = NaN;
    k = min(k);
end

%--------------------------------------------------------------------------
function tf = validDataVariableType(x)
% Indicates valid data types for table variables
    tf = (isnumeric(x) || islogical(x)) && isreal(x) && ...
        ~(isinteger(x) && ~isreal(x));
end

%--------------------------------------------------------------------------
function pt = getProminenceType(A)
% Return the data type of the prominence
    if isinteger(A) && startsWith(class(A), 'i')
        pt = ['u', class(A)];
    else
        pt = class(A);
    end
end

%--------------------------------------------------------------------------
function a = datetimeToComplex(b)
% Convert datetimes to complex numbers 

    y = year(b);
    m = month(b);
    d = day(b);
    h = hour(b);
    mn = minute(b);
    sc = floor(second(b));

    % Extract milliseconds
    ms = milliseconds(b - datetime(y,m,d,h,mn,sc));
    data = {y, m, d, h, mn, sc, ms};
    a = matlab.internal.datetime.createFromDateVec(data, '');

end