function [tf, P] = isLocalExtrema(A, isMaxSearch, varargin)
%ISLOCALEXTREMA Mark local minimum or maximum values in an array.
%MATLAB Code Generation Library Function

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

validateattributes(A,{'numeric','logical','table','timetable'},{'real'},mfilename,'A',1);
coder.internal.assert(~issparse(A), ...
    'Coder:toolbox:SparseNotSupported',mfilename);

isTableOrTimetable = isa(A,'table') || isa(A,'timetable');

coder.internal.prefer_const(varargin);

ZERO = coder.internal.indexInt(0);
ONE = coder.internal.indexInt(1);
nargs = coder.internal.indexInt(nargin - 2);
nextArg = ONE;
lastArg = nargs;

if nargs >= nextArg && eml_bitand(nargs - nextArg + ONE,ONE) == ONE
    dim = coder.internal.indexInt(varargin{nextArg});
    coder.internal.assert(~isTableOrTimetable,'MATLAB:isLocalExtrema:DimensionTable');
    coder.internal.assert(coder.internal.isConst(dim), ...
        'Coder:toolbox:DimMustBeConstant');
    coder.internal.assertValidDim(dim);
    nextArg = nextArg + 1;
else
    if isTableOrTimetable
        dim = ONE;
    else
        dim = coder.internal.constNonSingletonDim(A);
        coder.internal.assert(isscalar(A) || size(A,dim) ~= 1, ...
            'Coder:toolbox:autoDimIncompatibility');
    end
end

coder.internal.assert(eml_bitand(lastArg - nextArg + ONE,ONE) == ZERO, ...
    'MATLAB:isLocalExtrema:NameValuePairs');
% Parse NV pairs
parms = {'MinProminence','ProminenceWindow','FlatSelection', ...
    'MinSeparation','MaxNumExtrema','SamplePoints','DataVariables','OutputFormat'};
% Select any non-default parsing options.
poptions = struct( ...
    'PartialMatching','unique',...
    'StructExpand',false,...
    'IgnoreNulls',true,...
    'CaseSensitivity',false);
% Parse the inputs.
pstruct = coder.internal.parseParameterInputs(parms,poptions, ...
    varargin{nextArg:end});
% Retrieve parameter values.

% Validate MinProminence
minProm = coder.internal.getParameterValue( ...
    pstruct.MinProminence,0,varargin{nextArg:end});
if(pstruct.MinProminence)
    validateattributes(minProm,{'numeric','logical'}, ...
        {'real','scalar','nonnegative','finite'}, ...
        mfilename,'MinProminence');
end

% ProminenceWindow not supported
coder.internal.assert(pstruct.ProminenceWindow == 0,'MATLAB:isLocalExtrema:PromWindowNotSupported');

% Validate FlatSelection
if(pstruct.FlatSelection)
    flatTypeNV = coder.internal.getParameterValue( ...
        pstruct.FlatSelection,'center',varargin{nextArg:end});
    flatType = validatestring(flatTypeNV,{'all','first','center','last'}, ...
        mfilename,'FlatSelection');
else
    flatType = 'center';
end

% Validate MinSeparation
minSepTmp = coder.internal.getParameterValue( ...
    pstruct.MinSeparation,0,varargin{nextArg:end});
if(pstruct.MinSeparation)
    if isa(minSepTmp,'duration')
        validateattributes(minSepTmp,{'duration'}, ...
            {'real','scalar','finite'}, ...
            mfilename,'MinSeparation');
        validateattributes(milliseconds(minSepTmp),{'numeric'}, ...
            {'nonnegative'}, ...
            mfilename,'MinSeparation');
    else
        validateattributes(minSepTmp,{'numeric','logical'}, ...
            {'real','scalar','nonnegative','finite'}, ...
            mfilename,'MinSeparation');
    end
end

% Validate MaxNumExtrema
maxNumExt = coder.internal.getParameterValue( ...
    pstruct.MaxNumExtrema,size(A,dim),varargin{nextArg:end});
if(pstruct.MaxNumExtrema)
    validateattributes(maxNumExt,{'numeric','logical'}, ...
        {'real','scalar','positive','integer'}, ...
        mfilename,'MaxNumExtrema');
end

% Validate SamplePoints
coder.internal.assert(pstruct.SamplePoints==0 || ~isa(A,'timetable'),'MATLAB:isLocalExtrema:SamplePointsTimeTable')

