classdef ehtMUConfig < wlan.internal.ConfigBase
%ehtMUConfig Create a multi-user Extremely High Throughput (EHT) format configuration object
%   CFGEHT = ehtMUConfig(AllocationIndex) creates a multi-user (MU)
%   extremely high throughput (EHT) format configuration object. This
%   object contains the transmit parameters for an OFDMA EHT MU format of
%   IEEE P802.11be/D1.0.
%
%   AllocationIndex specifies the resource unit (RU) allocation. The
%   allocation defines the number and sizes of RUs and MRUs, and the number
%   of users assigned to each RU and MRU. IEEE P802.11be/D1.0, Table 36-35
%   defines the assignment index as a 9-bit index for each 20 MHz
%   subchannel.
%
%   AllocationIndex can be a vector of integers between 0 and 303 inclusive
%   (excluding the index for the validation in Table 36-35 of IEEE
%   P802.11be/D1.0), a string array, a character vector, or a cell array of
%   character vectors.
%
%   When AllocationIndex is specified as a vector of integers, each element
%   corresponds to a 9 bit index in Table 36-35 of IEEE P802.11be/D1.0. The
%   length of AllocationIndex must be 1, 2, 4, 8, or 16 defining the
%   assignment for each 20 MHz subchannel in a 20 MHz, 40 MHz, 80 MHz, 160
%   MHz, or 320 MHz channel bandwidth.
%
%   AllocationIndex can also be specified using the corresponding 9-bit
%   binary vector per allocation as specified in Table 36-35 of IEEE
%   P802.11be/D1.0. A 9 bit binary sequence can be provided as a character
%   vector or string. A string vector or cell array of character vectors
%   can be used to specify an allocation per 20 MHz subchannel.
%
%   To configure an OFDMA transmission greater than 20 MHz, AllocationIndex
%   consists of an assignment index for each 20 MHz subchannel. For
%   example, to configure an 80 MHz OFDMA transmission, a numeric row
%   vector with 4 allocation indices, or a string array with 4 elements is
%   required. You can signal punctured 20 MHz subchannels in an
%   80 MHz, 160 MHz, or 320 MHz transmission. To signal a punctured 20 MHz
%   subchannel, set the corresponding element of the AllocationIndex
%   property to 26.
%
%   CFGEHT = ehtMUConfig(CHANNELBANDWIDTH) creates a multi-user (MU)
%   extremely high throughput (EHT) format configuration object. This
%   object contains the transmit parameters for a non-OFDMA single-user
%   EHT MU format transmission.
%
%   CHANNELBANDWIDTH is a character vector or string describing the channel
%   bandwidth and must be 'CBW20' 'CBW40', 'CBW80', 'CBW160', or 'CBW320'.
%
%   CFGEHT = ehtMUConfig(...,NUMUSERS) creates a multi-user (MU) extremely
%   high throughput (EHT) format configuration object. This object contains
%   the transmit parameters of a non-OFDMA EHT MU packet format of IEEE
%   P802.11be/D1.0. NUMUSERS specifies the number of users in an MU-MIMO
%   configuration as scalar between 1 and 8 inclusive.
%
%   CFGEHT = ehtMUConfig(...,PUNCTUREDCHANNELFIELDVALUE) creates a
%   multi-user (MU) extremely high throughput (EHT) format configuration
%   object. This object contains the transmit parameters of a non-OFDMA EHT
%   MU format of IEEE P802.11be/D1.0. PUNCTUREDCHANNELFIELDVALUE specifies
%   the puncturing pattern as defined in Table 36-32 of IEEE
%   P802.11be/D1.0. PUNCTUREDCHANNELFIELDVALUE must be scalar between 0 and
%   24 inclusive.
%
%   CFGEHT = ehtMUConfig(...,Name,Value) creates an EHT MU object, CFGEHT,
%   with the specified property Name set to the specified Value. You can
%   specify additional name-value pair arguments in any order as
%   (Name1,Value1, ...,NameN,ValueN).
%
%   ehtMUConfig methods:
%
%   getPSDULength              - Number of bytes to be coded in the packet
%   packetFormat               - EHT MU packet format
%   ruInfo                     - Resource unit allocation information
%   getNumPostFECPaddingBits   - Required number of post-FEC padding bits
%   getNumEHTUSIGBits          - Required number of U-SIG field bits
%   getNumEHTSIGBits           - Required number of EHT-SIG field bits
%   showAllocation             - Shows the RU and MRU allocation
%               
%   ehtMUConfig properties:
%
%   AllocationIndex            - RU allocation for each 20 MHz subchannel
%   ChannelBandwidth           - Channel bandwidth
%   PuncturingPattern          - Punctured 20 MHz subchannels for MU-MIMO
%   PuncturedChannelFieldValue - Puncturing pattern value for MU-MIMO
%   RU                         - RU properties
%   User                       - User properties
%   NumTransmitAntennas        - Number of transmit antennas
%   PreEHTCyclicShifts         - Cyclic shift values for >8 transmit chains
%   PreEHTPhaseRotation        - Pre-EHT phase rotation values for 320 MHz
%   GuardInterval              - Guard interval duration
%   EHTLTFType                 - Indicate EHT-LTF compression mode
%   EHTSIGMCS                  - Indicates the MCS of the EHT-SIG field
%   USIGBitsSource             - U-SIG field bits source
%   USIGBitsSeed               - Initial random U-SIG field bits seed
%   USIGBits                   - U-SIG field bits
%   EHTSIGBitsSource           - EHT-SIG field bits source
%   EHTSIGBitsSeed             - Initial random EHT-SIG field bits seed
%   EHTSIGBits                 - EHT-SIG field bits
%
%   % Example 1: 
%   %  Create an EHT MU single user object for 320 MHz channel bandwidth.
%
%   cfgEHT = ehtMUConfig('CBW320');
%   disp(cfgEHT) 
%
%   % Example 2: 
%   %  Create an MU-MIMO object for a 320 MHz channel bandwidth with a
%   %  punctured 40 MHz subchannel.
%
%   cfgEHT = ehtMUConfig('CBW320',2,2);
%   disp(cfgEHT)
%
%   % Example 3:
%   %  Create an OFDMA configuration for a 20 MHz EHT MU packet with one
%   %  106-tone RU, one 106+26-tone MRU, and one user per RU, as defined
%   %  by allocation index 48.
%
%   cfgEHT = ehtMUConfig(48);
%   disp(cfgEHT)  
%
%   See also ehtData, ehtDataBitRecover.

%   Copyright 2020-2021 The MathWorks, Inc.

