function y = ehtData(txPSDU,cfg,varargin)
%ehtData EHT Data field carrying the PSDU(s)
%
%   Y = ehtData(PSDU,CFG) generates the EHT Data field carrying the
%   PSDU(s) time-domain signal for the EHT transmission format.
%
%   Y is the time-domain EHT Data field signal. It is a complex matrix of
%   size Ns-by-Nt, where Ns represents the number of time-domain samples
%   and Nt represents the number of transmit antennas.
%
%   PSDU is the PHY service data unit input to the PHY. For single-user,
%   PSDU can be a double or int8 typed binary column vector of length
%   cfg.getPSDULength*8.
%
%   CFG is the format configuration object of type <a
%   href="matlab:help('ehtMUConfig')">ehtMUConfig</a>.
%
%   Y = ehtData(...,SCRAMINIT) optionally allows specification of the
%   scrambler initialization.
%
%   When not specified, SCRAMINIT defaults to a value of 93. When
%   specified, it can be a double or int8-typed integer scalar or 1-by-Nu
%   row vector between 1 and 2047, inclusive, where Nu represents the
%   number of users.
%
%   Y = ehtData(...,SCRAMINIT,OSF) optionally allows specification of the
%   oversampling factor. When not specified, OSF defaults to 1.

%   Copyright 2020-2021 The MathWorks, Inc.

cbw = wlan.internal.cbwStr2Num(cfg.ChannelBandwidth);
allocationInfo = ruInfo(cfg);
numRUs = allocationInfo.NumRUs;
ruSizes = allocationInfo.RUSizes;
numUsers = allocationInfo.NumUsers;
alpha = allocationInfo.PowerBoostFactorPerRU;
numSTSTotalRU = allocationInfo.NumSpaceTimeStreamsPerRU;
osf = 1;

% Default baseband rate
if nargin == 2
    scramInitBits = uint8(repmat([0; 0; 0; 0; 1; 0; 1; 1; 1; 0; 1],1,numUsers)); % Default is 93
else
    % Validate scrambler initialization input
    scramInitBits = varargin{1};
    if nargin == 4
       osf = varargin{2}; 
    end
end
[commonCodingParams,userCodingParams] = ehtCodingParameters(cfg);

numTx = cfg.NumTransmitAntennas;
% Get OFDMA tone allocation constants for the RUs/MRUs
tac = wlan.internal.heRUToneAllocationConstants(cellfun(@sum,ruSizes));
cardKr = tac.NST;

Nfft = 256*cbw/20;
ofdmGrid = complex(zeros(Nfft,commonCodingParams.NSYM,numTx));

NUSIG = 2; % Number of OFDM symbols in U-SIG field
sigInfo = ehtSIGCodingInfo(cfg);
NEHTSIG = sigInfo.NumSIGSymbols; % Number of OFDM symbols in EHT-SIG field
z = 2+NUSIG+NEHTSIG; % Pilot symbol offset

if ~iscell(txPSDU)
    txPSDUCell = {txPSDU};
else
    txPSDUCell = txPSDU;
end

% Frequency and spatial mapping
for j = 1:numRUs
    % Get the index of the RU object containing the active RU properties
    iru = allocationInfo.RUNumbers(j);

    % Get current RU assignment details
    ru = cfg.RU{iru};
    numSTSTotal = allocationInfo.NumSpaceTimeStreamsPerRU(j);
    numUsers = allocationInfo.NumUsersPerRU(j);

    ruGrid = coder.nullcopy(complex(zeros(tac.NST(j),commonCodingParams.NSYM,numSTSTotal)));

    % Get indices of data and pilots within RU (without Nulls) and active frequency indices.
    [ruMappingInd,k] = ehtOccupiedSubcarrierIndices(cbw,ru.Size,ru.Index);

    % Encode, modulate, and map each user within the RU to space-time streams
    startSpaceTimeStream = 1;
    for i = 1:numUsers
        u = ru.UserNumbers(i);
        user = cfg.User{u};

        if user.APEPLength~=0
            if ~strcmp(user.PostFECPaddingSource,'User-defined')
                % Generate post-FEC padding bits
                postFECPaddingBits = randomPostFECPadding(userCodingParams(u).NPADPostFEC,user.PostFECPaddingSource,user.PostFECPaddingSeed);
            else
                % Repeat/cut/reshape user input bits
                postFECPaddingBits = wlan.internal.parseInputBits(int8(user.PostFECPaddingBits),userCodingParams(u).NPADPostFEC);
            end

            % Generate STS for the user
            stsPerUser = ehtSTSPerUser(txPSDUCell{u},scramInitBits(:,u),postFECPaddingBits,ru.Size,userCodingParams(u)); % Needs RU sizes and order

            % Determine the indices of space-time streams to map
            stsIdx = startSpaceTimeStream-1+(1:user.NumSpaceTimeStreams);
            % Map user data and pilots
            ruGrid(ruMappingInd.Data,:,stsIdx) = stsPerUser;
            n = 0:commonCodingParams.NSYM-1;
            ruGrid(ruMappingInd.Pilot,:,stsIdx) = ehtPilots(ru.Size,user.NumSpaceTimeStreams,n,z); % Needs RU sizes and order
        end
        startSpaceTimeStream = startSpaceTimeStream+user.NumSpaceTimeStreams;
    end

    % Cyclic shift
    ruGrid = wlan.internal.heCyclicShift(ruGrid,cbw,k);

    % Spatial mapping
    txRUGrid = wlan.internal.spatialMap(ruGrid,ru.SpatialMapping,numTx,ru.SpatialMappingMatrix);

    % Calculate per RU scaling
    ruScalingFactor = alpha(j)/sqrt(numSTSTotalRU(j));

    % Map subcarriers to full FFT grid and scale
    ofdmGrid(k+Nfft/2+1,:,:) = txRUGrid*ruScalingFactor;
end

% Overall scaling factor
allScalingFactor = Nfft/sqrt(sum(alpha.^2.*cardKr));

% OFDM modulate and scale
switch cfg.GuardInterval
    case 0.8
        cpLen = 0.8*cbw;
    case 1.6
        cpLen = 1.6*cbw;
    otherwise % 3.2
        cpLen = 3.2*cbw;
end
y = wlan.internal.wlanOFDMModulate(ofdmGrid,cpLen,osf)*allScalingFactor;
end


function postFECPPadBits = randomPostFECPadding(NPADPostFEC,randomStream,seed)
    persistent stream

    if strcmp(randomStream,'mt19937ar with seed')
        if isempty(stream)
            if coder.target('MATLAB')
                stream = RandStream('mt19937ar');
            else
                stream = coder.internal.RandStream('mt19937ar');
            end
        end
        % Use same seed when generating each packet
        reset(stream,seed);
        postFECPPadBits = randi(stream,[0 1],NPADPostFEC,1,'int8');
    else
        % Use global stream
        postFECPPadBits = randi([0 1],NPADPostFEC,1,'int8');
    end
end