if isa(A,'timetable')
    spTemp = A.Properties.RowTimes;
else
    spTemp = coder.internal.getParameterValue( ...
        pstruct.SamplePoints,1:size(A,dim),varargin{nextArg:end});
end

% Make the default minsep a duration if sample points are durations or
% datetime
if (isa(spTemp,'duration') || isa(spTemp,'datetime')) && ...
        pstruct.MinSeparation == 0
    minSepTmp2 = milliseconds(0);
else
    minSepTmp2 = minSepTmp;
end

if isa(spTemp,'duration')
    coder.internal.assert(isa(minSepTmp2,'duration'), ...
        'MATLAB:isLocalExtrema:SeparationMustBeDuration');
    minSep = milliseconds(minSepTmp2);
    spFloat = milliseconds(spTemp);
elseif isa(spTemp,'datetime')
    coder.internal.assert(isa(minSepTmp2,'duration'), ...
        'MATLAB:isLocalExtrema:SeparationMustBeDuration');
    minSep = minSepTmp2;
    spFloat = spTemp;
else
    coder.internal.assert(isnumeric(minSepTmp2), ...
        'MATLAB:isLocalExtrema:SeparationCannotBeDuration');
    minSep = minSepTmp2;
    spFloat = spTemp;
end

if pstruct.SamplePoints ~= 0
    % Only validate the length of spFloat for user-supplied sample points
    coder.internal.assert(numel(spFloat) == (size(A,dim) * ~isempty(A)), ...
        'MATLAB:isLocalExtrema:SamplePointsLength');
end

if(pstruct.SamplePoints || isa(A,'timetable'))
    if isa(spFloat,'datetime')
        if ~(isa(A,'timetable') && isempty(A))
            validateattributes(spFloat,{'datetime'}, ...
                {'vector','finite','real'},...
                mfilename,'SamplePoints')
            checkSPSortingDuplicates(spFloat,'isLocalExtrema', false);
        end
        sp = spFloat;
    else
        validateattributes(spFloat,{'double','single','duration','datetime'}, ...
            {'vector','increasing','finite','real'},...
            mfilename,'SamplePoints')
        % Duration and datetime are included here so that a more meaningful
        % error message is thrown.

        % For efficiency, we can check if we can just totally ignore sample
        % points and minimum separation.
        % Only perform this check if sample points are const
        if (coder.internal.isConst(spFloat))
            if (numel(spFloat) < 2)
                MD = 1;
            else
                SPDIFF = coder.const(feval('diff',spFloat));
                MD = coder.const(feval('min',SPDIFF));
            end
            if (minSep < MD)
                minSep = zeros(1,1,'like',minSep);
                sp = zeros(1,0,'like',spFloat);
            else
                sp = spFloat;
            end
        else
            sp = spFloat;
        end
    end
else
    sp = spFloat;
end

% Validate DataVariables
coder.internal.assert(isTableOrTimetable || pstruct.DataVariables == 0,'MATLAB:isLocalExtrema:DataVariablesArray');
if isTableOrTimetable
    dataVars = coder.internal.getParameterValue( ...
        pstruct.DataVariables,1:width(A),varargin{nextArg:end});
end

% Validate OutputFormat
if(pstruct.OutputFormat ~= 0)
    coder.internal.assert(isTableOrTimetable,'MATLAB:isLocalExtrema:OutputFormatArray');
    outputFormat = coder.internal.getParameterValue( ...
        pstruct.OutputFormat,'logical',varargin{nextArg:end});
    fmt = validatestring(outputFormat,{'logical','tabular'},'isLocalExtrema','OutputFormat');
    coder.internal.assert(coder.internal.isConst(fmt), ...
        'Coder:toolbox:OptionStringsMustBeConstant');
    tabularOutput = strcmp(fmt,'tabular');
else
    tabularOutput = false;
end

% Find local extrema
returnProminence = nargout > 1;
if (dim > ndims(A)) || isempty(A)
    tf = false(size(A));
    if isTableOrTimetable
        P = A;
    else
        P = zeros(size(A), getProminenceType(A));
    end