properties
    %RU Resource unit (RU or MRU) properties
    %   Set the transmission properties for each RU and MRU in the
    %   transmission. This property is a cell array of <a
    %   href="matlab:help('ehtRU')">ehtRU</a> objects. Each 
    %   element of the cell array contains properties to configure an RU or
    %   an MRU. This property is configured when the object is created
    %   based on the defined AllocationIndex for OFDMA or ChannelBandwidth
    %   and PuncturedChannelFieldValue for non-OFDMA configuration.
    RU;
    %User User properties
    %   Set the transmission properties for each User in the transmission.
    %   This property is a cell array of <a href="matlab:help('ehtUser')">ehtUser</a> objects. Each element
    %   of the cell array contains properties to configure a User. This
    %   property is configured when the object is created based on the
    %   defined AllocationIndex for OFDMA or NumUsers for non-OFDMA
    %   configuration.
    User;
    %NumTransmitAntennas Number of transmit antennas
    %   Specify the number of transmit antennas as a numeric, positive
    %   integer scalar. The default is 1.
    NumTransmitAntennas (1,1) {mustBeNumeric,mustBeInteger,mustBeGreaterThanOrEqual(NumTransmitAntennas,1)} = 1;
    %PreEHTCyclicShifts Cyclic shift values for >8 transmit chains
    %   Specify the cyclic shift values for the pre-EHT portion of the
    %   waveform, in nanoseconds for >8 transmit antennas as a row vector
    %   of length L = NumTransmitAntennas-8. The cyclic shift values must
    %   be between -200 and 0 inclusive. The first 8 antennas use the
    %   cyclic shift values defined in Table 21-10 of IEEE Std 802.11-2016.
    %   The remaining antennas use the cyclic shift values defined in this
    %   property. If the length of this row vector is specified as a value
    %   greater than L the object only uses the first L, PreEHTCyclicShifts
    %   values. For example, if you specify the NumTransmitAntennas
    %   property as 16 and this property as a row vector of length N>L, the
    %   object only uses the first L = 16-8 = 8 entries. This property
    %   applies only when you set the NumTransmitAntennas property to a
    %   value greater than 8. The default value of this property is -75.
    PreEHTCyclicShifts {mustBeNumeric,mustBeInteger,mustBeGreaterThanOrEqual(PreEHTCyclicShifts,-200),mustBeLessThanOrEqual(PreEHTCyclicShifts,0)} = -75;
    %PreEHTPhaseRotation Pre-EHT phase rotation values for 320 MHz
    %   Specify the phase rotation values for the pre-EHT portion of the
    %   waveform as a row vector of size 1-by-16. Each element of the
    %   vector corresponds to a 20 MHz subchannel in increasing order of
    %   frequency as defined in Equation 36-12 of IEEE P802.11be/D1.0. This
    %   property only applies when ChannelBandwidth is CBW320. The default
    %   value of this property is [1 -1 -1 -1 1 -1 -1 -1 -1 1 1 1 -1 1 1
    %   1].
    PreEHTPhaseRotation (1,16) {mustBeNumeric,mustBeFinite,mustBeNonzero} = [1 -1 -1 -1 1 -1 -1 -1 -1 1 1 1 -1 1 1 1];
    %GuardInterval Guard interval type
    %   Specify the guard interval (cyclic prefix) length in Microseconds
    %   as one of 0.8, 1.6 or 3.2. The default is 3.2.
    GuardInterval {wlan.internal.heValidateGI(GuardInterval)} = 3.2;
    %EHTLTFType Indicate EHT-LTF compression mode of EHT PPDU
    %   Specify the EHT-LTF compression type as 1, 2 or 4, corresponding to
    %   2x EHT-LTF and 4x EHT-LTF modes respectively. The default is 4.
    EHTLTFType (1,1) {mustBeNumeric,mustBeMember(EHTLTFType,[1 2 4])} = 4;
    %EHTSIGMCS Indicates the MCS of the EHT-SIG field
    %   Specify the modulation and coding scheme of the EHT-SIG field as 0,
    %   1, 3, or 15, corresponding to EHT-SIG field value 0, 1, 2, or 3 as
    %   defined in Table 36-87 of IEEE P802.11be/D1.0. The default is 0.
    EHTSIGMCS (1,1) {mustBeNumeric,mustBeMember(EHTSIGMCS,[0 1 3 15])} = 0;
    %USIGBitsSource U-SIG field bits source
    %   Specify the source of U-SIG field bits prior to CRC and tail bits
    %   addition as 'mt19937ar with seed', 'Global stream', or
    %   'User-defined'. To use the mt19937ar random number generator
    %   algorithm with a seed to generate normally distributed random bits,
    %   set this property to 'mt19937ar with seed'. The mt19937ar algorithm
    %   uses the seed specified by the value of the USIGBitsSeed property.
    %   To use the current global random number stream to generate normally
    %   distributed random bits, set this property to 'Global stream'. To
    %   use bits specified in the USIGBits property, set this property to
    %   'User-defined'. The default value of this property is 'mt19937ar
    %   with seed'.
    USIGBitsSource = 'mt19937ar with seed';
    %USIGBitsSeed Initial random U-SIG field bits seed
    %   Specify the initial seed of the mt19937ar random number generator
    %   algorithm as a nonnegative integer. This property applies when you
    %   set the USIGBitsSource property to 'mt19937ar with seed'. The
    %   default value of this property is 74.
    USIGBitsSeed (1,1) {mustBeNumeric,mustBeInteger,mustBeNonnegative} = 74;
    %USIGBits U-SIG field bits
    %   Specify U-SIG field bits as an int8, double, or single typed binary
    %   column vector of length 42 times number of 80 MHz segments. The
    %   number of 80 MHz segment is 1 for 20 MHz, 40 MHz, 80 MHz, 2 for 160
    %   MHz, and 4 for 320 MHz. The waveform generator loops the vector if
    %   the number of bits required exceeds the length of the vector
    %   provided. The number of bits the waveform generator uses from the
    %   vector is given by the <a href="matlab:help('ehtMUConfig/getNumEHTUSIGBits')">getNumEHTUSIGBits</a> object function. The default
    %   value of this property is 0.
    USIGBits (:,1) {ehtValidateSIGits(USIGBits)} = int8(0);
    %EHTSIGBitsSource EHT-SIG field bits source
    %   Specify the source of EHT-SIG field bits as 'mt19937ar with seed',
    %   'Global stream', or 'User-defined'. The EHT-SIG field bits
    %   includes, EHT-SIG field, CRC, tail and padding bits for content
    %   channel-1, followed by content channel-2. To use the mt19937ar
    %   random number generator algorithm with a seed to generate normally
    %   distributed random bits, set this property to 'mt19937ar with
    %   seed'. The mt19937ar algorithm uses the seed specified by the value
    %   of the EHTSIGBitsSeed property. To use the current global random
    %   number stream to generate normally distributed random bits, set
    %   this property to 'Global stream'. To use bits specified in the
    %   EHTSIGBits property, set this property to 'User-defined'. The
    %   default value of this property is 'mt19937ar with seed'.
    EHTSIGBitsSource = 'mt19937ar with seed';
    %EHTSIGBitsSeed Initial random EHT-SIG field bits seed
    %   Specify the initial seed of the mt19937ar random number generator
    %   algorithm as a nonnegative integer. This property applies when you
    %   set the EHTSIGBitsSource property to 'mt19937ar with seed'. The
    %   default value of this property is 75.
    EHTSIGBitsSeed (1,1) {mustBeNumeric,mustBeInteger,mustBeNonnegative} = 75;
    %EHTSIGBits EHT-SIG field bits
    %   Specify EHT-SIG field bits as an int8, double, or single typed
    %   binary column vector of length N times the number of 80 MHz
    %   segment. N is the total number of bits for all content channels in a
    %   80 MHz segment. The waveform generator loops the vector if
    %   the number of bits required exceeds the length of the vector
    %   provided. The number of bits the waveform generator uses from the
    %   vector is given by the <a href="matlab:help('ehtMUConfig/getNumEHTSIGBits')">getNumEHTSIGBits</a> object function. 
    %   The default value of this property is 0.
    EHTSIGBits (:,1) {ehtValidateSIGits(EHTSIGBits)} = int8(0);
