function [cfs,f,coi] = cwt(x,wname,fs,varargin)
%
% GPU code generation version. MATLAB code generation is done in place in
% the CWTFILTERBANK class.
% CWT codegen for GPU requires constant inputs.
%

% Copyright 2018-2020 The MathWorks, Inc.

%#codegen

narginchk(3,11);
nargoutchk(0,3);
validateattributes(x,{'double','single'},{'vector','finite','nonempty',...
    'nonsparse'},'CWT','X');
dataclass = class(x);
coder.internal.assert(numel(x) >= 4,...
    'Wavelet:synchrosqueezed:NumInputSamples');
% We error out if attempting non-CUDA C/C++ code generation
GPUCODER = coder.gpu.internal.isGpuEnabled;
coder.internal.errorIf(~GPUCODER,'Wavelet:codegeneration:GPUOnly');
Norig = cast(numel(x),'int32');
% Constant inputs for GPU code generation
coder.internal.prefer_const(wname);
coder.internal.prefer_const(fs);
% Validate the sampling frequency
validateattributes(fs,{'numeric'},{'positive','scalar','finite',...
    'nonsparse','nonempty'},'CWT','Fs');
coder.internal.prefer_const(varargin);
% Persistent variables are the wavelet filter bank and filter bank center
% frequencies
persistent psidft;
persistent cf;
% Initialize Morse parameters
ga = 3;
be = 20;
% If the filter bank is not pre-computed, parse inputs
if isempty(psidft)
    coder.internal.errorIf(~strcmpi(wname,'Morse') && ~strcmpi(wname,'amor'),...
        'Wavelet:codegeneration:GPUWavelet');
    parms = struct('TimeBandwidth',uint32(0),...
        'WaveletParameters',uint32(0),'VoicesPerOctave',uint32(0),...
        'FrequencyLimits',uint32(0),'ExtendSignal',uint32(0));
    
    popts = struct('CaseSensitivity',false, 'PartialMatching',true);
    % 1x1 doubles
    defaultTB = NaN;
    % 1x2 doubles
    defaultWP = [NaN NaN];
    flim = [NaN NaN];
    defaultvoices = 10;
    
    if strcmpi(wname,'morse')
        cutoff = 50.0;
    else
        cutoff = 10.0;
    end
    pstruct = coder.internal.parseParameterInputs(parms,popts,varargin{:});
    TB = ...
        coder.internal.getParameterValue(pstruct.TimeBandwidth,defaultTB,varargin{:});
    WP = ...
        coder.internal.getParameterValue(pstruct.WaveletParameters,defaultWP,varargin{:});
    Npad = idivide(Norig,2);
    defaultExtend = true;
    VP  = ...
        coder.internal.getParameterValue(pstruct.VoicesPerOctave,defaultvoices,varargin{:});
    validateattributes(VP,{'numeric'},...
        {'positive','scalar','integer','nonsparse','>=',1,'<=',48},...
        'CWT','VoicesPerOctave');
    FreqLimits = ...
        coder.internal.getParameterValue(pstruct.FrequencyLimits,flim,varargin{:});
    
    if ~all(isnan(FreqLimits))
        FreqLimits = validFreqRange(FreqLimits,Norig,wname,cutoff,ga,be,fs,VP);
    end
    validateattributes(FreqLimits,{'double'},{'numel',2,...
        'nonempty','nonsparse'});
    Bndry = ...
        coder.internal.getParameterValue(pstruct.ExtendSignal,defaultExtend,varargin{:});
    validateattributes(Bndry,{'logical'},{'scalar'});
    if Bndry
        if Norig > int32(1e5)
            Npad = cast(ceil(log2(double(Norig))),'int32');
        end
    else
        Npad = int32(0);
    end
 coder.internal.errorIf((~isnan(TB) || ~all(isnan(WP))) && strcmpi(wname,'amor'),...
         'Wavelet:cwt:InvalidParamsWavelet');
 coder.internal.errorIf(~isnan(TB) && ~all(isnan(WP)),...
        'Wavelet:cwt:paramsTB');
    if ~isnan(TB) && all(isnan(WP))
        validateattributes(TB,{'numeric'}, ...
            {'scalar','>=',3, '<=',120, 'nonsparse'},'CWT','TimeBandwidth');
        be = TB/ga;
    elseif ~all(isnan(WP)) && isnan(TB)
        checkwavparams = @(x) isnumeric(x) && numel(x)==2 && ...
            x(1) >= 1 && x(2) >= x(1) && x(2)/x(1) <= 40 && ~issparse(x);
        checkWP = checkwavparams(WP);
        coder.internal.errorIf(~checkWP,'Wavelet:codegeneration:WavParams');
        ga = WP(1);
        be = WP(2)/ga;
    end
    omega = wavelet.internal.gpu.cwtFreqGrid(Norig,Npad);
    if ~all(isnan(FreqLimits))
        [~,~,omegapsi] = wavelet.internal.cwt.wavCFandSD(wname,ga,be);
        scales = wavelet.internal.gpu.freqToScales(FreqLimits,VP,omegapsi,fs);
    else
        scales = wavelet.internal.gpu.cwtScales(wname,Norig,VP,ga,be,2,cutoff);
    end
    % Compute wavelet filter banks
    if strcmpi(wname,'morse')
        [psidft,cf] = wavelet.internal.gpu.morseBPFilters(omega,scales,ga,be,fs,dataclass);
    else
        [psidft,cf] = wavelet.internal.gpu.amorletFilter(omega,scales,fs,dataclass);
    end   
        
end

[cfs,coi] = wavelet.internal.gpu.wt(x,psidft,cf,wname,fs,ga,be);
% Common use case would be just compute coefficients in which case we 
% do not need to compute f.
if nargout > 1
    % Copy persistent variable to output variable
    f = cf;
end



%--------------------------------------------------------------------------
function freqlims = validFreqRange(freqlims,N,wav,cutoff,ga,be,fs,nv)
validateattributes(freqlims,{'numeric'},...
    {'finite','increasing','numel',2},'CWT','FrequencyLimits');
minfreq = wavelet.internal.cwt.cwtfreqlimits(wav,double(N),cutoff,ga,...
        be,fs,2,nv);

% If the minimum frequency is less than the minimum allowable
% frequency, set equal to the minimum.
if freqlims(1) < minfreq
    freqlims(1) = minfreq;
end

if freqlims(2) > fs/2
    % If the maximum frequency is greater than the Nyquist, set
    % equal to the Nyquist.
    freqlims(2) = fs/2;
end
% Sufficient spacing in frequencies to respect VoicesPerOctave
freqsep = log2(freqlims(2))-log2(freqlims(1)) >= 1/nv;
coder.internal.errorIf(~freqsep,'Wavelet:cwt:freqsep',...
        sprintf('%2.2f',1.0/nv));