else
    fx = @(x) isLocalExtremaArray(x, maxNumExt, minSep, minProm,...
        flatType, sp, isMaxSearch, returnProminence, isTableOrTimetable);
    if isTableOrTimetable
        [tfTmp,PTmp] = coder.internal.applyToTableVariables(A,fx, ...
            dataVars,[tabularOutput true],[~tabularOutput, false], ...
            [true, true],'','MATLAB:isLocalExtrema:DataVariablesTableSubscript');
        if istimetable(A)
            if tabularOutput
                tf = table2timetable(tfTmp,'RowTimes',A.Properties.RowTimes);
            else
                tf = tfTmp;
            end
            P = table2timetable(PTmp,'RowTimes',A.Properties.RowTimes);
        else
            tf = tfTmp;
            P = PTmp;
        end
    else
        [tf,P] = coder.internal.applyToMultipleDims(fx,A,dim);
    end
end

%--------------------------------------------------------------------------
function [tf, P] = isLocalExtremaArray(AIn, maxNumExt, minSep, minPromIn,...
    flatType, x, isMaxSearch, returnProminence, isTableorTimetable)

%Re-check inputs in case A was a table
validateattributes(AIn,{'numeric','logical'},{'real'});

coder.internal.assert(~isTableorTimetable || iscolumn(AIn),...
    'MATLAB:isLocalExtrema:NonVectorTableVariable');

nonFiniteSupport = coder.internal.get_eml_option('NonFinitesSupport');
if isa(AIn, 'integer')
    cls = class(AIn);
    % 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(AIn), scls);
        idx = AIn <= imax;
        B(idx) = cast(AIn(idx), scls) - intmax(scls) - 1;
        B(~idx) = cast(AIn(~idx) - imax - 1, scls);
        A = B;
    else
        A = AIn;
    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
    A = AIn;
    if ~isMaxSearch
        if islogical(A)
            A = ~A;
        else
            A = -A;
        end
    end
end

% Cast prominence threshold to the correct class.
minProm = cast(minPromIn, getProminenceType(A));

if isfloat(A) && nonFiniteSupport
    isnanA = isnan(A);
else
    isnanA = false;
end

if nonFiniteSupport && isfloat(A) && ~coder.internal.scalarizedAll(@(x)~x,isnanA(:),1)
    tf = false(size(A));
    P = zeros(size(A), getProminenceType(A));
    
    for j = 1:coder.internal.prodsize(A,'above',1)
        % Subsample the input and output.
        idx = ~isnanA(:,j);
        xsub = x(idx);
        [tf(idx,j), P(idx,j)] = doLocalMaxSearch(...
            A(idx,j), maxNumExt, minSep, minProm, ...
            flatType, xsub, returnProminence);
    end
else
    [tf, P] = doLocalMaxSearch(A, maxNumExt, minSep, minProm, ...
        flatType, x, returnProminence);
end

%--------------------------------------------------------------------------
function [maxVals, P] = doLocalMaxSearch(A, maxNumExt, minSep, minProm, ...
    flatType, x, returnProminence)
% Search for local maxima in an array without NaN.
coder.noImplicitExpansionInFunction; % use of & and | with same-size operands (all size(A))

P = zeros(size(A), getProminenceType(A));

if size(A,1) < 3
    maxVals = false(size(A));
    return;
end

nonFiniteSupport = coder.internal.get_eml_option('NonFinitesSupport');
% Replace all positive infinites with NaN temporarily.
if nonFiniteSupport && 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 nonFiniteSupport && 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
    [maxVals, P] = filterByProminence(A, maxVals, P, inflectionPts, ...
        minProm, calculateProminence);
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

%--------------------------------------------------------------------------
function [maxVals, inflectionPts] = getAllLocalMax(A)
% Find all local maxima along the rows of A.
coder.noImplicitExpansionInFunction; % uses of & with same-size operands from context:
                                     % 1:(end-1) and (2:end)

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

nonFiniteSupport = coder.internal.get_eml_option('NonFinitesSupport');
if nonFiniteSupport
    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
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 isinteger(A)
    sType = single(s);
else
    sType = s;
end
if ~all(sType(:))
    % The following does these two lines of code without filling with NaN
    % or calling find
    % s(s == 0) = NaN;
    % s = fillmissing(s, 'next', 'EndValues', 'nearest');
    for j = 1:coder.internal.prodsize(sType,'above',1)
        b = sType(:,j);
        idx = b ~= 0;
        if sum(idx,1) > 1
            x = 1:size(sType,1);
            b(~idx) = interp1(x(idx),b(idx),x(~idx),'next','extrap');
        end
        ctr = 0;
        indBeg = 1;
        indEnd = numel(idx);
        for k = 1:numel(idx)
            if idx(k) == true
                if ctr == 0
                    indBeg = k;
                end
                indEnd = k;
                ctr = ctr + 1;
            end
        end
        b(1:indBeg-1)   = sType(indBeg,j);
        b(indEnd+1:end) = sType(indEnd,j);
        sType(:,j) = b;
    end
