function txObj = WIFI_TX(wifi_params,txObj,i_mcs)
% WIFI transmitter function


%% DEFINE MODEL PARAMETERS



% define parameters of used MCS
MCS = wifi_params.MCS(i_mcs);

% define modulation parameters
hMod = wifi_params.modulation(log2(MCS.M)).hMod;
modNorm = wifi_params.modulation(log2(MCS.M)).hMod_factor;

% define channel coding parameters


n_fft = wifi_params.mapping.n_fft;
n_tot = wifi_params.mapping.n_tot;

LENGTH = wifi_params.general.LENGTH;
service_length = wifi_params.mapping.service_length;

N_sym = wifi_params.coding.N_sym;


%% GENERATE PSDU INFORMATION

% PSDU_tx = randi([0 1],LENGTH*8,1);
PSDU_tx = ones(LENGTH*8,1);


%     PSDU_test = (2*(size(PSDU_tx,1)+ service_length + wifi_params.coding.tail_length + OneStream)+rem3)/MCS.N_ss;
    OneStream = (2*rem(MCS.N_ss,2))*abs(fix(MCS.N_ss/2)-1);
    PSDU_test = (2*(size(PSDU_tx,1) + service_length + wifi_params.coding.tail_length + wifi_params.coding.N_pad_bits + OneStream));%/MCS.N_ss;
%     if rem(PSDU_test,MCS.N_ss)~=0
%         a = 1
%         rem(PSDU_test,MCS.N_ss)
%         
%     elseif rem((PSDU_test/MCS.N_ss),MCS.N_dbps) ~=0
%         rem((PSDU_test/MCS.N_ss),MCS.N_dbps)
%        
%     end
    
    
%    % PSDU_test = ((any(rem(PSDU_test,MCS.N_cbps)) + fix(PSDU_test/MCS.N_cbps)))*MCS.N_cbps;
%     
%     %PSDU_test = round((((PSDU_test*MCS.N_ss)-rem3)/2)- service_length - wifi_params.coding.tail_length - OneStream);

PSDU_tx_plus_service = [ones(service_length,1); PSDU_tx];

%% SCRAMBLING
switch wifi_params.coding.type
    case 'BCC'
        if MCS.N_es == 1
            DEC_offset = wifi_params.coding.tail_length;
        elseif MCS.N_es == 2
            DEC_offset = 2*wifi_params.coding.tail_length;
        else
            error('Wrong number of BCC encoders');
        end
        scramblerIn = [PSDU_tx_plus_service; zeros(wifi_params.coding.N_pad_bits,1); zeros(DEC_offset,1)];
        scramblerIn = [PSDU_tx_plus_service; ones(wifi_params.coding.N_pad_bits,1); zeros(DEC_offset,1)];
        
        scr_seed = wifi_params.scrambling.scr_seed;
        scramblingSeq = BCC_ScramblingSeqGen(scramblerIn, scr_seed);
        scramblerOut = xor(scramblerIn, scramblingSeq);
        
        %tail of encoderIn must be zero (6 or 12 bits) - now in BCC
        %ENCODER PARSING
        %scramblerOut(end-DEC_offset+1:end) = zeros(DEC_offset,1);
        %scramblerOut = [PSDU_tx_plus_service; ones(wifi_params.coding.N_pad_bits,1); zeros(DEC_offset,1)];
    case 'LDPC'
        
end

%% BCC ENCODER PARSING - J146
    encoderParserIn = scramblerOut;

%     %Check if is posible split input data equally between 6 streams (no need anymore)
%     if (n_streams == 6)&&(rem(length(encoderParserIn),6) ~= 0)
%         
%         %add 2 or 4 ones at the end
%         encoderParserIn_add = [encoderParserIn; ones((2/(rem(length(encoderParserIn),6)))*4,1)] 
%         
%         %remove 2 or 4 bits at the end
%         encoderParserIn = encoderParserIn(1:end-rem(length(encoderParserIn),6));
%     end
    
    %split input data equally between N_es data streams - robin round
    %encoderParserIn = [encoderParserIn ;zeros(rem(numel(encoderParserIn),MCS.N_es),1)];
    for i_encoderParser = 1:MCS.N_es
        encoderParserOut(:,i_encoderParser) =  encoderParserIn(i_encoderParser:MCS.N_es:end, 1);
        
        % tail of encoderIn must be zero (6 or 12 bits)
        encoderParserOut(end-DEC_offset+1:end,i_encoderParser) = zeros(DEC_offset,1);
    end



