function v = cordicrotate(theta, u, varargin)
% CORDICROTATE Rotate input using CORDIC-based approximation

% Copyright 2009-2017 The MathWorks, Inc.
%#codegen

coder.allowpcode('plain');

if eml_ambiguous_types
    % Compute output dimensions and output all zeros
    lengthOfUArray = numel(u);
    
    if lengthOfUArray > 1
        sz = size(u);
    else
        sz = size(theta);
    end
    
    v = eml_not_const(zeros(sz));
    
else
    coder.internal.prefer_const(theta);
    coder.internal.prefer_const(u);
    
    if nargin > 2
        coder.internal.prefer_const(varargin);
    end
    
    if (nargin < 2) || (nargin > 5)
        eml_invariant(false,'fixed:fi:invalidNumberOfInputArgs');
    end
    
    % ---------------------
    % Check input arguments
    % ---------------------
    eml_cordic_check_theta_arg(theta, 'cordicrotate');
    eml_check_numeric_nonempty_finite(u,'fixed:cordic:rotate_invalidU');

    lengthOfThetaArray = numel(theta);
    isThetaNonScalar   = (lengthOfThetaArray > 1);
    lengthOfUArray     = numel(u);
    is_U_NonScalar     = (lengthOfUArray > 1);
    
    if isThetaNonScalar && is_U_NonScalar
        eml_invariant(isequal(size(theta), size(u)), ...
            'fixed:cordic:rotate_invalidDims');
    end
    
    if isfloat(theta) || isfloat(u)
        eml_invariant(isequal(class(theta), class(u)), ...
            'fixed:cordic:rotate_invalidMixedDataTypes');
    end
    
    if isfi(theta) && isfi(u)
        eml_invariant(isequal(isdouble(theta), isdouble(u)) && ...
            isequal(issingle(theta), issingle(u)), ...
            'fixed:cordic:rotate_invalidMixedDataTypes');

        if isfloat(theta)
            % Call CORDICROTATE with equivalent builtin floating-point args
            if issingle(theta)
                theta_float = single(theta);
                u_float     = single(u);
            else
                theta_float = double(theta);
                u_float     = double(u);
            end
            if nargin > 2
                v_float = cordicrotate(theta_float, u_float, varargin{1:end});
            else
                v_float = cordicrotate(theta_float, u_float);
            end
            
            % Cast builtin values to FI values
            v = coder.nullcopy(fi(v_float, numerictype(theta)));
            for idx = 1:length(v_float)
                v(idx) = v_float(idx);
            end
            return; % EARLY RETURN
        end
    end
    
    % --------------
    % Initialization
    % --------------

    % Compute maximum number of iterations constant
    if isa(theta, 'double')
        maxNITERS = 52;
    elseif isa(theta, 'single')
        maxNITERS = 23;
    else
        % THETA is FI or integer
        if isfi(theta)
            thetaFPNT = numerictype(theta);
            zFPWL = thetaFPNT.WordLength;
            eml_invariant(zFPWL > 1, ...
                'fixed:cordic:inputWordLengthNotGTOne');
        else
            switch class(theta)
                case { 'int8', 'uint8'}
                    zFPWL = 8;
                case {'int16', 'uint16'}
                    zFPWL = 16;
                case {'int32', 'uint32'}
                    zFPWL = 32;
                otherwise
                    zFPWL = 64;
            end
        end
        
        % U is FI or integer
        if isfi(u)
            uFPNT = numerictype(u);
            uFPWL = uFPNT.WordLength;
            eml_invariant(uFPWL > 1, ...
                'fixed:cordic:inputWordLengthNotGTOne');
        else
            switch class(u)
                case { 'int8', 'uint8'}
                    uFPWL = 8;
                case {'int16', 'uint16'}
                    uFPWL = 16;
                case {'int32', 'uint32'}
                    uFPWL = 32;
                otherwise
                    uFPWL = 64;
            end
        end
    
        max_Z_NITERS  = zFPWL - 1;
        max_XY_NITERS = uFPWL;
        
        if max_Z_NITERS < max_XY_NITERS
            maxNITERS = max_Z_NITERS;
        else
            maxNITERS = max_XY_NITERS;
        end
    end
        
    if nargin > 2
        switch nargin
            case 3
                % varargin is {NITERS}
                numIters_dbl    = checkNITERSArg(varargin{1}, maxNITERS);
                doOutputScaling = true; % use default
            case 4
                % varargin is {'ScaleOutput', value}
                numIters_dbl    = maxNITERS; % use default
                doOutputScaling = checkScaleOutputPVPair(varargin{1}, varargin{2});
            otherwise
                % varargin is {NITERS, 'ScaleOutput', value}
                numIters_dbl    = checkNITERSArg(varargin{1}, maxNITERS);
                doOutputScaling = checkScaleOutputPVPair(varargin{2}, varargin{3});
        end
    else
        % No optional arguments specified
        numIters_dbl = maxNITERS; % use default
        doOutputScaling = true;   % use default
    end
    
    % Compute "real-world-values" for constant initializations
    inputLUT_dbl = atan(2 .^ (-(0:(numIters_dbl-1))'));
    
    if isa(u, 'double')
        % CORDIC "input side" (Z) lookup table
        lut = inputLUT_dbl;
        
        % CORDIC inverse gain constant value
        invGC = 1 / fixed.internal.cordic_compute_gain(numIters_dbl);
        
    elseif isa(u, 'single')
        % CORDIC "input side" (Z) lookup table
        lut = single(inputLUT_dbl);
        
        % CORDIC inverse gain constant value
        invGC = single(1 / fixed.internal.cordic_compute_gain(numIters_dbl));
        
    else
        % CORDIC "input side" (Z) lookup table
        if isfi(theta)
            tNT = numerictype(theta);
            zWL = tNT.WordLength;
        else
            switch class(theta)
                case { 'int8', 'uint8'}
                    zWL = 8;
                case {'int16', 'uint16'}
                    zWL = 16;
                case {'int32', 'uint32'}
                    zWL = 32;
                otherwise
                    zWL = 64;
            end
        end
        if isfi(theta)
            zNT = numerictype(numerictype(1, zWL, (zWL-2)), 'DataType',tNT.DataType);
        else
            zNT = numerictype(1, zWL, (zWL-2));
        end
        lut = fi(inputLUT_dbl, zNT); % Using NEAR/SAT (NOT local fimath)

        % Compute word length of U input
        if isinteger(u)
            switch class(u)
                case { 'int8',  'uint8'}
                    uWL =  8;
                case {'int16', 'uint16'}
                    uWL = 16;
                case {'int32', 'uint32'}
                    uWL = 32;
                otherwise
                    uWL = 64;
            end
        else
            uNT = numerictype(u);
            uWL = uNT.WordLength;
        end

        % Get fixed-point CORDIC inverse gain constant value (~0.607).
        % Initialize using "fimathless FI" rules (NEAREST, SATURATE).
        % Use BEST PRECISION FRACTION LENGTH for the gain const value.
        if isinteger(u)
            invGC = fi((1 / fixed.internal.cordic_compute_gain(numIters_dbl)), 1, uWL);
        else
            invGC = fi((1 / fixed.internal.cordic_compute_gain(numIters_dbl)), 1, uWL,...
                'DataType',uNT.DataType);
        end
    end

    % Compute output using CORDICROTATE
    v = eml_al_cordic_rotate(theta, u, numIters_dbl, ...
        lut, doOutputScaling, invGC);
    
end % eml_ambiguous_types

end % function

% ==============================================================
function numIters_dbl = checkNITERSArg(niters, maxNITERS)
coder.internal.prefer_const(niters, maxNITERS);
eml_cordic_check_niters_arg(niters);
if ~isempty(niters) && isfinite(niters) && (double(niters) < maxNITERS)
    numIters_dbl = double(niters);
else
    numIters_dbl = maxNITERS;
end
end % function
    
% ==============================================================
function doOutputScaling = checkScaleOutputPVPair(paramStr, paramVal)
coder.internal.prefer_const(paramStr, paramVal);

eml_invariant(~isempty(paramStr) && ischar(paramStr), ...
           'fixed:coder:invalidInputArg');

eml_invariant(coder.const(strcmp(coder.internal.toLower(paramStr),'scaleoutput')), ...
    'fixed:cordic:invalidParameter');

eml_invariant(isscalar(paramVal) && ...
           ( islogical(paramVal) || ...
             (isnumeric(paramVal) && isreal(paramVal) && isfinite(paramVal)) ), ...
           'fixed:cordic:invalidScaleOutputArg');

doOutputScaling = logical(paramVal);
end % function