end

properties(SetAccess = 'private')
    %ChannelBandwidth Channel bandwidth (MHz) of EHT MU PPDU
    %   The channel bandwidth, specified as one of 'CBW20' | 'CBW40' |
    %   'CBW80' | 'CBW160' | 'CBW320'. For OFDMA this property is set when
    %   the object is created based on the defined AllocationIndex. For
    %   non-OFDMA this property is set once the object is created based on
    %   the defined ChannelBandwidth. Once the object is created this
    %   property is read only.
    ChannelBandwidth = 'CBW320';
    %AllocationIndex RU allocation index for each 20 MHz subchannel
    %   Specify the RU allocation index when creating the object. Once the
    %   object is created, AllocationIndex is read only. The allocation
    %   index defines the number and sizes of RUs, and the number of users
    %   assigned to each RU. Table 36-35 of IEEE P802.11be/D1.0 defines
    %   the assignment index as a 9 bit index for each 20 MHz subchannel.
    %
    %   AllocationIndex can be a vector of integers between 0 and 303
    %   inclusive (excluding the index for the validation in Table 36-35 of
    %   IEEE P802.11be/D1.0), a string array, a character vector, or a cell
    %   array of character vectors.
    %
    %   When AllocationIndex is specified as a vector of integers, each
    %   element corresponds to a 9 bit index in Table 36-24. The length of
    %   AllocationIndex must be 1, 2, 4, 8, or 16 defining the assignment
    %   for each 20 MHz subchannel in a 20 MHz, 40 MHz, 80 MHz, 160 MHz, or
    %   320 MHz channel bandwidth.
    %
    %   AllocationIndex can also be specified using the corresponding 9-bit
    %   binary vector per allocation as specified in Table 36-35. An 9 bit
    %   binary sequence can be provided as a character vector or string. A
    %   string vector or cell array of character vectors can be used to
    %   specify an allocation per 20 MHz subchannel. This property is only
    %   visible for OFDMA configuration.
    AllocationIndex = 0;
    %PuncturingPattern Indicates punctured subchannels for non-OFDMA
    %   Indicates inactive 20 MHz or 40 MHz subchannels as a logical vector
    %   for the given PuncturedChannelFieldValue property. For 80 MHz and
    %   160 MHz the number of elements are equal to the number of 20 MHz
    %   subchannels. A true value indicates an inactive (punctured) 20 MHz
    %   subchannel in a 80 MHz and 160 MHz bandwidth. For 320 MHz the
    %   number of elements are equal to the number of 40 MHz subchannels.
    %   In this case a true value indicates an inactive (punctured) 40 MHz
    %   subchannel. Subchannels are ordered from lowest to highest absolute
    %   frequency. This property applies only when ChannelBandwidth is
    %   'CBW80', 'CBW160', or 'CBW320'. Once the object is created this
    %   property is read only.
    PuncturingPattern logical = false;
    %PuncturedChannelFieldValue Puncturing pattern value for non-OFDMA
    %   Indicates the corresponding puncturing pattern value for the given
    %   punctured channel field value for a non-OFDMA configuration as
    %   defined in Table 36-30 of IEEE P802.11be/D1.0. This property is set
    %   when the object is created based on the defined
    %   PuncturedChannelFieldValue. Once the object is created this
    %   property is read only.
    PuncturedChannelFieldValue (1,1) double = 0;
end

properties(Access=private)
    PrivateUserRUNumber = 1;
end

properties(SetAccess = 'private',Hidden)
    IsOFDMA (1,1) logical = true;
    First80MHzSubblock (1,1) logical = true;
end

properties(Constant,Hidden)
    USIGBitsSource_Values = {'mt19937ar with seed','Global stream','User-defined'};
    EHTSIGBitsSource_Values = {'mt19937ar with seed','Global stream','User-defined'};
end

