classdef tgaxChannelManager < handle
%tgaxChannelManager Create a TGaxchannel manager object
% CM = tgaxChannelManager(CHAN,NUMNODES) returns a channel manager object
% for the specified channel configuration object CHAN and number of nodes in
% the simulation NUMNODES. This assumes all nodes can transmit and receive
% and channels between the nodes are reciprocal.
%
% CHAN is a wlanTGaxChannel, wlanTGacChannel or wlanTGnChannel object. The
% ChannelFiltering property must be set to false and the NumSamples
% property must be set. The channel configuration is assumed to be the same
% between all nodes.
%
% CM = tgaxChannelManager(CHAN,LINKMASK) returns a channel manager object
% to create channels for downlink node links described by LINKMASK.
%
% LINKMASK is a NumTx-by-NumRx logical array were an element is true if a
% link between a transmitter and receiver is to be modeled and false
% otherwise. NumTx is the number of transmitters and NumRx is the number of
% receivers. This assumes that the transmitters and receivers are separate
% nodes and there are no reciprocal channels.
%
%   tgaxChannelManager methods:
%
%   getChannelMatrix - returns the channel matrix between a pair of nodes.
%
% Example 1: Generate channel for a network with 5 nodes
%
% % Create channel manager for 5 nodes
% cfgChan = wlanTGaxChannel('ChannelFiltering',false);
% cm = tgaxChannelManager(cfgChan,5);
% 
% % Configure transmission
% cfgHE = wlanHESUConfig('ChannelBandwidth','CBW80');
% 
% % Get channel between pair of nodes in each direction, for preamble and
% % data fields
% txIdx = 2;
% rxIdx = 4;
% H1 = getChannelMatrix(cm,cfgHE,txIdx,rxIdx,'preamble');
% txIdx = 4;
% rxIdx = 2;
% H2 = getChannelMatrix(cm,cfgHE,txIdx,rxIdx,'data');
% 
% % Plot channel magnitude
% dataInfo = wlanHEOFDMInfo('HE-Data',cfgHE);
% preambleInfo = wlanHEOFDMInfo('HE-SIG-A',cfgHE);
% figure;
% plot(4*preambleInfo.ActiveFrequencyIndices,abs(H1),'rs');
% hold on; 
% plot(dataInfo.ActiveFrequencyIndices,abs(H2),'b.');
% legend('Preamble','Data')
%
% Example 2: Generate channel for a network with 3 APs, each with 1 STA.
% Two APs share the same channel.
%
% % Create channel manager for 5 nodes
% cfgChan = wlanTGaxChannel('ChannelFiltering',false);
% linkMask = eye(3,3); % Assume all APs have a link with a STA.
% linkMask(1,3) = true; % Link between 1st AP and 3rd STA as same channel
% linkMask(3,1) = true; % Link between 3rd AP and 1st STA as same channel
% cm = tgaxChannelManager(cfgChan,linkMask);

%   Copyright 2019 The MathWorks, Inc.

%#codegen

properties (Access=private)
    pathGains;   % Path gains for generated channels
    tOffset;     % Time offsets for generated channels
    pathFilters; % Path filters for all channels
    
    sub2chanIndFn; % Function handle to lookup channel index with tx and rx index
    chanIndLUT;  % Array used to map channel index
end

