% umtsUplinkWaveformGenerator UMTS Uplink waveform generation
%   WAVEFORM = umtsUplinkWaveformGenerator(DEF) returns the UMTS uplink
%   waveform WAVEFORM defined by the configuration structure DEF. This
%   generator supports W-CDMA, HSUPA and HSPA+. The output WAVEFORM will be
%   sampled at 3.84*OversamplingRatio MHz.
%
%   DEF must be a structure characterizing the waveform to generate. This
%   structure can be generated easily by using the 
%   <a href="matlab:
%   help('umtsUplinkReferenceChannels')">umtsUplinkReferenceChannels</a> function. This structure includes fields to
%   describe the top level parameters and substructures to describe the
%   different channels to include in the waveform.
%   
%   The top level parameters of DEF are:
%   TotFrames         - Total number of frames to be generated
%   ScramblingCode    - UE scrambling code
%   FilterType        - Filter type ('RRC','Off')
%   OversamplingRatio - The oversampling ratio
%   NormalizedPower   - Overall waveform power in dBW relative to 1 ohm.
%                       Set to 'Off' to disable power normalization
%
%   Substructures can be added to enable the different channels: 
%   DPDCH             - DPDCH configuration structure
%   DPCCH             - DPCCH configuration structure
%   HSUPA             - HSUPA configuration structure
%   HSDPCCH           - HS-DPCCH configuration structure
% 
%   Example:
%   The example shows the generation of FRC1.
%
%   config = umtsUplinkReferenceChannels('FRC1');
%   waveform = umtsUplinkWaveformGenerator(config);
%
%   See also umtsUplinkReferenceChannels, umtsDownlinkWaveformGenerator.

%    Copyright 2014-2017 The MathWorks, Inc.

function out = umtsUplinkWaveformGenerator(config)

    %% Setup
    powerGainAdj = calcGainAdj(config);
    validateUMTSParameter('TotFrames',config);
    validateUMTSParameter('ScramblingCode',config);
    out = zeros(38400*config.TotFrames,1);
    Nmaxdpdch = 0;                             % Initialize Nmaxdpdch to 0
    % Define function for checking if channel is enabled
    isChannelEnabled = @(x)isfield(config,x) && ~strcmpi(config.(x).Enable,'off');

    %% DCH (DTCH & DCCH) Transport Channel Processing
    if isChannelEnabled('DPDCH') 
        Nmaxdpdch = length(config.DPDCH.CodeCombination);
        dims = FddULDPDCHDims(config.DPDCH.SlotFormat);
        if strcmpi(config.DPDCH.DataSource,'CCTrCH')
            % Generate the DCH channel coded data
            transportblocks = FddCCTrCHData(config.DPDCH.CCTrCH,config.TotFrames);
            dchframes = FddULCCTrCH(config.DPDCH.CCTrCH,dims.NDataPerFrame*Nmaxdpdch,transportblocks{:});           
            dchframes = dchframes(1:dims.NDataPerFrame*config.TotFrames*Nmaxdpdch); 
        else
            phySource = vectorDataSource(config.DPDCH.DataSource);
            dchframes = phySource.getPacket(dims.NDataPerFrame*config.TotFrames*Nmaxdpdch);
        end
    end

    %% Physical Channel Processing
    % DPDCH & DPCCH
    if isChannelEnabled('DPDCH')  || isChannelEnabled('DPCCH') 
        dpchConfig.ScramblingCode = config.ScramblingCode;
        if (exist('dchframes','var')~=1)
            % if DPDCH is Off (Nmaxdpdch is 0) or absent, define dchframes
            % based on a default configuration with power set to -Inf so that
            % the DPDCH is not transmitted
            if ~isChannelEnabled('DPDCH')
                config.DPDCH.SlotFormat = 0;
                config.DPDCH.CodeCombination = 256;
                config.DPDCH.Power = -Inf; % To disable the channel

            end
            dims = FddULDPDCHDims(config.DPDCH.SlotFormat);
            dchframes = zeros(dims.NDataPerFrame*config.TotFrames,1);
        end

        dpchConfig.CodeCombination = config.DPDCH.CodeCombination;
        dpchConfig.DPDCHPower = config.DPDCH.Power;
        % Check if DPDCH Slot Format and Code combination are valid
        validateUMTSParameter('CodeCombination',dpchConfig.CodeCombination);
        if length(dpchConfig.CodeCombination)==1 && config.DPDCH.SlotFormat ~= (8-log2(dpchConfig.CodeCombination))
           error('umts:error','For CodeCombination = %d, the DPDCH SlotFormat must be %d',dpchConfig.CodeCombination,(8-log2(dpchConfig.CodeCombination))); 
        end
        if isChannelEnabled('DPCCH')  
            dpchConfig.SlotFormat = config.DPCCH.SlotFormat;
            dpchConfig.DPCCHPower = config.DPCCH.Power;
            tpcSource = vectorDataSource(config.DPCCH.TPCData);
            fbiSource = vectorDataSource(config.DPCCH.FBIData);
            out = out + FddULDPCH(dpchConfig,dchframes,config.DPCCH.TFCI,tpcSource.getPacket(config.TotFrames),fbiSource.getPacket(config.TotFrames));
        else
            out = out + FddULDPCH(dpchConfig,dchframes);
        end
    end

    % HS-DPCCH
    if isChannelEnabled('HSDPCCH') 
        hsdpcchConfig = config.HSDPCCH;
        hsdpcchConfig.ScramblingCode = config.ScramblingCode;
        hsdpcchConfig.ScramblingOffset = 0;
        hsdpcchConfig.UEMIMO = config.HSDPCCH.UEMIMO;
        hsdpcchConfig.Nmaxdpdch = Nmaxdpdch;
        out = out + FddHSDPCCH(hsdpcchConfig,config.TotFrames)* db2mag(config.HSDPCCH.Power);
    end

    %% HSUPA
    if isChannelEnabled('HSUPA') 
        config.Nmaxdpdch = Nmaxdpdch;
        config.HSUPA.HSDSCHConfigured = 1;
        out = out + FddHSUPA(config);
    end

    %%  Gain Adjustment and Filtering
    out = out*powerGainAdj;
    validateUMTSParameter('FilterType',config);
    validateUMTSParameter('OversamplingRatio',config);
    out = FddRRCFiltering(out,32,config.OversamplingRatio,config.FilterType);