methods
    function obj = ehtMUConfig(x,varargin)
    % ehtMUConfig Create an EHT MU format configuration object
    
        narginchk(1,Inf);
        numVarInputs = 0; % Number of inputs before the NV pair excluding the first input
        % Differentiate between allocation index and channel bandwidth
        if (isstring(x) || ischar(x)) && any(numel(char(x))==[5 6]) % MU-MIMO (non-OFDMA)
            % ehtMUConfig('CBW',X,X)
            % Validate Channel Bandwidth
            chanBW = validatestring(x,{'CBW20','CBW40','CBW80','CBW160','CBW320'},'channel bandwidth');
            % Validate remaining inputs
            numUsers = 1; % Set number of users to 1
            PuncturedChannelFieldValue = 0; % Set field value to no puncturing
            isNV = true;
            if nargin>1
                if isnumeric(varargin{1}) % If second input is a number
                    % Validate second input, must be number of users
                    validateattributes(varargin{1},{'numeric'},{'scalar','>=',1,'<=',8},mfilename,'NumUsers');
                    numUsers = varargin{1};
                    numVarInputs = 1;
                    isNV = false;
                end
               
                if nargin>2 && ~isNV % Two option still numeric or NV pair
                    if isnumeric(varargin{2}) % If second input is a number
                        validateattributes(varargin{2},{'numeric'},{'scalar','>=',0,'<=',24},mfilename,'PuncturedChannelFieldValue');
                        PuncturedChannelFieldValue = varargin{2};
                        numVarInputs = 2;
                    end
                end
            end
            % Map channel bandwidth and number of users to allocation index
            obj.IsOFDMA = false; % Set flag for non-OFDMA transmission
            [obj.RU,obj.User,obj.PrivateUserRUNumber,obj.PuncturingPattern] = ehtRUAllocation(chanBW,numUsers,PuncturedChannelFieldValue);
            obj.ChannelBandwidth = chanBW;
            obj.PuncturedChannelFieldValue = PuncturedChannelFieldValue;
        end
        
        % Determine allocation index given input
        if obj.IsOFDMA
            % ehtMUConfig(allocationIndex)
            allocationIndex = x;
            if isstring(allocationIndex) || ischar(allocationIndex) || iscell(allocationIndex)
                if ischar(allocationIndex)
                    % Single character array
                    if numel(allocationIndex)~=9
                        error('When AllocationIndex is provided as a character vector, string, or cell array of character vectors, each allocation must contain 9 bits.');
                    end
                    allocationIndex = bin2dec(allocationIndex);
                elseif iscell(allocationIndex)
                    % Cell array of character vectors
                    n = numel(allocationIndex);
                    allocationIndexInUse = zeros(1,n);
                    for i = 1:numel(allocationIndex)
                        val = allocationIndex{i};
                        if ~ischar(val) || numel(val)~=9
                            error('When AllocationIndex is provided as a character vector, string, or cell array of character vectors, each allocation must contain 9 bits.');
                        end
                        allocationIndexInUse(i) = bin2dec(val);
                    end
                    allocationIndex = allocationIndexInUse;
                    % String or string array
                elseif isstring(allocationIndex)
                    for i = 1:numel(allocationIndex)
                        if numel(char(allocationIndex(i)))~=9
                            error('When AllocationIndex is provided as a character vector, string, or cell array of character vectors, each allocation must contain 9 bits.');
                        end
                    end
                    allocationIndex = bin2dec(allocationIndex);
                end
            else
                % Numeric
                validateattributes(allocationIndex,{'numeric'},{'integer'})
                if isempty(allocationIndex)
                    error('The number of allocation indices must be 1, 2, 4, 8, or 16 corresponding to a 20 MHz, 40 MHz, 80 MHz, 160 MHz, or 320 MHz channel bandwidth.');
                end
            end
            validateattributes(allocationIndex,{'numeric'},{'>=',0,'<=',303},mfilename,'allocation index');
            if inAllocation(allocationIndex,[31 56 57])
                error ('Allocation index 31 and 56 to 63 (inclusive) are not supported.');
            end

            % Check for the transmission in either first, second, and third
            % or second, third, and fourth 80 MHz subchannel for allocation
            % index range 256:303
            if numel(allocationIndex)==16 && inAllocation(allocationIndex,256:303)
                if (inAllocation(allocationIndex(1:4),256:303) || all(allocationIndex(1:4)==30)) ...
                        && (inAllocation(allocationIndex(13:16),256:303) || all(allocationIndex(13:16)==30))
                    error ('Invalid RU allocation for 2x996+484-tone MRU.')
                elseif inAllocation(allocationIndex(1:4),256:303) || all(allocationIndex(1:4)==30)
                    obj.First80MHzSubblock = true;
                else
                    obj.First80MHzSubblock = false;
                end
            end
            obj.AllocationIndex = allocationIndex;

            [obj.RU,obj.User,obj.PrivateUserRUNumber,obj.PuncturingPattern] = ehtRUAllocation(obj.AllocationIndex,obj.First80MHzSubblock);
            % Set channel bandwidth based on allocation index
            switch numel(allocationIndex)
                case 1
                    obj.ChannelBandwidth = 'CBW20';
                case 2
                    obj.ChannelBandwidth = 'CBW40';
                case 4
                    obj.ChannelBandwidth = 'CBW80';
                case 8
                    obj.ChannelBandwidth = 'CBW160';
                otherwise
                    obj.ChannelBandwidth = 'CBW320';
            end
        end

        if nargin>numVarInputs+1
            coder.internal.errorIf((mod(nargin-1-numVarInputs,2)~=0),'wlan:ConfigBase:InvalidPVPairs');
            for i = 1+numVarInputs:2:nargin-1
                if any(strcmp(varargin{i},{'RU','User'}))
                    error('You cannot set the RU/User property with a Name-Value pair.');
                end
                obj.(char(varargin{i})) = varargin{i+1};
            end
        end
    end

    function obj = set.USIGBitsSource(obj,val)
        val = validateEnumProperties(obj,'USIGBitsSource',val);
        obj.USIGBitsSource = ''; % Force varsize
        obj.USIGBitsSource = val;
    end

    function obj = set.EHTSIGBitsSource(obj,val)
        val = validateEnumProperties(obj,'EHTSIGBitsSource',val);
        obj.EHTSIGBitsSource = ''; % Force varsize
        obj.EHTSIGBitsSource = val;
    end

    function s = ruInfo(obj)
    %ruInfo Returns information relevant to the resource unit
    %   S = ruInfo(cfgEHT) returns a structure, S, containing the resource
    %   unit (RU) allocation information for the ehtMUConfig object,
    %   cfgEHT. The output structure S has the following fields:
    %
    %   NumUsers                 - Number of users
    %   NumRUs                   - Number of RUs
    %   RUIndices                - Index of the RU or MRU
    %   RUSizes                  - Size of the RU or MRU
    %   NumUsersPerRU            - Number of users per RU
    %   NumSpaceTimeStreamsPerRU - Total number of space-time streams
    %   PowerBoostFactorPerRU    - Power boost factor
    %   RUNumbers                - RU number
    %
    %   RUIndices and RUSizes are a cell array where each element is the
    %   index or size of an RU/MRU. For an MRU the cell array element is a
    %   vector containing RU indices and sizes which form an MRU.

        numRUs = numel(obj.RU);
        numUsers = numel(obj.User);
        ruActive = true(1,numRUs);
        for j = 1:numUsers
           if obj.User{j}.STAID==2046
               % If APEP length is 0 or STAID is 2046 then RU is inactive
               ruNum = obj.User{j}.RUNumber;
               if ruNum<=numRUs
                   ruActive(ruNum) = false;
               end
           end
        end
        numActiveRUs = sum(ruActive==true);
        numActiveUsers = numUsers;
        ruIndices = cell(1,numActiveRUs);
        ruSizes = cell(1,numActiveRUs);
        ruNumbers = zeros(1,numActiveRUs);
        numSTS = zeros(1,numActiveRUs);
        powerBoostFactor = zeros(1,numActiveRUs);
        numUsersPerRU = zeros(1,numActiveRUs);

        k = 1;
        for i = 1:numRUs
            if ~ruActive(i)
               continue
            end
            for j = 1:numUsers
                ruShared = i==obj.PrivateUserRUNumber(j);
                if ruShared
                    numUsersPerRU(k) = numUsersPerRU(k)+1;
                    numSTS(k) = numSTS(k)+obj.User{j}.NumSpaceTimeStreams;
                end
            end
            ruIndices{k} = obj.RU{i}.Index;
            ruSizes{k} = obj.RU{i}.Size;
            ruNumbers(k) = i;
            powerBoostFactor(k) = obj.RU{i}.PowerBoostFactor;
            k = k+1;
        end

       s = struct;
       s.NumUsers = numActiveUsers;
       s.NumRUs = numActiveRUs;
       s.RUIndices = ruIndices;
       s.RUSizes = ruSizes;
       s.NumUsersPerRU = numUsersPerRU;
       s.NumSpaceTimeStreamsPerRU = numSTS;
       s.PowerBoostFactorPerRU = powerBoostFactor;
       s.RUNumbers = ruNumbers;
    end
    
    function n = getNumPostFECPaddingBits(obj)
    %getNumPostFECPaddingBits Required number of post-FEC padding bits
    %   Returns a vector with the required number of post-FEC padding bits
    %   for each user.
    
        [~,userCodingParams] = ehtCodingParameters(obj);
        n = userCodingParams.NPADPostFEC;
    end
    
    function numEHTSIGBits = getNumEHTUSIGBits(obj)
    %getNumEHTUSIGBits Required number of U-SIG bits
    %   Returns the required number of U-SIG bits for the given
    %   configuration.
        
        % Number of 80 MHz segments
        S = ceil(wlan.internal.cbwStr2Num(obj.ChannelBandwidth)/80);
        numEHTSIGBits = 42*S;
    end

    function numEHTSIGBits = getNumEHTSIGBits(obj)
    %getNumEHTSIGBits Required number of EHT-SIG bits
    %   Returns the required number of EHT-SIG bits for the given
    %   configuration.

        codingInfo = ehtSIGCodingInfo(obj);
        numEHTSIGBits = codingInfo.NumSIGBits;
    end

    function psduLength = getPSDULength(obj)
    %getPSDULength Returns PSDU length for the configuration
    %   Returns the PSDU length for an EHT MU configuration as defined in
    %   Section 36.4.3 of IEEE P802.11be/D1.0.

         validateSTAID(obj); % Validate STAID 2046
         psduLength = ehtPLMETxTimePrimative(obj);
    end
    
    function format = packetFormat(obj) %#ok<MANU>
    %packetFormat Returns the packet format
    %   Returns the packet format as a character vector, based on the
    %   current configuration.
    
        format = 'EHT-MU';
    end
    
    function showAllocation(obj,varargin)
    %showAllocation Shows the RU and MRU allocation
    %   showAllocation(cfgEHT) shows the RU and MRU allocation for an EHT
    %   MU packet format.
    %
    %   showAllocation(cfgEHT,AX) shows the allocation in the axes
    %   specified by AX instead of in the current axes.

        allocInfo = ruInfo(obj);
        ruTypeInfo = strings(1,allocInfo.NumRUs);
        isMRU = true(1,allocInfo.NumRUs);
        for ru = 1:allocInfo.NumRUs
            if numel(allocInfo.RUSizes{ru})==1
                ruTypeInfo(ru) = num2str(allocInfo.RUSizes{ru});
                isMRU(ru) = false;
                continue
            end
            switch sum(allocInfo.RUSizes{ru})
                case 26+52
                    ruTypeInfo(ru) = "52+26";
                case 26+106
                    ruTypeInfo(ru) = "106+26";
                case 242+484
                    ruTypeInfo(ru) = "484+242";
                case 484+996
                    ruTypeInfo(ru) = "996+484";
                case 242+484+996
                    ruTypeInfo(ru) = "996+484+242";
                case 2*996
                    ruTypeInfo(ru) = "2*996";
                case 2*996+484
                    ruTypeInfo(ru) = "2*996+484";
                case 3*996
                    ruTypeInfo(ru) = "3*996";
                otherwise % 3*996+484
                    ruTypeInfo(ru) = "3*996+484";
            end
        end
        
        if nargin==1
            f = figure('Name','RU/MRU Assignment and Occupied Subcarriers');
            ax = axes(f);
        else
            ax = varargin{1};
        end
        cla(ax,'reset')
        disableDefaultInteractivity(ax);
        
        cbw = wlan.internal.cbwStr2Num(obj.ChannelBandwidth);
        Nfft = cbw/20*256;
        
        % Plot the pre-EHT occupied subcarriers
        cfgOFDM = wlan.internal.hePreHEOFDMConfig(obj.ChannelBandwidth);
        preHE = zeros(cfgOFDM.FFTLength,1);
        preHE(cfgOFDM.DataIndices) = 1;
        preHE(cfgOFDM.PilotIndices) = 1;
        preHE = preHE.*ehtPreEHTCarrierRotations(obj);
        kPreHE = find(abs(preHE)==1)-(cfgOFDM.FFTLength/2+1);
        nonContigEnd = [find(diff(kPreHE)~=1); numel(kPreHE)];
        startIdx = 1; % The start index of the continuous block of subcarriers
        
        for jj = 1:numel(nonContigEnd)
            % Plot a patch of the continuous block of subcarriers
            blkIdx = startIdx:nonContigEnd(jj);
            y = [kPreHE(blkIdx(1)) kPreHE(blkIdx(end)) kPreHE(blkIdx(end)) kPreHE(blkIdx(1))]*4;
            x = [0                 0                   0.5                 0.5   ];
            patch(ax,x,y,0);
            hold(ax,'on');
            startIdx = nonContigEnd(jj)+1;
        end
        xoffset = 0.5;
        
        % infoLims contains tuples of channel bandwidth and the minimum RU size for
        % which RU information is to be displayed on a patch: [cbw rusize].
        infoLims = ...
            [20 26; ...
             40 52; ...
             80 106; ...
            160 242; ...
            320 484];
        
        % Plot the EHT occupied subcarriers
        bi = 1;
        startEndIdx = zeros(0,2);
        hRU = gobjects(allocInfo.NumRUs,2); % At most 2 patches per RU
        for ii = 1:allocInfo.NumRUs
            % Get the active subcarrier indices for the RU
          
            k = zeros(0,1);
            ruSize = allocInfo.RUSizes{ii};
            ruIndices = allocInfo.RUIndices{ii};
            for mru=1:numel(ruSize)
                k = [k; ehtRUSubcarrierIndices(cbw,ruSize(mru),ruIndices(mru))]; %#ok<AGROW> 
            end
            
            % Get the end index of each continuous block of subcarriers within the RU
            nonContigEnd = [find(diff(k)~=1); numel(k)];
            startIdx = 1; % The start index of the continuous block of subcarriers
            for jj = 1:numel(nonContigEnd)
                % Plot a patch of the continuous block of subcarriers
                blkIdx = startIdx:nonContigEnd(jj);
                startEndIdx(bi,:) = [k(blkIdx(1)) k(blkIdx(end))];
                y = [k(blkIdx(1)) k(blkIdx(end)) k(blkIdx(end)) k(blkIdx(1))];
                x = [0            0              1.5            1.5   ] + xoffset;
        
                % Add callback to display RU info in legend when patch clicked
                colorToUse = ii;
                hRU(ii,jj) = patch(ax,x,y,colorToUse);
                hRU(ii,jj).ButtonDownFcn = @displayRUInfo;
                hRU(ii,jj).PickableParts = 'all';
                hRU(ii,jj).UserData.RUNumber = ii;
                
                hold(ax,'on');
                startIdx = nonContigEnd(jj)+1;
                bi = bi+1;
            end
        end
        ylabel(ax,"Subcarrier Index")
        xticks(ax,[])
        ylim(ax,[-Nfft/2 Nfft/2-1]);
        ax.Box = 'on';
        
        xticks(ax,[0.25 1.25])
        ax.XTickLabel = ["Pre-EHT portion","EHT portion"];
        
        % Get the start and end subcarrier indices of each segment
        segBW = 80; % Bandwidth of segment in MHz
        numSeg = floor(cbw/segBW);
        nss = 256*segBW/20; % Number of subcarriers to show in a segment
        startIdx = -numSeg*nss/2:nss:((numSeg-1)*nss/2-1);
        endIdx = (-numSeg*nss/2+nss-1):nss:((numSeg-1)*nss/2+nss-1);
        scidx = [startIdx' endIdx']; % Each row is a segment

        h = plot(ax,NaN,NaN,'ow'); % Placeholder for empty legend

        % Plot text overlay on RU segments
        plotTextOverlay();
        
        % When initializing create a dummy entry 
        lgd = legend(ax,h,' ','Location','southoutside');
        title(lgd,"RU Details - Click an RU")
        hold(ax,'off');
    
        tb = axtoolbar(ax,{'export','pan','zoomin','zoomout','restoreview'});
        restoreviewbtn = tb.Children(5);
        restoreviewbtn.ButtonPushedFcn = {@restoreview}; % Change the callback function

        btn = gobjects(1,numSeg);
        for s = numSeg:-1:1
            btn(s) = axtoolbarbtn(tb,'push');
            btn(s).Icon = ['s' num2str(s) '.png'];
            btn(s).Tooltip = ['Zoom into 80 MHz segment ' num2str(s)];
            btn(s).UserData = s;
            btn(s).ButtonPushedFcn = @segCallback;
        end
        
        function segCallback(src,event)
            % Set axes and reset text overlay with new view
            ylim(event.Axes,scidx(src.UserData,:));
            plotTextOverlay();
        end
        function restoreview(~,event)
            % Default ButtonPushedFcn for the reset view toolbar button
            @(e,d)matlab.graphics.controls.internal.resetHelper(d.Axes,true);
            % Reset axes and text overlay
            ylim(event.Axes,[scidx(1) scidx(end)]);
            plotTextOverlay();
        end
        function plotTextOverlay()
            % Plot text overlay on RU segments. The text is only displayed
            % if the bandwidth of the view and RU size allow.

            % Delete any existing text overlays on RUs as we recreate them when
            % zooming in or out
            hExistingText = findobj(ax,'Tag','RUText');
            for a = 1:numel(hExistingText)
                delete(hExistingText(a));
            end

            userIdx = 1;
            for i = 1:allocInfo.NumRUs
                % Text with user numbers associated with RU
                if allocInfo.NumUsersPerRU(i)==1
                    userTxt = "User #"+num2str(userIdx);
                else
                    userTxt = "Users #"+num2str(userIdx)+'-'+num2str(userIdx+allocInfo.NumUsersPerRU(i)-1);
                end
                % String with RU number, user index and RU size displayed on patch
                ruPatchTxt = join(["RU #"+num2str(i) "Size: "+ruTypeInfo(i) userTxt],", ");
            
                % String with detailed RU size and index making up MRU displayed in legend
                if isMRU(i)
                    legendRUTxt = "RU # "+num2str(i)+" (MRU)";
                else
                    legendRUTxt = "RU # "+num2str(i);
                end
                legendRUDetialTxt = strings(1,0);
                for j = 1:numel(allocInfo.RUSizes{i})
                    legendRUDetialTxt = [legendRUDetialTxt (num2str(allocInfo.RUSizes{i}(j))+"-tone RU "+num2str(allocInfo.RUIndices{i}(j)))]; %#ok<AGROW> 
                    if j~=numel(allocInfo.RUSizes{i})
                        % Separate RUs within an RU by +
                        legendRUDetialTxt = [legendRUDetialTxt "+"]; %#ok<AGROW> 
                    end
                    if j==2 && numel(allocInfo.RUSizes{i})>3
                        % If lots of RUs in an M-RU split up legend over two lines
                        legendRUDetialTxt(end) = legendRUDetialTxt(end)+newline;
                    end
                end
                legendRUDetialTxt = join(legendRUDetialTxt); % Concatenate all RUs making up an M-RU separated by a space
                legendTxt = join([legendRUTxt legendRUDetialTxt userTxt],", ");
        
                % Get the equivalent bandwidth for the current view/zoom
                cyl = ylim(ax);
                cbwView = min(cbw,(diff(cyl)+1)/nss*segBW);
        
                for j = 1:size(hRU,2) % Each column is a patch making up an RU
                    if isPatch(hRU(i,j))
                        % Check if RU visible in limits
                        vert = hRU(i,j).Vertices([1 2],2);
                        if max(vert)>max(cyl) || min(vert)<min(cyl) 
                            continue
                        end
            
                        % Store useful data in patch UserData to be used in
                        % ButtonDownFcn callback
                        hRU(i,j).UserData.RUText = legendTxt;   % Text for selected RU info
                        hRU(i,j).UserData.isSelected = false;   % Is RU selected
                        hRU(i,j).UserData.hPlaceholderLine = h; % Handle to placeholder for empty legend
            
                        % Display RU info on a patch unless the patch is too small. If
                        % an RU spans multiple patches, print RU info in each patch.
                        % Use the vertices to print in the middle of each RU.
                        if (hRU(i,j).Vertices(2,2)-hRU(i,j).Vertices(1,2)+1)<infoLims(cbwView == infoLims(:,1),2)
                            continue % Do not print text on patch
                        end
                        xMiddle = hRU(i,j).Vertices(1,1)+(hRU(i,j).Vertices(end,1)-hRU(i,j).Vertices(1,1))/2;
                        yMiddle = hRU(i,j).Vertices(1,2)+(hRU(i,j).Vertices(2,2)-hRU(i,j).Vertices(1,2))/2;
                        text(ax,xMiddle,yMiddle,ruPatchTxt,'HorizontalAlignment','center','FontSize',9,'Tag','RUText','PickableParts','none');
                    end
                end
                userIdx = userIdx+allocInfo.NumUsersPerRU(i);
            end
        end
    end
    
    function [mode,isNDP] = getCompressionMode(obj)
    %getCompressionMode Get compression mode as defined in Table 36-29 of
    %IEEE P802.11be/D1.0
        mode = 0; % DL OFDMA
        isNDP = 0; % NDP packet
        if ~obj.IsOFDMA
            if numel(obj.User)==1
                mode = 1; % SU or NDP
                isNDP = obj.User{1}.APEPLength==0;
            else
                mode = 2; % DL MU-MIMO
            end
        end
    end

    function obj = subsasgn(obj,s,varargin)
    % Error if the user attempts to set a property of a RU or User which
    % does not exist
        if strcmp(s(1).type,'.')
            switch s(1).subs
                case 'RU'
                    if numel(s)==3
                        if strcmp(s(2).type,'{}')
                            % cfg.RU{1}.foo = x
                            if s(2).subs{1}>numel(obj.RU)
                                coder.internal.error('wlan:wlanHEMUConfig:IndexedRUExceedsNumber');
                            end
                        end
                    end
                case 'User'
                    if numel(s)==3
                        % cfg.User{1}.foo = x
                        if strcmp(s(2).type,'{}')
                            if s(2).subs{1}>numel(obj.User)
                                coder.internal.error('wlan:wlanHEMUConfig:IndexedUserExceedsNumber');
                            end
                        end
                    end
                otherwise
                    % Error if property to be set is private
                    mc = meta.class.fromName(class(obj));
                    propMatch = strcmp(s(1).subs,{mc.PropertyList.Name});
                    if any(propMatch) && strcmp(mc.PropertyList(propMatch).SetAccess,'private')
                        coder.internal.error('MATLAB:class:SetProhibited',s(1).subs,class(obj));
                    end
            end
        end
        obj = builtin('subsasgn',obj,s,varargin{:});
    end
    
    function varargout = validateConfig(obj,varargin)
    %validateConfig Validate the dependent properties of ehtMUConfig object
    %   validateConfig(obj) validates the dependent properties for the
    %   specified ehtMUConfig configuration object.
    % 
    %   For INTERNAL use only, subject to future changes
    %
    %   validateConfig(CFG,MODE) validates only the subset of dependent
    %   properties as specified by the MODE input. MODE must be one of:
    %       'EHTLTFGI'
    %       'DataLocationLength'     
    %       'Coding'
    %       'CyclicShift'
    %       'Full'

        narginchk(1,2);
        nargoutchk(0,1);
        if (nargin==2)
            mode = varargin{1};
        else
            mode = 'Full';
        end

        switch mode
            case 'EHTLTFGI'
                % Validate the EHT-LTF type and GuardInterval
                validateEHTLTFGI(obj);
            case 'DataLocationLength'
                % Validate and get Length properties of EHT MU packet
                s = validateMCSLength(obj);
            case 'Coding'
                % Validate coding parameters
                validateCoding(obj);
            case 'CyclicShift'
                % Validate PreEHTCyclicShifts against NumTransmitAntennas
                validatePreHECyclicShifts(obj);
            otherwise % ehtData
                % Full object validation

                % Validate Spatial mapping properties and spatial mapping matrix 
                validateSpatialMapping(obj)

                % Validate PreEHTCyclicShifts against NumTransmitAntennas
                validatePreEHTCyclicShifts(obj);

                % Validate MCS and length
                s = validateMCSLength(obj);
        end

        if nargout == 1
            varargout{1} = s;
        end
    end
  end

  methods (Access = private)
    function validateCoding(obj)
    %   validateCoding Coding properties for ehtMUConfig configuration object
    %
    %   Validated property-subset includes:
    %     ChannelBandwidth, NumSpaceTimeStreams, MCS, ChannelCoding, RU
    %     Size, STAID

        % Validate ChannelCoding and the number of space-time streams for all users
        for userIdx = 1:numel(obj.User)
            if strcmp(obj.User{userIdx}.ChannelCoding,'BCC')
                coder.internal.errorIf(obj.RU{obj.User{userIdx}.RUNumber}.Size>242,'wlan:he:InvalidBCCRUSize');
                coder.internal.errorIf(obj.User{userIdx}.NumSpaceTimeStreams>4,'wlan:he:InvalidNSTS');
                if any(obj.User{userIdx}.MCS==[10 11 12 13])
                    error('BCC encoding is not defined for MCS 10 11 12 or 13.');
                end
            end
        end

        validateSTAID(obj);
    end
    
    function validateSTAID(obj)
    %   validateSTAID Validate STAID and APEPLength
    %   Validate at least one user is active for OFDMA. For non-OFDMA STAID
    %   for the user must not be 2046. The APEPLength for non-OFDMA must
    %   not be zero for any user if the number of users are greater then 1
    %
    %   Validated property-subset includes: 
    %     STAID, APEPLength

        % Validate STAID is not 2046 for all users
        userActive = true(1,numel(obj.User)); % Vector indicating if a user object is active
        if obj.IsOFDMA
            for userIdx = 1:numel(obj.User)
                if obj.User{userIdx}.STAID==2046
                    % If STAID is 2046, then RU carries no data, and user is inactive
                    userActive(userIdx) = false;
                end
                % APEPLength should not be zero for OFDMA users
                if obj.User{userIdx}.APEPLength==0
                    error('APEPLength must not be zero for an OFDMA user.');
                end
            end
            % Make sure at least one of the users is active
            numActiveUsers = sum(userActive==true);
            coder.internal.errorIf(numActiveUsers==0,'wlan:wlanHEMUConfig:NoActiveUsers');
        else
            % If STAID is 2046 in a MU-MIMO RU, then error
            for userIdx = 1:numel(obj.User)
                coder.internal.errorIf(obj.User{userIdx}.STAID==2046,'wlan:wlanHEMUConfig:InactiveUserInMU');
                if obj.User{userIdx}.APEPLength==0
                    userActive(userIdx) = false;
                end
            end
            % APEPLength of any user must not be zero in MU-MIMO
            if numel(userActive)>1 && ~all(userActive)
                error('APEPLength of any user must not be zero in MU-MIMO configuration.')
            end
        end
    end
    
    function s = validateMCSLength(obj)
    %   validateMCSLength Length properties for ehtMUConfig configuration object
    %
    %   Validated property-subset includes:   
    %     ChannelBandwidth, NumUsers, NumSpaceTimeStreams, MCS,
    %     ChannelCoding, GuardInterval, APEPLength
    
        % Validate EHTLTFType and GuardInterval for EHT-LTF
        validateEHTLTFGI(obj);
        
        % Validate coding related properties
        validateCoding(obj);

        [psduLength,txTime,commonCodingParams] = ehtPLMETxTimePrimative(obj);
        
        sf = 1e3; % Scaling factor to convert time from ns to us
        % Set output structure
        s = struct( ...
            'NumDataSymbols',commonCodingParams.NSYM, ... 
            'TxTime',txTime/sf, ...% TxTime in us
            'PSDULength',psduLength);

        % Validate txTime (max 5.484ms for EHT format)
        coder.internal.errorIf(s.TxTime>5484,'wlan:shared:InvalidPPDUDuration',round(s.TxTime),5484);
    end

    function validateSpatialMapping(obj)
    %validateSpatialMapping Validate the spatial mapping properties
    %
    %   Validated property-subset includes:
    %     ChannelBandwidth, NumTransmitAntennas, NumSpaceTimeStreams,
    %     SpatialMapping, SpatialMappingMatrix

        % Validate SpatialMappingMatrix, SpatialMapping and the number of transmit antennas
        infoRUs = ruInfo(obj);
        for i = 1:infoRUs.NumRUs
            % NumTx and Nsts: numTx cannot be less than sum(Nsts)
            coder.internal.errorIf(obj.NumTransmitAntennas < infoRUs.NumSpaceTimeStreamsPerRU(i),'wlan:he:NumSTSLargerThanNumTx',infoRUs.RUNumbers(i),infoRUs.NumSpaceTimeStreamsPerRU(i),obj.NumTransmitAntennas);
            if strcmp(obj.RU{i}.SpatialMapping,'Custom')
                % Validate spatial mapping matrix
                SPMtx = obj.RU{i}.SpatialMappingMatrix;
                is3DFormat  = (ndims(SPMtx) == 3) || (iscolumn(SPMtx) && ~isscalar(SPMtx));
                numSTSTotal = size(SPMtx,1+is3DFormat);
                numTx = size(SPMtx, 2+is3DFormat);
                numST = sum(infoRUs.RUSizes{i});
                coder.internal.errorIf((is3DFormat && (size(SPMtx, 1) ~= numST)) || (numSTSTotal ~= infoRUs.NumSpaceTimeStreamsPerRU(i))  || (numTx ~= obj.NumTransmitAntennas), ...
                    'wlan:he:MappingMtxNotMatchOtherProp',infoRUs.NumSpaceTimeStreamsPerRU(i),obj.NumTransmitAntennas,numST,i);
            else
                coder.internal.errorIf(strcmp(obj.RU{i}.SpatialMapping,'Direct') && infoRUs.NumSpaceTimeStreamsPerRU(i)~= obj.NumTransmitAntennas,'wlan:he:NumSTSNotEqualNumTxDirectMap',i,infoRUs.NumSpaceTimeStreamsPerRU(i),obj.NumTransmitAntennas);            
            end
        end
    end
    
    function validateEHTLTFGI(obj)
    %validateEHTLTFGI Validate the EHT-LTF type and GuardInterval of
    %ehtMUConfig object as defined in Table 36-44 of IEEE P802.11be/D1.0.
    %
    %   Validated property-subset includes:
    %     EHTLTFType, GuardInterval

        [~,isNDP] = getCompressionMode(obj);
        switch obj.GuardInterval
            case 0.8
                if obj.EHTLTFType==1
                   error('The combination of GuardInterval = 0.8 and EHTLTFType = 1 is not supported.');
                end
                if obj.EHTLTFType==4 && isNDP
                    error('The combination of GuardInterval = 0.8 and EHTLTFType = 4 is not supported for NDP.');
                end
            case 1.6
                if obj.EHTLTFType~=2
                    error('The combination of GuardInterval = 1.6 and EHTLTFType = 1 and 4 is not supported.');
                end
            otherwise % 3.2
                if obj.EHTLTFType~=4
                    error('The combination of GuardInterval = 3.2 and EHTLTFType = 1 and 2 is not supported.');
                end
        end
    end
    
    function validatePreEHTCyclicShifts(obj)
    %   validatePreEHTCyclicShifts Validate PreEHTCyclicShifts values against NumTransmitAntennas
    %
    %   Validated property-subset includes:
    %     PreEHTCyclicShifts, NumTransmitAntennas

        numTx = obj.NumTransmitAntennas;
        csh = obj.PreEHTCyclicShifts;
        if numTx>8 && ~(numel(csh)>=numTx-8)
            error('The PreEHTCyclicShifts should be a row vector of cyclic shift values of length %s. ',num2str(numTx-8));
        end
    end
  end
       
  methods (Access = protected)
    function flag = isInactiveProperty(obj,prop)
        if strcmp(prop,'PreEHTCyclicShifts')
            % Hide PreEHTCyclicShifts when NumTransmitAntennas <=8
            flag = obj.NumTransmitAntennas<=8;
        elseif strcmp(prop,'USIGBitsSeed')
            % Hide USIGBitsSeed when USIGBitsSource is not 'mt19937ar with seed'
            flag = ~strcmp(obj.USIGBitsSource,'mt19937ar with seed');
        elseif strcmp(prop,'USIGBits')
            % Hide USIGBits when USIGBitsSource is not 'User-defined'
            flag = ~strcmp(obj.USIGBitsSource,'User-defined');
        elseif strcmp(prop,'EHTSIGBitsSeed')
            % Hide EHTSIGBitsSeed when EHTSIGBitsSource is not 'mt19937ar with seed'
            flag = ~strcmp(obj.EHTSIGBitsSource,'mt19937ar with seed');
        elseif strcmp(prop,'EHTSIGBits')
            % Hide EHTSIGBits when EHTSIGBitsSource is not 'User-defined'
            flag = ~strcmp(obj.EHTSIGBitsSource,'User-defined');
        elseif any(strcmp(prop,{'PuncturingPattern','PuncturedChannelFieldValue'}))
            flag = obj.IsOFDMA || any(strcmp(obj.ChannelBandwidth,{'CBW20','CBW40'}));
        elseif any(strcmp(prop,'AllocationIndex'))
            flag = ~obj.IsOFDMA;
        elseif strcmp(prop,'PreEHTPhaseRotation')
            flag = ~strcmp(obj.ChannelBandwidth,'CBW320');
        else
            flag = false;
        end
    end
