function [failCheck, cfg] = interpretVHTSIGABitsFailCheck(lsigBits, vhtsigaBits, cfg)
%interpretVHTSIGABitsFailCheck Interprets the bits in VHT-SIG-A field
%   [FAILCHECK, CFG] = interpretVHTSIGABitsFailCheck(LSIGBITS, VHTSIGABITS,
%   CFG) interprets the given VHT-SIG-A bits and fills the input
%   configuration object, CFG, of type wlanVHTConfig. The input L-SIG bits
%   are used to calculate the APEP length. The following properties are
%   filled in the output configuration.
%       - ChannelBandwidth
%       - STBC
%       - GroupID
%       - NumUsers
%       - NumSpaceTimeStreams
%       - PartialAID
%       - MCS
%       - ChannelCoding
%       - GuardInterval
%       - APEPLength
%       - Beamforming

%   Copyright 2019-2020 The MathWorks, Inc.

    failCheck = false;

    % Retrieve information from VHT-SIG-A bits
    sigabits = double(reshape(vhtsigaBits,24,2)');
    if all(sigabits(1,1:2) == [0 0])
        chanBW = 'CBW20';
        numSD = 52;
    elseif  all(sigabits(1,1:2) == [1 0])
        chanBW = 'CBW40';
        numSD = 108;
    elseif  all(sigabits(1,1:2) == [0 1])
        chanBW = 'CBW80';
        numSD = 234;
    else
        chanBW = 'CBW160';
        numSD = 468;
    end
    cfg.ChannelBandwidth = chanBW;
    
    stbc = logical(sigabits(1,4));
    cfg.STBC = stbc;
    groupID = bi2de(sigabits(1,5:10)); 
    if groupID == 0 || groupID == 63
        isSU = true;
    else
        isSU = false; % is MU transmission
    end
    cfg.GroupID = groupID;
    
    % Get extra OFDM symbol information from VHTSIG-A bits
    if sigabits(2,4)==1
        ldpcExtraOFDMsymbol = 1;
    else
        ldpcExtraOFDMsymbol = 0;
    end
    
    if isSU
        numUsers = 1;
        cfg.NumUsers = numUsers;

        numSpaceTimeStreams = double(bi2de(sigabits(1,11:13))) + 1;
        cfg.NumSpaceTimeStreams = numSpaceTimeStreams;
        cfg.PartialAID = bi2de(sigabits(1,14:22)); 
        mcs = double(bi2de(sigabits(2,5:8)));
        if mcs>9
            failCheck = true;
            return;
        end
        cfg.MCS = mcs;
        
          % Test if the number of space-time streams is odd when STBC is used for SU
        if stbc && all(mod(numSpaceTimeStreams,2) == 1)
            failCheck = true;
            return
        end

        channelCodingBit = bi2de(sigabits(2,3));
        if channelCodingBit==1
            % Channel coding is LDPC
            channelCoding = 'LDPC';
        else
            % Channel coding is BCC
            channelCoding = 'BCC';
        end
        cfg.ChannelCoding = channelCoding;

        % Calculate RXTIME from L-SIG as this is used to calculate the number
        % of OFDM symbols and derive the PSDULength
        rxTime = calculateRxTime(lsigBits);
    
        % Get number of OFDM Data symbols
        [numDataSym, cfg.GuardInterval] = getNumDataSym(sigabits,numSpaceTimeStreams,rxTime);
        
        % Calculate received PSDULength and set it to be the APEPLength
        % IEEE Std 802.11-2016 Equation 21-107 and 21-108
        [numDBPS, numES] = getMCSTable(mcs,numSD,numSpaceTimeStreams/(1+stbc),channelCoding); 
        if strcmp(cfg.ChannelCoding,'BCC')
            numTailBits = 6;
            psduLength = floor((numDataSym*numDBPS - numTailBits*numES - 16)/8);
        else % LDPC
            psduLength = floor(((numDataSym-ldpcExtraOFDMsymbol*(1+stbc))*numDBPS - 16)/8);
        end
       
        if psduLength > 1048575 || psduLength < 0 % Maximum and minimum number of bytes
            failCheck = true;
            return
        end
        cfg.APEPLength = psduLength;
        
        cfg.Beamforming = logical(sigabits(2,9));

        % Test Bandwidth/MCS/Nss valid combinations
        %   Reference: IEEE Std 802.11-2016, Tables 21-30:21-56
        invalidComb = ...
              [20,  9, 1; ... % [chanBW, MCS, numSS]
               20,  9, 2; ...
               20,  9, 4; ...
               20,  9, 5; ...
               20,  9, 7; ...
               20,  9, 8; ...
               80,  6, 3; ...
               80,  6, 7; ...
               80,  9, 6; ...
               160, 9, 3];           
        cbw = wlan.internal.cbwStr2Num(chanBW); 
        vecMCS = repmat(mcs, 1, numUsers/length(mcs));
        numSS  = numSpaceTimeStreams / (((numUsers == 1) && stbc) + 1);
        for u = 1:numUsers
            thisComb = [cbw, vecMCS(u), numSS(u)];
            if any(all(bsxfun(@eq, thisComb, invalidComb), 2))
                failCheck = true;
                return;
            end
        end
        
        % Check that recovered PSDULength and MCS provide a valid
        % transmission time
        txTime = calculateTxTime(cfg);
        if txTime>5484
            failCheck = true;
            return
        end
        
        % Fail check for unsupported features
        if ~strcmp(cfg.ChannelBandwidth ,'CBW20')
                failCheck = true;
                return;
        end

    else
        % MU not supported
        failCheck = true;
        return
    end
end

function rxTime = calculateRxTime(LSIGBits)
% Retrieve RXTime from L-SIG for VHT transmission

% 4 symbol range, [RXTime - 3: RXTime]
rxTime = (bi2de(double(LSIGBits(6:17)')) + 3)/3*4 + 20; 

end

function [numDataSym, giType] = getNumDataSym(VHTSIGABits, numSTSTotal, rxTime)
% Recover number of OFDM symbols and guard interval type

numPreambSym = 9 + wlan.internal.numVHTLTFSymbols(numSTSTotal);
if VHTSIGABits(2, 1)
    giType = 'Short';
    numDataSym = floor((rxTime/4 - numPreambSym)*10.0/9.0) - VHTSIGABits(2, 2);
else
    giType = 'Long';
    numDataSym = rxTime/4 - numPreambSym;  % Precise
end

end

function [Ndbps, Nes] = getMCSTable(MCS, Nsd, Nss, channelCoding)
% Similar to wlan.internal.getRateTable, but with modified inputs

switch MCS
  case 0
    Nbpscs = 1; % 'BPSK'
    rate   = 1/2;
  case 1
    Nbpscs = 2; % 'QPSK'
    rate   = 1/2;
  case 2
    Nbpscs = 2; 
    rate   = 3/4;
  case 3
    Nbpscs = 4; % '16QAM'
    rate   = 1/2;
  case 4
    Nbpscs = 4; 
    rate   = 3/4;
  case 5
    Nbpscs = 6; % '64QAM'
    rate   = 2/3;
  case 6
    Nbpscs = 6; 
    rate   = 3/4;
  case 7
    Nbpscs = 6;
    rate   = 5/6;
  case 8
    Nbpscs = 8; % 256QAM
    rate   = 3/4;
  otherwise % MCS == 9
    Nbpscs = 8;
    rate   = 5/6;
end    

Ndbps = Nsd * Nbpscs * Nss * rate;

if strcmp(channelCoding, 'LDPC')
    Nes = 1; % always the case
else % BCC
    % Handle exceptions to Nes generic rule - Table 7.13 [2].
    %   For each case listed, work off the Ndbps value and create a look-up
    %   table for the Nes value.
    %   Only 9360 has a valid value from the generic rule also,
    %   all others are exceptions
    NdbpsVec = [2457 8190 9828 9360 14040 9828 16380 19656 21840 14976 22464];
    expNes =   [   3    6    6    6     8    6     9    12    12     8    12];
    
    exceptIdx = find(Ndbps == NdbpsVec);
    if ~isempty(exceptIdx)
        if (Ndbps == 9360) && (Nss == 5) % One valid case for 160, 80+80
            Nes = 5;
        else  % Two exception cases
            Nes = expNes(exceptIdx(1));
        end
    else  % Generic rule: 3.6*600 - for a net 600Mbps per encoder
        Nes = ceil(Ndbps/2160);
    end
end

end

function txTime = calculateTxTime(obj)

    numUsers = obj.NumUsers;
    APEPLen  = repmat(obj.APEPLength, 1, numUsers/length(obj.APEPLength));            
    mcsTable = wlan.internal.getRateTable(obj);
    numDBPS  = mcsTable.NDBPS;
    numES    = mcsTable.NES;
    rate     = mcsTable.Rate;
    mSTBC = (numUsers == 1)*(obj.STBC ~= 0) + 1;
    
    % Calculate number of OFDM symbols
    if isscalar(obj.APEPLength) && (obj.APEPLength(1) == 0) % NDP 
        numDataSymbols = 0;
    else
        % Get ChannelCoding property to a cell
        channelCoding = getChannelCoding(obj);
        
        userCodingIndex = zeros(1,numUsers); 
        for u=1:numUsers
            userCodingIndex(u) = strcmp(channelCoding{u}, 'BCC');
        end
        userCodingVector = 1:numUsers;
        indBCC  = userCodingVector(userCodingIndex == 1);
        indLDPC = userCodingVector(userCodingIndex == 0);
        
        numSymbolsLDPC = zeros(1,numUsers);
        numDataSymbols = 0;
        numTailBits    = 6; % For BCC encoding
        
        if ~isempty(indBCC)
            numDataSymbols = max(mSTBC*ceil((8*APEPLen(indBCC) + 16 + numTailBits*numES(indBCC))./(mSTBC.*numDBPS(indBCC))));
        end

        if ~isempty(indLDPC)
            % LDPC encoding parameters as defined in IEEE Std 802.11-2012,
            % IEEE Std 802.11ac-2013
            numSymbolsLDPC(indLDPC) = mSTBC * ceil((8*APEPLen(indLDPC) + 16)./(mSTBC * numDBPS(indLDPC))); % Eq 22-64
            numSymMaxInit = max([numDataSymbols,numSymbolsLDPC(indLDPC)]);                                 % Eq 22-65-MU, Eq 22-62-SU
            numSymbol = zeros(1, size(indLDPC,2));

            for u = 1:size(indLDPC,2)
                numPLD = numSymMaxInit*numDBPS(indLDPC(u));
                cfg = wlan.internal.getLDPCparameters(numDBPS(indLDPC(u)), rate(indLDPC(u)), mSTBC, numPLD); 
                numSymbol(u) = cfg.NumSymbol; % Eq 22-67
            end
            
            if max(numSymbol)>numSymMaxInit
                numDataSymbols = max(numSymbol);
            else 
                numDataSymbols = max(numSymMaxInit);
            end
        end
    end
    
    % Calculate burst time
    numPreambSym = 4 + 1 + 2 + 1 + wlan.internal.numVHTLTFSymbols(sum(obj.NumSpaceTimeStreams)) + 1;            
    if strcmp(obj.GuardInterval, 'Short')
        txTime = 4*numPreambSym + 4*ceil(numDataSymbols*3.6/4);
    else
        txTime = 4*numPreambSym + 4*numDataSymbols;
    end 
    
end