%% CHANNEL CODING
for i_bccEncoder = 1:MCS.N_es
    parserCoder_transfer = encoderParserOut(:,i_bccEncoder);
    
    if strcmp(wifi_params.coding.type,'BCC')
        encoderIn = parserCoder_transfer;
        switch MCS.CR
            case 1/2
                puncpat = wifi_params.coding.puncturing(1).pattern;
            case 2/3
                puncpat = wifi_params.coding.puncturing(2).pattern;
            case 3/4
                puncpat = wifi_params.coding.puncturing(3).pattern;
            case 5/6
                puncpat = wifi_params.coding.puncturing(4).pattern;
        end
        trellis = wifi_params.coding.trellis;
        if ~istrellis(trellis)
            error('Wrong trellis structure');
        end
        hEnc = comm.ConvolutionalEncoder(trellis,...
            'PuncturePatternSource','Property',...
            'PuncturePattern',puncpat.',...
            'FinalStateOutputPort',true); %);

        [encoderOut(:,i_bccEncoder), CONV_FSTATE(:,i_bccEncoder)]= step(hEnc,encoderIn);
        if CONV_FSTATE ~= 0
            error('BCC is not performed correctly - final state of the encoder is nonzero');
        end
        encoderIn_storage (:,i_bccEncoder) = encoderIn(1:end-DEC_offset); % save to results structure without zeros tail bits

    else
        % generate encoder structure
        PSDU_tx_plus_service_tmp = PSDU_tx_plus_service;


        hEnc = comm.LDPCEncoder(wifi_params.coding.UsedLDPC_matrix);
        LDPC_N = size(wifi_params.coding.UsedLDPC_matrix,2);
        LDPC_K = LDPC_N - size(wifi_params.coding.UsedLDPC_matrix,1);
        % mux PSDU plus SERVICE into codewords
        PSDU_tx_plus_service_cw = cell(wifi_params.coding.N_cw,1);

        for i_cw = 1:wifi_params.coding.N_cw
            PSDU_tx_plus_service_cw{i_cw} = PSDU_tx_plus_service_tmp(1:wifi_params.coding.shortening.n_bitwoshrt_cw(i_cw),1);
            if i_cw ~= wifi_params.coding.N_cw
                PSDU_tx_plus_service_tmp = PSDU_tx_plus_service_tmp(wifi_params.coding.shortening.n_bitwoshrt_cw(i_cw)+1:end,1);
            end
        end

        % inserting shortening bits according to N_shrt and N_cw parameter
        shortenedCodeword = zeros(wifi_params.coding.N_bpcw,1);
        for i_cw = 1:wifi_params.coding.N_cw
            actualCodeword = PSDU_tx_plus_service_cw{i_cw};
            actualCodeword = [actualCodeword; zeros(wifi_params.coding.shortening.n_shrt_cw(i_cw),1)];
            PSDU_tx_plus_service_cw{i_cw} = actualCodeword;
        end

        % LDPC encoding process for each codeword
        encoded_PSDU_tx = cell(wifi_params.coding.N_cw,1);
        encoded_PSDU_tx_NoRepeated = cell(wifi_params.coding.N_cw,1); % #1

        for i_cw = 1:wifi_params.coding.N_cw

            inputEncoderCodeword = PSDU_tx_plus_service_cw{i_cw};
            %         dataIn_cw{i_cw} = inputEncoderCodeword;
            LDPC_encoderOut = hEnc.step(inputEncoderCodeword);
            % demux encoded data and parity bits
            punctured_parityBits = LDPC_encoderOut(wifi_params.coding.puncturing.parity_ind(i_cw,:)); % include puncturing
            % discard shortening bits
            shortenedEncoderCodeword = LDPC_encoderOut(wifi_params.coding.shortening.bitwoshrt_ind(i_cw,:));

            % mux encodedData and punctured parity bits
            encodedDataPlusParity = [shortenedEncoderCodeword; punctured_parityBits];
            encodedDataPlusParity_NoRepeated = encodedDataPlusParity; % #1
            % repeating bits
            if wifi_params.coding.repeating.n_rep_cw(i_cw) ~= 0
                %             disp('Data should be repeated')
                %             disp(['CW#',num2str(i_cw),'Bitu po kan kodovani ', num2str(wifi_params.coding.shortening.n_bitwoshrt_cw(i_cw)+sum(wifi_params.coding.puncturing.parity_ind(i_cw,:))),' | Bitu k dispozici ',num2str(wifi_params.coding.N_bpcw+sum(wifi_params.coding.puncturing.parity_ind(i_cw,:)))])
                %
                if wifi_params.coding.puncturing.N_punc ~= 0
                    %                 disp(['NOTE: In this case repetition should not be performed'])
                else
                    encodedDataPlusParity = [encodedDataPlusParity; encodedDataPlusParity(1:wifi_params.coding.repeating.n_rep_cw(i_cw))];
                end
            end

            encoded_PSDU_tx{i_cw} = encodedDataPlusParity;
            encoded_PSDU_tx_NoRepeated{i_cw} = encodedDataPlusParity_NoRepeated;  % #1
        end

        if length(vertcat(encoded_PSDU_tx{:})) ~= wifi_params.coding.N_avbits
            error('Error in puncturing operation --> LDPC encoder')
        end

        %     dataIn = vertcat(PSDU_tx_plus_service_cw{:});
        encoderIn = PSDU_tx_plus_service;

        % LDPC encoding parser
        encoderOut = vertcat(encoded_PSDU_tx{:});

    end
