function frameOut = hdlmedfilt2(frameIn)
%HDLMEDFILT2 2-D median filtering.
%   frameOut = HDLMEDFILT2(frameIn) performs median filtering of the matrix
%   frameIn using the default 3-by-3 neighborhood. HDLMEDFILT2 pads the
%   image with zeros on the edges, so the median values for the points
%   within [m n]/2 of the edges may appear distorted.
%
%   Class Support
%   -------------
%   The input image frameIn can be logical, numeric, or fixed-point. The
%   output image frameOut is of the same class as frameIn.
%
%   Example
%   -------
%       I = imread('eight.tif');
%       J = imnoise(I, 'salt & pepper', 0.02);
%       K = hdlmedfilt2(J);
%       figure, imshow(J), figure, imshow(K)
%
%   See also FILTER2, ORDFILT2, WIENER2.

%   Copyright 2023 The MathWorks, Inc.

%#codegen

% --- Input Validation ----------------------------------------------------
funcName = 'hdlmedfilt2';
% Num. Inputs
validateattributes(frameIn, ...
    {'single', 'double', 'uint8', 'uint16', 'uint32', 'int8', 'int16', 'int32', 'logical', 'embedded.fi'}, ... % Class
    {'2d', 'nonempty', 'nonsparse', 'finite', 'nonnan', 'real'}, ... % Attributes
    funcName, 'frameIn', 1); % funcName, argName, argIdx

% No doubles for HDL
if coder.target('hdl')
    coder.internal.assert(~isa(frameIn, 'double'),'visionhdl:hdlmedfilt2:DoubleType', funcName);
end

% --- Setup ---------------------------------------------------------------

% Sorting Networks
% 3x3
sortTable = [ ...
    0  7   1  2   3  5   4  8;
    0  2   1  5   3  8   4  7;
    0  3   1  4   2  8   5  7;
    3  4   5  6  -1 -1  -1 -1;
    2  5   4  6  -1 -1  -1 -1;
    2  3   4  5  -1 -1  -1 -1;
    3  4  -1 -1  -1 -1  -1 -1] + 1;

[~, numComparisons] = size(sortTable);


mn = [3 3];

paddingMethod = 'Constant';
paddingValue  = 0;

medianPos = ceil(mn(1)^2 / 2);

% --- Filter --------------------------------------------------------------
frameOut = hdl.npufun(@MedianKernel3by3, mn, frameIn, ...
    'KernelArg', sortTable, 'KernelArg', numComparisons, 'KernelArg', medianPos, ...
    'BoundaryMethod', paddingMethod, 'BoundaryConstant', paddingValue);

end

% --- NPUFUNs -------------------------------------------------------------
% 3x3
function median = MedianKernel3by3(neighbourhoodIn, sortTable, numComparisons, medianPos)

sortedNeighborhood = coder.hdl.pipeline(reshape(neighbourhoodIn', 1, []));
inputNeighborhood  = sortedNeighborhood;

% Stage 1
for jj = 1 : 2 : numComparisons
    if and(sortTable(1, jj), sortTable(1, jj + 1)) ~= 0
        if inputNeighborhood(sortTable(1, jj)) > inputNeighborhood(sortTable(1, jj + 1))
            sortedNeighborhood(sortTable(1, jj + 1)) = inputNeighborhood(sortTable(1, jj));
            sortedNeighborhood(sortTable(1, jj))     = inputNeighborhood(sortTable(1, jj + 1));
        end
    end
end

inputNeighborhood = coder.hdl.pipeline(sortedNeighborhood);

% Stage 2
for jj = 1 : 2 : numComparisons
    if and(sortTable(2, jj), sortTable(2, jj + 1)) ~= 0
        if inputNeighborhood(sortTable(2, jj)) > inputNeighborhood(sortTable(2, jj + 1))
            sortedNeighborhood(sortTable(2, jj + 1)) = inputNeighborhood(sortTable(2, jj));
            sortedNeighborhood(sortTable(2, jj))     = inputNeighborhood(sortTable(2, jj + 1));
        end
    end
end

inputNeighborhood = coder.hdl.pipeline(sortedNeighborhood);

% Stage 3
for jj = 1 : 2 : numComparisons
    if and(sortTable(3, jj), sortTable(3, jj + 1)) ~= 0
        if inputNeighborhood(sortTable(3, jj)) > inputNeighborhood(sortTable(3, jj + 1))
            sortedNeighborhood(sortTable(3, jj + 1)) = inputNeighborhood(sortTable(3, jj));
            sortedNeighborhood(sortTable(3, jj))     = inputNeighborhood(sortTable(3, jj + 1));
        end
    end
end

inputNeighborhood = coder.hdl.pipeline(sortedNeighborhood);

% Stage 4
for jj = 1 : 2 : numComparisons
    if and(sortTable(4, jj), sortTable(4, jj + 1)) ~= 0
        if inputNeighborhood(sortTable(4, jj)) > inputNeighborhood(sortTable(4, jj + 1))
            sortedNeighborhood(sortTable(4, jj + 1)) = inputNeighborhood(sortTable(4, jj));
            sortedNeighborhood(sortTable(4, jj))     = inputNeighborhood(sortTable(4, jj + 1));
        end
    end
end

inputNeighborhood = coder.hdl.pipeline(sortedNeighborhood);

% Stage 5
for jj = 1 : 2 : numComparisons
    if and(sortTable(5, jj), sortTable(5, jj + 1)) ~= 0
        if inputNeighborhood(sortTable(5, jj)) > inputNeighborhood(sortTable(5, jj + 1))
            sortedNeighborhood(sortTable(5, jj + 1)) = inputNeighborhood(sortTable(5, jj));
            sortedNeighborhood(sortTable(5, jj))     = inputNeighborhood(sortTable(5, jj + 1));
        end
    end
end

inputNeighborhood = coder.hdl.pipeline(sortedNeighborhood);

% Stage 6
for jj = 1 : 2 : numComparisons
    if and(sortTable(6, jj), sortTable(6, jj + 1)) ~= 0
        if inputNeighborhood(sortTable(6, jj)) > inputNeighborhood(sortTable(6, jj + 1))
            sortedNeighborhood(sortTable(6, jj + 1)) = inputNeighborhood(sortTable(6, jj));
            sortedNeighborhood(sortTable(6, jj))     = inputNeighborhood(sortTable(6, jj + 1));
        end
    end
end

inputNeighborhood = coder.hdl.pipeline(sortedNeighborhood);

% Stage 7
for jj = 1 : 2 : numComparisons
    if and(sortTable(7, jj), sortTable(7, jj + 1)) ~= 0
        if inputNeighborhood(sortTable(7, jj)) > inputNeighborhood(sortTable(7, jj + 1))
            sortedNeighborhood(sortTable(7, jj + 1)) = inputNeighborhood(sortTable(7, jj));
            sortedNeighborhood(sortTable(7, jj))     = inputNeighborhood(sortTable(7, jj + 1));
        end
    end
end

median = coder.hdl.pipeline(sortedNeighborhood(medianPos));

end