function b = designLowpassFIR(Spec)
%DESIGNLOWPASSFIR Design N-th order lowpass FIR filter

%   Copyright 2022-2023 The MathWorks, Inc.

%#codegen

% Function arguments
arguments
    Spec.FilterOrder (1,1) {mustBeReal, mustBeNonnegative, mustBeInteger}
    Spec.CutoffFrequency (1,1) {mustBeReal, mustBePositive, mustBeLessThanOrEqual(Spec.CutoffFrequency,1)}
    Spec.Window (1,:) char {mustBeMember(Spec.Window,{'hann','hamming','blackman','blackman-harris','chebyshev','kaiser','custom'})}
    Spec.CustomWindow (1,:) {mustBeReal, mustBeVector}
    Spec.SystemObject (1,1) {mustBeNumericOrLogical(Spec.SystemObject)} = false;
    Spec.Verbose (1,1) {mustBeNumericOrLogical(Spec.Verbose)} = false;
end

% Define extrinsic methods
coder.extrinsic("dsp.filterdesign.internal.fir.getConstFIRCoefficients");

% check specified name-value pairs
isNSpec = isfield(Spec, "FilterOrder");
isFcSpec = isfield(Spec, "CutoffFrequency");
isWindowSpec = isfield(Spec, "Window");
isCustomWindowSpec = isfield(Spec, "CustomWindow");

% decide the output data type
if dsp.util.isAnyStructFieldSingle(Spec)
    outputDT = single(0);
else
    outputDT = 0;
end

% Use default values for N, Fc and Window if is not specified
if ~isNSpec
    Spec.FilterOrder = cast(100,"like",outputDT);
end

if ~isFcSpec
    Spec.CutoffFrequency = cast(0.25,"like", outputDT);
end

if ~isWindowSpec
    Spec.Window = "hamming";
end

maxL = 2^16;
N = Spec.FilterOrder;
% Error if Order is odd
coder.internal.errorIf(mod(N,2) ~=0 , "dsp:designFIRFilter:filterOrderMustBeEven");
% Length of te filter
L = N + 1;
assert(L <= maxL - 1);
assert(N <= maxL);
isCustomWindow = Spec.Window == "custom";

% Error if custom window is not specified, but window="custom"
coder.internal.errorIf(isCustomWindowSpec && ~isCustomWindow, "dsp:designFIRFilter:invalidWindow");

if ~isCustomWindow && ~Spec.SystemObject
    Spec.CustomWindow = zeros(1,L,"like",outputDT);
    Spec.CustomWindow(1,1:L) = dsp.filterdesign.internal.fir.getDesignWindowValue(Spec.Window, L, class(outputDT));
elseif ~coder.target("MATLAB")
    % At compile time the coder knows only the size and the type of window.
    % If the window value passed happens to be the same length as custom
    % (kaiser is an example of that). The check on line 103 becomes a
    % runtime check and the compiler will flag that CustomWindow is not
    % defined on all code paths.
    if ~isCustomWindowSpec
        Spec.CustomWindow = zeros(1,L,"like",outputDT);
    end
end

% Error if window length is not order + 1
coder.internal.errorIf(isCustomWindow && numel(Spec.CustomWindow) ~= L, "dsp:designFIRFilter:invalidCustomWindowLength");

% Generate the function signature if needed
if coder.target("MATLAB") && Spec.Verbose
    fprintf("%s\n", genFunctionSignature(Spec));
end

% Design filter
if Spec.SystemObject
    % Output coefficients or System Object if needed
    coeffs = coder.const(@dsp.filterdesign.internal.fir.getConstFIRCoefficients,"lowpass",Spec);
    b = dsp.FIRFilter(coeffs);
else
    Fc = cast(Spec.CutoffFrequency,"like",outputDT);
    b = dsp.filterdesign.internal.fir.lpfir(N,Fc,2,Spec.CustomWindow(:).',N);
end
end

function s = genFunctionSignature(Spec)
Spec = rmfield(Spec,"Verbose");
if isfield(Spec,"CustomWindow")
    Spec = rmfield(Spec,"CustomWindow");
end
Spec = orderfields(Spec);
s = sprintf("designLowpassFIR(%s)", dsp.util.struct2nvstr(Spec));
end