end


%% Local functions
% This is the function to calculate the gain adjust value if
% 'NormalizedPower' is not 'Off'
function powerGainAdj = calcGainAdj(config)
    % get the total power from all active physical channels
    p = 0;
    p = p+getChannelPower(config,'DPDCH');
    p = p+getChannelPower(config,'DPCCH');
    p = p+getChannelPower(config,'HSUPA');
    p = p+getChannelPower(config,'HSDPCCH');
    if ischar(config.NormalizedPower) || isstring(config.NormalizedPower)
        if strcmpi(config.NormalizedPower,'Off')
            powerGainAdj = 1;
            return;
        else
            error('umts:error','Invalid value (%s) specified for NormalizedPower',config.NormalizedPower);
        end
    end
    % Get the overall gain in dB
    validateUMTSParameter('NormalizedPower',config);
    overallgain = config.NormalizedPower;

    % If total channel power is not overall power specified, we need
    % gain adjustment
    powerScaledB = overallgain-pow2db(p);
    powerGainAdj = db2mag(powerScaledB);
end

% Function to calculate the power of each phy channel
function pow = getChannelPower(config,channel)
    pow = 0;
    if isfield(config,channel) 
        for c = 1:numel(config.(channel))
            currentch = config.(channel)(c);
            validateUMTSParameter('Enable',currentch); 
            if ~strcmpi(currentch.Enable,'off')
                if strcmpi(channel,'HSUPA')
                    % The HSUPA channel set consists of E-DPDCH and E-DPCCH
                    % channels
                    validateUMTSParameter('EDPDCHPower',currentch); 
                    validateUMTSParameter('EDPCCHPower',currentch); 
                    pow = pow + db2pow(currentch.EDPDCHPower)*length(currentch.CodeCombination) + db2pow(currentch.EDPCCHPower);
                elseif strcmpi(channel,'DPDCH')
                    validateUMTSParameter('Power',currentch); 
                    pow = pow + db2pow(currentch.Power)*length(currentch.CodeCombination);
                else
                    validateUMTSParameter('Power',currentch); 
                    pow = pow + db2pow(currentch.Power);
                end
            end

        end
    end
end