end
    
end

function [ru,user,userRUNumber,puncturingPattern] = ehtRUAllocation(varargin)
% Returns a cell array of RUs, Users and the user RU number
    
    s = ehtAllocationInfo(varargin{:});
    numRUs = s.NumRUs; % Only single RU configuration is supported
    numUsers = s.NumUsers; % Only single user configuration is supported

    Usertmp = cell(1,numUsers);
    RUtmp = cell(1,numRUs);
    userIdx = 1;
    userRUNumber = zeros(1,numUsers);
    for i = 1:numRUs
        ruUserNumber = zeros(1,s.NumUsersPerRU(i));
        for j = 1:s.NumUsersPerRU(i)
            userRUNumber(userIdx) = i;
            ruUserNumber(j) = userIdx;
            userIdx = userIdx+1;
        end
        RUtmp{i} = ehtRU(s.RUSizes{i},s.RUIndices{i},ruUserNumber);
    end

    for u = 1:numUsers
        Usertmp{u} = ehtUser(userRUNumber(u),'PostFECPaddingSeed',u);
    end

    user = Usertmp;
    ru = RUtmp;
    puncturingPattern = s.PuncturingPattern;
end

function ehtValidateSIGits(bits)
%ehtValidateSIGBits Validate U-SIG and EHT-SIG field bits

    % Bits must be a binary column vector.
    mustBeNumeric(bits);
    mustBeNonempty(bits);
    mustBeMember(bits,[0 1]);