methods
    function obj = tgaxChannelManager(chan,varargin)
        narginchk(2,2)

        if chan.ChannelFiltering==true
            error('Channel filtering must be false')
        end

        if isscalar(varargin{1})
            numNodes = varargin{1};
            % Generate channels between all transmitters and receivers.
            % * Assume a transmitter and receiver are the same node,
            %   therefore do not generate a channel between a node and
            %   itself.
            % * Assume the channel is reciprocal between a transmitter
            %   and receiver therefore only generate channels in one
            %   direction.
            linkMask = triu(~eye(numNodes,numNodes));
            % Assume all nodes can transmit and receive
            obj.sub2chanIndFn = @sub2chanIndRecip;
        else
            linkMask = logical(varargin{1}); % Force to logical
            % Assume transmitting nodes are different to receiving nodes
            obj.sub2chanIndFn = @sub2chanIndNonRecip;
        end

        reset(chan);
        chanInfo = info(chan);
        obj.pathFilters = chanInfo.ChannelFilterCoefficients;

        % Allocate arrays to store channels generated
        numChannels = sum(linkMask,'all');
        obj.pathGains = coder.nullcopy(complex(zeros(chan.NumSamples,numel(chanInfo.AveragePathGains),chan.NumTransmitAntennas,chan.NumReceiveAntennas,numChannels)));
        obj.tOffset = coder.nullcopy(zeros(1,numChannels));

        % Create a mapping function to map requested channel between a pair
        % of nodes (tx,rx) to a channel
        obj.chanIndLUT = zeros(size(linkMask,1),size(linkMask,2));
        obj.chanIndLUT(linkMask) = 1:numChannels;

        % Generate channels
        for ichan = 1:numChannels
            % Generate new channel response
            pathGains = generateChannel(chan);

            % Timing offset
            tOffset = channelDelay(pathGains,obj.pathFilters);

            % Store channel info
            obj.pathGains(:,:,:,:,ichan) = pathGains;                    
            obj.tOffset(ichan) = tOffset;
        end      
    end

    function H = getChannelMatrix(obj,cfg,varargin)
        % H = getChannelMatrix(CM,CFGSU,TXIDX,RXIDX) returns a channel
        % matrix H between a pair of nodes with transmitter index TXIDX and
        % receiver index RXIDX.
        %
        % H is a Nst-by-Ntx-by-Nrx array containing the channel
        % coefficients where Nst is the number of active subcarriers, Ntx
        % is the number of transmit antennas and Nrx is the number of
        % receive antennas.
        %
        % CFGSU is a wlanHESUConfig, wlanVHTConfig, wlanHTConfig or
        % wlanNonHTConfig object which specifies the transmission
        % parameters.
        %
        % H = getChannelMatrix(CM,CFGMU,RUNUMBER,RUIDX,TXIDX) returns a
        % channel matrix H for an OFDMA configuration CFGMU and resource
        % unit (RU) of interest RUNUMBER.
        %
        % CFGMU is a wlanHEMUConfig object which specifies the OFDMA
        % transmission parameters.
        %
        % H = getChannelMatrix(...,FIELD) returns the channel matrix for
        % the given field, either 'preamble' or 'data'. If not provided the
        % data field is assumed.
        %
        % Once a channel realization is created using the above method,
        % subsequent calls to the method with the same TXIDX, RXIDX pair
        % will return the same channel matrix. A new channel manager object
        % must be recreated to obtain a new realization.

        narginchk(4,6)

        % Assume data field if not specified
        field = 'data';
        if nargin>4
            if isa(cfg,'wlanHEMUConfig')
                ruIdx = varargin{1};
                txIdx = varargin{2};
                rxIdx = varargin{3};
                if nargin>5
                    field = varargin{4};
                end
            else
                txIdx = varargin{1};
                rxIdx = varargin{2};
                field = varargin{3};
            end
        else
            txIdx = varargin{1};
            rxIdx = varargin{2};
        end

        % Extract channel information
        idx = obj.sub2chanInd(txIdx,rxIdx);
        % Check that the channel has been created, if not it will be NaNs
        % A channel does not exist between a node and itself
        if idx==0
            error('Channel does not exist between node #%d and #%d.',txIdx,rxIdx)
        end      

        pathGainsUse = obj.pathGains(:,:,:,:,idx);
        tOffsetUse = obj.tOffset(idx);
        pathFiltersUse = obj.pathFilters;

        % Perfect channel estimate
        if isa(cfg,'wlanHEMUConfig')
            ofdmInfo = getOFDMInfo(cfg,field,ruIdx);
        else
            ofdmInfo = getOFDMInfo(cfg,field);
        end
        Htmp = helperPerfectChannelEstimate(pathGainsUse,pathFiltersUse,ofdmInfo.FFTLength,ofdmInfo.CPLength,ofdmInfo.ActiveFFTIndices,tOffsetUse);
        % Average over symbols to Nst-by-Nt-by-Nr
        H = permute(mean(Htmp,2),[1 3 4 5 2]);

    end
end

methods (Access=private)
    function idx = sub2chanInd(obj,txIdx,rxIdx)
        % Returns the channel index given the transmit and receive node
        % indices
        [txIdxUse,rxIdxUse] = obj.sub2chanIndFn(txIdx,rxIdx);
        idx = obj.chanIndLUT(txIdxUse,rxIdxUse);
    end
end
    
end

function [txIdxUse,rxIdxUse] = sub2chanIndRecip(txIdx,rxIdx)
    % Channel is reciprocal and channel has been generated for rxIdx being
    % always greater than txIdx.
    if txIdx>rxIdx
        rxIdxUse = txIdx;
        txIdxUse = rxIdx;
    else
        rxIdxUse = rxIdx;
        txIdxUse = txIdx;
    end
end

function [txIdx,rxIdx] = sub2chanIndNonRecip(txIdx,rxIdx)
    % Map directly

end

function pathGains = generateChannel(chan)
    % Reset channel for a new realization
    reset(chan);

    % Get path gains
    pathGains = chan();
end

function ofdmInfo = getOFDMInfo(cfg,field,varargin)

    % Get OFDM info for preamble and data portion
    switch field
        case 'data'
            switch class(cfg)
                case 'wlanHEMUConfig'
                    ofdmInfo = wlanHEOFDMInfo('HE-Data',cfg,varargin{:});
                case 'wlanHESUConfig'
                    ofdmInfo = wlanHEOFDMInfo('HE-Data',cfg);
                case 'wlanVHTConfig'
                    ofdmInfo = wlanVHTOFDMInfo('VHT-Data',cfg);
                case 'wlanHTConfig'
                    ofdmInfo = wlanHTOFDMInfo('HT-Data',cfg);
                case 'wlanNonHTConfig'
                    ofdmInfo = wlanNonHTOFDMInfo('NonHT-Data');
                otherwise
                    error('Unexpected format');
            end
        case 'preamble'
            switch class(cfg)
                case 'wlanHEMUConfig'
                    ofdmInfo = wlanHEOFDMInfo('HE-SIG-A',cfg,varargin{:});
                case 'wlanHESUConfig'
                    ofdmInfo = wlanHEOFDMInfo('HE-SIG-A',cfg);
                case 'wlanVHTConfig'
                    ofdmInfo = wlanVHTOFDMInfo('VHT-SIG-A',cfg);
                case 'wlanHTConfig'
                    ofdmInfo = wlanHTOFDMInfo('HT-SIG',cfg);
                case 'wlanNonHTConfig'
                    ofdmInfo = wlanNonHTOFDMInfo('NonHT-Data');
                otherwise
                    error('Unexpected format');
            end
        otherwise
            error('unexpected field')
    end

end