end

% Find points where the second order difference is negative and pad the
% result to be of the correct size.
pad = true(1, coder.internal.prodsize(A,'above',1));
maxVals = false(size(A));
maxVals(2:size(A,1)-1,:) = diff(sType) < 0;

% Ignore repeated values.
uniquePts = [pad; (A(2:end,:) ~= A(1:(end-1),:))];
if isfloat(A) && any(isnan(A(:))) && nonFiniteSupport
    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; (sType(1:(end-1),:) ~= sType(2:end, :)) & ...
    uniquePts(2:(end-1),:); pad];

%--------------------------------------------------------------------------
function [maxVals, P] = filterByProminence(A, maxVals, P, inflectionPts, ...
    minProm, returnProminence)
% Remove local maxima not satisfying a prominence criteria.
coder.noImplicitExpansionInFunction; % use of | with same-size operands: 
                                     % maxVals(:,j) | inflectionPts(:,j)
                                     % where maxVals and inflectionPts have same size

% Go through columns of the input.
idx = 1:size(A,1);
m = min(A);

for j = 1:coder.internal.prodsize(A,'above',1)
    % Pull out local maxima and local minima.
    extremaList = idx(maxVals(:,j) | inflectionPts(:,j));
    isLocMax = maxVals(idx,j);
    
    % Iterate through all extrema.
    for i = 1:length(extremaList)
        % Skip localMins.
        if ~isLocMax(extremaList(i))
            continue;
        end
        localMaxValue = A(extremaList(i),j);
        
        % Non-finite points.
        if ~isfinite(localMaxValue)
            P(extremaList(i),j) = coder.internal.inf;
            continue;
        end
        
        % If we filter by prominence but don't need to return the
        % computed prominence values, then do a quick skip if this
        % local maxima could not possibly fit the criteria.
        if ~returnProminence
            if isa(A, 'integer')
                if unsignedDiff(localMaxValue, m(j)) < minProm
                    maxVals(extremaList(i),j) = false;
                    continue;
                end
            elseif (localMaxValue - m(j)) < minProm
                maxVals(extremaList(i),j) = false;
                continue;
            end
        end
        
        % Initialize the lowest points on either side.
        localMins = localMaxValue([1 1]);
        left = i-1;
        right = i+1;
        % Search points to the left
        while left > 0
            if ~isLocMax(extremaList(left))
                % Update left local minimum if this point is a minimum.
                localMins(1) = min(localMins(1), ...
                    A(extremaList(left),j));
            elseif A(extremaList(left),j) > localMaxValue
                % Exit if we found a larger local maximum.
                break;
            end
            left = left - 1;
        end
        % Search points to the right
        while right <= length(extremaList)
            if ~isLocMax(extremaList(right))
                % Update right local minimum if this point is a minimum.
                localMins(2) = min(localMins(2), ...
                    A(extremaList(right),j));
            elseif A(extremaList(right),j) > localMaxValue
                % Exit if we found a larger local maximum.
                break;
            end
            right = right + 1;
        end
        % Figure out which localMin we will use to compute the
        % prominence.
        localMinToUse = max(localMins);
        % Compute the prominence.
        if isa(A, 'integer')
            P(extremaList(i),j) = unsignedDiff(localMaxValue, ...
                localMinToUse);
        else
            P(extremaList(i),j) = localMaxValue - localMinToUse;
        end
        maxVals(extremaList(i),j) = P(extremaList(i),j) >= minProm;
    end
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.
m = size(A,1);
idx = 1:m;

for j = 1:coder.internal.prodsize(A,'above',1)
    % Get the linear indices of all local maxima in this column.
    locMaxima = idx(maxVals(:,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))
                while (k > 0) && (A(k-1,j) == A(k,j))
                    k = k - 1;
                end
                leftIndices(i) = x(k);
            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))
                while (k < (m-1)) && (A(k+1,j) == A(k,j))
                    k = k + 1;
                end
                rightIndices(i) = x(k);
            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)) = [];
        if ~isempty(leftIdx)
            leftMax = max(P(leftIdx,j));
            % Remove this local maxima if there is another to the left
            % within range that is larger.
            if (leftMax >= P(locMaxima(i),j))
                maxVals(locMaxima(i),j) = false;
            end
        elseif ~isempty(locMaxima((i+1):right))
            rightMax = max(P(locMaxima((i+1):right),j));
            % Remove this local maxima if there is another to the
            % right within range that is larger.
            if (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:coder.internal.prodsize(A,'above',1)
    % 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),1,'descend');
    maxVals(locMaxima(sortedIdx((maxNumExt+1):end)),j) = false;