end

function f = inAllocation(allocationIndex,indices)
%inAllocation returns true if any element of indices is present in the
%vector allocationIndex
    f = any(allocationIndex==indices','all');
end

function displayRUInfo(src,~)  
    ax = src.Parent; % Handle to axis
    f = ax.Parent;   % Handle to figure
    
    % Determine if control key pressed
    modifiers = get(f,'CurrentModifier');
    ctrlPressed = ismember('control',modifiers);
    
    % If a RU is currently selected and is ctrl-clicked then remove it from
    % those displayed with the legend
    if ctrlPressed && src.UserData.isSelected
        unselectPatch = true;
    else
        unselectPatch = false;
    end
    
    % Get handles to patch objects which show RUs
    hPatch = findobj(f,'Type','Patch');
    % Remove patches which are not RUs
    rmIdx = false(numel(hPatch),1);
    for i = 1:numel(hPatch)
        rmIdx(i) = isempty(hPatch(i).UserData);
    end
    hPatch(rmIdx) = [];
    
    % Reset all patches to have a default edge color and width if ctrl key
    % is not held. This means that only the RU clicked on will be
    % highlighted
    if ~ctrlPressed
        for i = 1:numel(hPatch)
            resetPatch(hPatch(i));
        end
    end
    
    % Select or unselect the patch clicked on depending if the ctrl is
    % pressed or not
    for i = 1:numel(hPatch)
        if hPatch(i).UserData.RUNumber==src.UserData.RUNumber
            % If any patch matches the patch clicked on then select or
            % unselect all patches related to that RU. A patch is
            % highlighted when selected.
            if unselectPatch
                resetPatch(hPatch(i));
            else
                selectPatch(hPatch(i));
            end
        end
    end
    
    % Create an array of legend text strings and handles to patches which
    % are selected
    strLegend = strings(1,0);
    hPatchLegend = gobjects(0,1);
    for i = 1:numel(hPatch)
        if hPatch(i).UserData.isSelected
            strLegend = [strLegend hPatch(i).UserData.RUText]; %#ok<AGROW>
            hPatchLegend = [hPatchLegend hPatch(i)]; %#ok<AGROW>
        end
    end
    % Remove duplicates caused by multiple patches per RU so they are not
    % displayed in the legend
    [~,uniqueIdx] = unique(strLegend);
    strLegend = strLegend(uniqueIdx);
    hPatchLegend = hPatchLegend(uniqueIdx);

    % If no RUs are selected then display a special message of the legend
    if isempty(hPatchLegend)
        hPatchLegend = src.UserData.hPlaceholderLine;
        strLegend = ' ';
    end
    
    % Display the RU information in the legend
    legend(ax,hPatchLegend,strLegend,'Location','southoutside')
end

function out = isPatch(in)
    % Returns true if the object passed in is a patch
    out = isa(in,'matlab.graphics.primitive.Patch');
end

function selectPatch(hPatch)
    % Select/highlight patch
    hPatch.EdgeColor = [1 0 0];
    hPatch.LineWidth = 2;
    hPatch.UserData.isSelected = true;
end

function resetPatch(hPatch)
    % Unselect/reset patch
    hPatch.EdgeColor = [0 0 0];
    hPatch.LineWidth = 0.5;
    hPatch.UserData.isSelected = false;
end