end
%% STREAM PARSING  (N_es -> N_ss)
    streamParsingIn = encoderOut;
    streamParsingTransfer = [];
%     
%     for i_Nes = 1:wifi_params.MCS(i_mcs).N_es
%         streamParsingTransfer = [streamParsingTransfer; streamParsingIn(:,i_Nes)];
%     end
%     n_streamsParsed = length(streamParsingTransfer)/wifi_params.MCS(i_mcs).N_ss;
%     
%     for i_Nss = 1:wifi_params.MCS(i_mcs).N_ss
%         streamParsingOut(:,i_Nss) = streamParsingTransfer(n_streamsParsed*(i_Nss-1)+1:n_streamsParsed*i_Nss);
%     end

    
    s_parser = max([(log2(MCS.M))/2, 1]);
    S_parser = MCS.N_ss*s_parser;
         
    for i = 1:MCS.N_es     
        streamParsingTransfer = [streamParsingTransfer reshape(streamParsingIn(:,i),S_parser,[])];
    end
     
    streamParsingTransfer =  reshape(streamParsingTransfer,s_parser,[]);
    streamParsingTransfer2 = [];

    for i = 1:MCS.N_ss
        streamParsingTransfer2 = streamParsingTransfer(:,i:MCS.N_ss:end);
        streamParsingOut2(:,i) = reshape(streamParsingTransfer2,1,[]); 
    end

    % streamParsingTransfer2 =  streamParsingTransfer(s_parser*((i_Nss-1)+((i_streamsParsed-1)*MCS.N_ss)):s_parser*(i_Nss+((i_streamsParsed-1)*MCS.N_ss)));

     
     

%% INTERLEAVING