end

%---------------------------------------------------------------------------
function [left, right] = findUniqueRanges(v)
d = diff(v);
locUniLeadMax = d ~= 0;
idxTemp = zeros(size(v,1),1);
ctr = 1;
for j = 1:numel(locUniLeadMax)
    if locUniLeadMax(j) == true
        idxTemp(ctr) = j;
        ctr = ctr + 1;
    end
end
idx = idxTemp(1:ctr-1);
left = [1; 1 + idx];
right = [left(2:end)-1; numel(v)];

%---------------------------------------------------------------------------
function [maxVals, P] = adjustFlatRegions(A, maxVals, flatType, P, ...
    returnProminence)
% Compensate for flat regions.
coder.noImplicitExpansionInFunction; % use of | > and & with same size operands:
                                     % logical(P) | maxVals: size(P)=size(A)=size(maxVals)
                                     % rightRange > leftRange: findUniqueRanges returns same-size outputs
                                     % maxVals(leftRange,j) & flatRegion: leftRange and flatRegion have same size
                                     % (Pvals > 0) & flatRegion: Pvals and flatRegion have same size

if returnProminence
    % If we are returning the prominence, we must set it correctly
    % for flat regions.
    colRngTF = any(logical(P) | maxVals,1);
else
    % Otherwise just evaluate the regions for the final maxima
    % list.
    colRngTF = any(maxVals,1);
end

colRngTemp = zeros(1,coder.internal.prodsize(A,'above',1));
ctr = 1;
for k = 1:numel(colRngTF)
    if colRngTF(k) == true
        colRngTemp(ctr) = k;
        ctr = ctr + 1;
    end
end
colRng = colRngTemp(1:ctr-1);

for k = 1:numel(colRng)
    j = colRng(k);
    % 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 coder.internal.allOrAny('any',flatRegion,1)
        % Adjust the local maxima - if flatType is 'first' or 'last', they
        % are already correct.
        if strcmp(flatType,{'center'}) || strcmp(flatType,{'all'})
            % Determine which unique leading values are maxima.
            ismax = maxVals(leftRange,j) & flatRegion;
            if coder.internal.allOrAny('any',ismax,1)
                ranges = [leftRange(ismax), rightRange(ismax)];
                if strcmp(flatType,{'center'})
                    maxVals(ranges(:,1),j) = false;
                    maxVals(floor(mean(ranges,2)),j) = true;
                else %flatTypeValue is 'all'
                    for jj = 1:size(ranges,1)
                        maxVals(ranges(jj,1):ranges(jj,2),j) = true;
                    end
                end
            end
        end
        
        % Adjust the prominences.
        if returnProminence
            % Pull the prominence from whichever side it has been stored on.
            if strcmp(flatType,{'center'}) || strcmp(flatType,{'first'}) || strcmp(flatType,{'all'})
                % 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 jj = 1:size(ranges,1)
                    P(ranges(jj,1):ranges(jj,2),j) = Pvals(jj);
                end
            end
        end
    end
end

%--------------------------------------------------------------------------
function D = unsignedDiff(a,b)
% Compute D = a - b of two signed integer values where a >= b and return as an
% unsigned integer value.
ucls = coder.internal.unsignedClass(class(a));
% Assume a >= b.
if a < 0
    D = eml_minus(unsignedAbsOfNegInt(b),unsignedAbsOfNegInt(a),ucls,'spill');
elseif b < 0
    D = eml_plus(a,unsignedAbsOfNegInt(b),ucls,'spill');
else
    D = eml_minus(a,b,ucls,'spill');
end

%--------------------------------------------------------------------------
function y = unsignedAbsOfNegInt(x)
% Returns the absolute value of a negative integer x as an unsigned integer.
ucls = coder.internal.unsignedClass(class(x));
% Assume x <= 0.
if x == intmin(class(x))
    y = coder.const(eml_plus(intmax(class(x)),1,ucls,'spill'));
else
    y = eml_cast(-x,ucls,'spill');
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