for i_bccInterleaver = 1:wifi_params.MCS(i_mcs).N_ss
    interleaver_transfer = streamParsingOut2(:,i_bccInterleaver);

    switch wifi_params.coding.type
        case 'BCC'
            interleaverIn = reshape(interleaver_transfer,wifi_params.interleaving.N_cbpss,[]);
            % first permutation
            permOut = interleaverIn((wifi_params.interleaving.i{1}+1).',:);
            % second permutation
            permOut = permOut(wifi_params.interleaving.j{1}+1,:);
            % third permutation (only if Nss > 1)
            if wifi_params.MCS(i_mcs).N_ss > 10
                permOut = permOut(wifi_params.interleaving.r{1}+1,:);
            end
            interleaverOut(:,i_bccInterleaver) = reshape(permOut,[],1);

    %         interleaverOut = encoderOut;
        case 'LDPC'
            interleaverOut = encoderOut;
    end
end
%% MODULATION MAPPING

for i_modulationMapper = 1:wifi_params.MCS(i_mcs).N_ss
    modulationMapper_transfer = interleaverOut(:,i_modulationMapper);
    
    modulatedDataSymbol = step(hMod,modulationMapper_transfer)*modNorm;
    modulatedDataOut(:,i_modulationMapper) = modulatedDataSymbol;
    
    if strcmp(wifi_params.coding.type,'LDPC')
        encoderOut = vertcat(encoded_PSDU_tx_NoRepeated{:});
    end
end

%% STBC

STBCEncIn = modulatedDataOut;
STBCEncOut = [];

if wifi_params.general.includeSTBC == true
    
    hOSTBCEnc = comm.OSTBCEncoder;
    release(hOSTBCEnc);
    hOSTBCEnc.NumTransmitAntennas = 2; 

    for i_STBC = 1:wifi_params.MCS(i_mcs).N_ss
        STBCEncTransfer = STBCEncIn(:,i_STBC);

        STBCEncoded = step(hOSTBCEnc, STBCEncTransfer);
        
        STBCEncOut = [STBCEncOut STBCEncoded];
    end
    
else
    % No STBC need
    STBCEncOut = STBCEncIn;
end


%% Cyclic Shift Diversity per STS
    CSDIn = STBCEncOut;
    
    for i_csd = 1:wifi_params.general.N_sts
        
    CSDOut(:,i_csd) = circshift(CSDIn(:,i_csd),round(wifi_params.mapping.csd_per_sts(i_csd)*wifi_params.Fs));
    end
    


%% generate preambles and concatenate to frame
% switch txObj.mode
%     case 'Greenfield (HT)'
%         % preamble
%         % HT-STF
%
%         % HT-LTF1
%
%         % HT-SIG
%
%         % HT-LTFn
%
%     case 'Mixed (Non-HT)'
%         error('chyba');
%     otherwise
%         error('Wrong operation mode');
% end

% mapping to frame (w/o pilots, etc.)

x = zeros(wifi_params.mapping.n_data+wifi_params.mapping.n_pilot+numel(wifi_params.mapping.i_DC),wifi_params.mapping.n_data_symbols_per_frame,1);    


% map payload
% xRGrid(wifi_params.mapping.map_data) = modSignal_data; % asi zbytecne...
%% SPATIAL MAPPING

 %Direct mapping (1-to-1 mapping from space time stream (STBC)to TX) 

   spatialMappingIn = CSDOut;
   spatialMappingOut = spatialMappingIn;
   
   new_N_sym = ones(size(wifi_params.mapping.map_data,1),1);
   new_N_sym = numel(new_N_sym(wifi_params.mapping.map_data(:,1)));
   new_N_sym = size(spatialMappingOut,1)/new_N_sym;

   x = repmat(x(:,1),1,new_N_sym);
   
%% create ofdm signal
%prealocated output


for i_amountOfAntennas = 1:wifi_params.general.N_sts

    % add pilots
    % mod_pilots = ones(4,n_sf);
    modulatedPilotSymbols = randsrc(new_N_sym*wifi_params.mapping.n_pilot,1,[-1 1]); % dodelat dle normy! BPSK = {-1,+1}
    modulatedPilotSymbols = repmat(wifi_params.mapping.pilots_value.',new_N_sym,1);

    % x = zeros(length(wifi_params.mapping.map_data),Nsym)
   %if (wifi_params.general.n_users(1,2)>2)
        mapData = wifi_params.mapping.map_data(:,1);
        mapData = repmat(mapData,1,new_N_sym);
  %  else
       % mapData = wifi_params.mapping.map_data;
  %  end
    
    x(mapData) = spatialMappingOut(:,i_amountOfAntennas);
    % x(mapData) = ones(sum(sum(wifi_params.mapping.map_data)),1)*999;

    % phase rotation - (CSD per STS - Pilot insertion and cyclic shift diversity per Space-Time Streams)
    phase_rot_temp = ones(size(x));
    phase_rot_temp(repmat(wifi_params.mapping.map_phase_rotation(:,1),1,new_N_sym)) = wifi_params.mapping.phase_rotation_factor;
    x = x.*phase_rot_temp;


    mapPilots = wifi_params.mapping.map_pilots(:,1);
    mapPilots = repmat(mapPilots,1,new_N_sym);
    x(mapPilots) = modulatedPilotSymbols;
    
    mapDC = wifi_params.mapping.map_DC(:,1);
    mapDC = repmat(mapDC,1,new_N_sym);
    x(mapDC) = zeros(numel(wifi_params.mapping.i_DC)*new_N_sym,1);
    % [mod_data(1:7,:); mod_pilots(1,:); mod_data(8:21,:); mod_pilots(2,:); mod_data(22:27,:); zeros(1,n_sf); mod_data(28:33,:); mod_pilots(3,:); mod_data(34:47,:); mod_pilots(4,:); mod_data(48:54,:)];

    x_dc_padded = [x; zeros(n_fft-n_tot-numel(wifi_params.mapping.i_DC),new_N_sym)];
    x_shifted = circshift(x_dc_padded,ceil((n_fft-n_tot-numel(wifi_params.mapping.i_DC))/2));

    % image(abs(real(y_shifted))*20)
    x_ifft = (n_fft/sqrt(n_tot))*ifft(x_shifted,n_fft);

    cyclic_pref = x_ifft(wifi_params.cprefix.indices,:);

    x_cp_ifft = [cyclic_pref; x_ifft];
    x_s = reshape(x_cp_ifft,1,[]);
    % x_s = [x_s, zeros(1,wifi_params.coexistence.N_wifi_all_symbols-length(x_s))];
    
    txOutput(i_amountOfAntennas,:) = x_s;
    
    

end

txObj.output.x_s = txOutput;
txObj.output.myOutput = txOutput;
% save outputs
txObj.output.PSDU = PSDU_tx; % user traffic data without overhead
txObj.output.PSDU_plus_service = PSDU_tx_plus_service; % user traffic data + SERVICE zeros field but without overhead

txObj.output.coded_data = encoderIn_storage; %NOW MATRIX HAVE TO CHANGE THE REST OF PROGRAM % encoder input - user traffic data + SERVICE zeros field with overhead
txObj.output.uncoded_data = interleaverOut; % encoder output (raw data)
txObj.output.scramblerOut = scramblerOut; 
txObj.output.newNsym = new_N_sym; 

% save('tx_test.mat','PSDU_tx','PSDU_tx_plus_service','encoderIn','encoderOut');


end