classdef rateAdaptationMinstrelNonHT < handle
%rateAdaptationMinstrelNonHT Minstrel algorithm
%   CFG = rateAdaptationMinstrelNonHT(NUMSTATIONS) creates a minstrel
%   object. This object provides functionality for minstrel algorithm.
%
%   NUMSTATIONS must be a scalar double value specifying the number of
%   stations in the network.
%
%   CFG = rateAdaptationMinstrelNonHT(...,Name,Value) creates a minstrel
%   object, CFG, 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).
%
%   rateAdaptationMinstrelNonHT properties:
%   LongRetryLimit          - Retry limit for frames with size greater than
%                             RTS threshold
%   ShortRetryLimit         - Retry limit for frames with size less than 
%                             RTS threshold
%   ProbeRate               - Rate of probing new data rates
%   RecentIntervalWeight    - Weight for the moving average
%
%   rateAdaptationMinstrelNonHT methods:
%   getRate             - Return a data rate for frame transmission
%   updateStatus        - Update status of frame transmission
%   updateRateTable     - Update the minstrel rate table
%   minstrelStatistics  - Output the minstrel statistics

%   Copyright 2020 The MathWorks, Inc.

%#codegen

    properties
        %LongRetryLimit Limit for frames with size greater than RTS
        %threshold
        %   Specify the property as a scalar integer.
        LongRetryLimit(1,1) {mustBeNumeric, mustBeInteger} = 7;
        
        %ShortRetryLimit Limit for frames with size less than RTS threshold
        %   Specify the property as a scalar integer.
        ShortRetryLimit(1,1) {mustBeNumeric, mustBeInteger} = 7;
        
        %ProbeRate Rate of probing new data rates
        %   Specify the property as a value between 0 and 1 inclusive. The
        %   default is 0.1.
        ProbeRate(1,1) {mustBeNumeric, mustBeGreaterThanOrEqual(ProbeRate,0), ...
            mustBeLessThanOrEqual(ProbeRate,1)} = 0.1;
        
        %RecentIntervalWeight Weight for the moving average
        %   Specify the property as a value between 0 and 1 inclusive. The
        %   default is 0.75.
        RecentIntervalWeight(1,1) {mustBeNumeric, mustBeGreaterThanOrEqual(RecentIntervalWeight,0), ...
            mustBeLessThanOrEqual(RecentIntervalWeight,1)} = 0.75;
    end
    
    properties (Access = private)
       %NumStations Number of stations in the network
       NumStations = 10;
       
       % N x 1 array holding the transmission times for a 1400 byte data
       % frame. N is the number of rates supported.
       PerfectTxTime;
       
       % N x 1 array holding the transmission times for an acknowledgment
       % frame. N is the number of rates supported.
       AckTxTime;
       
       % N x 1 array where each element holds an index in the minstrel rate
       % chain that represents the current data rate in use for data
       % transmissions. N is the number of stations.
       CurrentRateChainIdx;
       
       % N x 1 array holding the flags for indicating if the current frame
       % is a retransmission. N is the number of stations.
       Retransmit;
       
       % N x 1 array holding the flags for indicating if the current frame
       % is a retransmission for RTS frame. N is the number of stations.
       RTSRetransmit;
       
       % N x 1 array holding the flags for indicating if the previous frame
       % was a probe frame. N is the number of stations.
       PrevProbe;
       
       % N x 1 array holding the flags for indicating if the previous frame
       % actually used the probe rate from the rate chain. N is the number
       % of stations.
       ProbeRateUsed;
       
       % N x 1 array where each element holds the current index in the
       % static random probe rate set. N is the number of stations.
       CurrProbeRateIdx;
       
       % N x 1 array holding the counts indicating the number of frames
       % that have used the probe rate in the current interval. N is the
       % number of stations.
       NumProbesCompleted;
       
       % N x 1 array holding the counts indicating the number of frames
       % that have probe rates in the rate chain, but have not used the
       % probe rate for transmission. N is the number of stations.
       NumProbesDeferred;
       
       % N x M array holding the success probability for each rate and each
       % station. N is the number of stations and M is the number of rates.
       SuccessProbability;
       
       % N x M array holding the throughput estimate values for each rate
       % and each station. N is the number of stations and M is the number
       % of rates.
       ThroughputEstimate;
       
       % N x M array holding the success probability for the current
       % interval for each rate and each station. N is the number of
       % stations and M is the number of rates.
       ThisIntervalProbability;
    end
    
    properties (Access = private)
        % N x M array holding the number of transmission attempts in the
        % current interval. N is the number of stations and M is the number
        % of rates supported.
        NumAttempts;
        
        % N x M array holding the total number of transmissions made by the
        % transmitter. N is the number of stations and M is the number
        % of rates supported.
        TotalNumTx;
        
        % N x M array holding the number of successful transmissions in the
        % current interval. N is the number of stations and M is the number
        % of rates supported.
        NumSuccess;
        
        % N x M array holding the total number of successful transmissions
        % made by the transmitter. N is the number of stations and M is the
        % number of rates supported.
        TotalSuccess;
        
        % N x M array holding the maximum retries that can be made for each
        % rate for a given time segment size. N is the number of stations
        % and M is the number of rates supported.
        RetryCount;
        
        % N x M array holding the maximum retries that can be made for a
        % frame with RTS/CTS exchange, for each rate for a given time
        % segment size. N is the number of stations and M is the number of
        % rates supported.
        RetryCountRTSCTS;
        
        % N x 4 array holding the 4 minstrel rates for each receiving
        % stations. N is the number of stations and 4 is the number of
        % minstrel rates.
        RateChain;
        
        % N x 4 array holding the remaining retries for each minstrel rate.
        % N is the number of stations and 4 is the number of minstrel
        % rates. The retries are divided among the minstrel rates, not
        % exceeding the MAC retry limit.
        RemainingRetryCount;
        
        % N x 1 array holding the remaining retries for RTS frame. N is the
        % number of stations.
        RemainingRetryCountRTS;
        
        % N x 1 array holding flags for each station. Each flag indicates
        % if the frame sequence has been completed (discarded or
        % succeeded), and a new rate chain needs to be constructed.
        FrameSequenceCompleted;
        
        % N x 4 array of flags indicating which rates in the RateChain are
        % probe frames. N is the number of stations and 4 is the number of
        % minstrel rates.
        IsProbeRate;
    end
    
    properties (Constant, Hidden)
        % Number of rates
        NumRates = 8;
        
        % Contention window minimum value
        CWmin = 7;
        
        % Contention window maximum value
        CWmax = 1023;
        
        % Slot time
        SlotTime = 9; % microseconds
        
        % Number of minstrel rates in a rate chain
        NumMinstrelRates = 4;
        
        % Pseudo random rate set for probing purpose. MCS indices for the
        % rates [18, 9, 36, 12, 54, 48, 24]. 6 Mbps rate is not explicitly
        % probed since it is always included in the minstrel rate chain.
        RandomProbeRateSet = [3, 1, 5, 2, 7, 6, 4];
        
        %SegmentSize Time limit for calculating the maximum retries for
        %each rate
        SegmentSize = 6*10^3; % microseconds
    end
    
    methods
        function obj = rateAdaptationMinstrelNonHT(numStations, varargin)
        % Constructor method
        
            numNVpair = numel(varargin);
            coder.internal.errorIf((mod(numNVpair, 2)~=0), 'wlan:ConfigBase:InvalidPVPairs');
            validateattributes(numStations, {'numeric'}, {'scalar', 'positive', 'integer'}, mfilename, '');
            propertySet = {'LongRetryLimit', 'ShortRetryLimit', 'ProbeRate', 'RecentIntervalWeight'};
            for i = 1:2:numNVpair
                validatestring(varargin{i}, propertySet, mfilename);
                obj.(char(varargin{i})) = varargin{i+1};
            end
            obj.NumStations = numStations;
            
            % Per rate
            obj.PerfectTxTime = zeros(obj.NumRates, 1);
            obj.AckTxTime = zeros(obj.NumRates, 1);
            
            % Per station
            obj.CurrentRateChainIdx = ones(obj.NumStations, 1);
            obj.Retransmit = false(obj.NumStations, 1);
            obj.RTSRetransmit = false(obj.NumStations, 1);
            obj.PrevProbe = false(obj.NumStations, 1);
            obj.ProbeRateUsed = false(obj.NumStations, 1);
            obj.CurrProbeRateIdx = ones(obj.NumStations, 1);
            obj.NumProbesCompleted = zeros(obj.NumStations, 1);
            obj.NumProbesDeferred = zeros(obj.NumStations, 1);
            obj.RemainingRetryCountRTS = obj.ShortRetryLimit*ones(obj.NumStations, 1);
            obj.FrameSequenceCompleted = true(obj.NumStations, 1);
            
            % Per rate + Per station
            obj.NumAttempts = zeros(obj.NumStations, obj.NumRates);
            obj.NumSuccess = zeros(obj.NumStations, obj.NumRates);
            obj.TotalSuccess = zeros(obj.NumStations, obj.NumRates);
            obj.TotalNumTx = zeros(obj.NumStations, obj.NumRates);
            obj.SuccessProbability = 1*ones(obj.NumStations, obj.NumRates);
            obj.ThisIntervalProbability = -1*ones(obj.NumStations, obj.NumRates);
            obj.ThroughputEstimate = zeros(obj.NumStations, obj.NumRates);
            obj.RetryCount = zeros(obj.NumStations, obj.NumRates);
            obj.RetryCountRTSCTS = zeros(obj.NumStations, obj.NumRates);
            
            % Stores 4 rates for each station
            obj.RateChain = zeros(obj.NumStations, obj.NumMinstrelRates);
            % Stores 4 retry counts for each station
            obj.RemainingRetryCount = zeros(obj.NumStations, obj.NumMinstrelRates);
            
            % Array of flags indicating which rates are probe rates in the
            % RateChain
            obj.IsProbeRate = false(obj.NumStations, obj.NumMinstrelRates);
        end
    end
    
    methods (Access = private)
        function tR = throughputEstimate(obj, rxStationIdx, rateIdx)
        %throughputEstimate Calculate the throughput estimate for given
        %rate
        
            avgMSDUSize = 1400; % average size selected for estimation purpose
            headerPlusFCSSize = 30;
            addSIFS = false;
            dR = obj.calculateTxTime(rateIdx, (avgMSDUSize+headerPlusFCSSize), addSIFS);
            
            % Throughput estimation for the rate
            pR = obj.SuccessProbability(rxStationIdx, rateIdx);
            tR = (pR/dR)*avgMSDUSize;
        end
        
        function txTime = calculateTxTime(~, rateIdx, psduLength, addSIFS)
        %calculateTxTime Calculate the transmission time for given
        %parameters
        
            sifs = 0;
            if addSIFS
                sifs = 16;
            end
            mcs = rateIdx - 1;
            persistent n
            if isempty(n)
                n = wlanNonHTConfig('MCS', mcs, 'PSDULength', psduLength);
            else
                n.MCS = mcs;
                n.PSDULength = psduLength;
            end
            s = n.validateConfig;
            
            txTime = sifs + s.TxTime;
        end
        
        function rateChain = checkIfProbeFrame(obj, rxStationIdx, rateChain)
        %checkIfProbeFrame Check if probe frame should be used and update
        %the rate chain
        
            nT = sum(obj.TotalNumTx(rxStationIdx, :));
            % If the total transmissions exceed 10000, reset the number of
            % probes completed and number of probes deferred to zero
            if nT > 10000
                obj.NumProbesCompleted(rxStationIdx) = 0;
                obj.NumProbesDeferred(rxStationIdx) = 0;
            end
            rP = obj.ProbeRate; 
            nP = obj.NumProbesCompleted(rxStationIdx);
            nD = obj.NumProbesDeferred(rxStationIdx);
            % If the rate of probing falls behind the configured rate, try
            % a new rate from the random probe rate list. Do not try probe
            % rates for two consecutively frames
            numProbesToDo = ceil(nT*rP - nP + (nD/2));
            isProbeFrame = (numProbesToDo > 0) && ~obj.PrevProbe(rxStationIdx);
            
            % Reset the flag for the new rate chain
            obj.PrevProbe(rxStationIdx) = false;
            obj.IsProbeRate(rxStationIdx, :) = false(1, obj.NumMinstrelRates);
            
            if isProbeFrame
                % Pick a probe rate from the random rate set and put it in
                % the minstrel rate chain
                probeRateIdx = obj.CurrProbeRateIdx(rxStationIdx);
                
                % Pick a probe rate that isn't already in the rate chain.
                % Go through the random probe rate set to find a probe
                % rate
                while true
                    probeRate = obj.RandomProbeRateSet(probeRateIdx);
                    
                    % If this probe rate isn't present in the current rate
                    % chain
                    if ~any(probeRate == rateChain(:))
                        % Put the probe rate as the first rate in the chain
                        % only if it is greater than the rate with the
                        % maximum throughput
                        maxTP = rateChain(1);
                        if probeRate > maxTP
                            rateChain(1) = probeRate;
                            rateChain(2) = maxTP;
                            obj.IsProbeRate(rxStationIdx, 1) = true;
                        else
                            rateChain(2) = probeRate;
                            obj.IsProbeRate(rxStationIdx, 2) = true;
                        end
                        
                        obj.PrevProbe(rxStationIdx) = true;
                        break;
                        
                    else
                        % If the rate is already present in the rate chain, try
                        % the next rate in the random probe rate set
                        probeRateIdx = probeRateIdx + 1;
                        if probeRateIdx > numel(obj.RandomProbeRateSet)
                            % Wrap around the random probe set
                            probeRateIdx = 1;
                        end
                    end
                end
                
                % Probe rates are chosen in a round-robin fashion.
                % Increment the probe rate index for the next probe frame.
                if probeRateIdx < numel(obj.RandomProbeRateSet)
                    obj.CurrProbeRateIdx(rxStationIdx) = probeRateIdx + 1;
                else
                    obj.CurrProbeRateIdx(rxStationIdx) = 1;
                end
                
                % Limit the backlog of probe frames to less than twice the
                % number of rates in use, by artificially increasing the
                % number of probes completed
                if numProbesToDo > 2*(obj.NumRates)
                    obj.NumProbesCompleted(rxStationIdx) = obj.NumProbesCompleted(rxStationIdx) + (numProbesToDo - 2*(obj.NumRates));
                end
            end
        end
        
        function retryCount = getRetryCounts(obj, rxStationIdx, rates, maxMACRetryLimit, minstrelRetryLimit)
        %getRetryCounts Calculate the retry counts for minstrel rates
        %adhering to both minstrel retry limits and MAC retry limits
        
            % Corresponding retry counts
            sumRetries = 0;
            retryCount = zeros(1, obj.NumMinstrelRates);
            
            for idx = 1:obj.NumMinstrelRates
                minstrelLimit = minstrelRetryLimit(rxStationIdx, rates(idx));
                
                if (sumRetries + minstrelLimit) <= maxMACRetryLimit
                    % If the total retries do not exceed the maximum
                    % limit, use the maximum retries that can be
                    % afforded for this rate
                    retryCount(idx) = minstrelLimit;
                else
                    % Adjust the retry count for this rate to limit
                    % total retries to the maximum limit
                    retryCount(idx) = maxMACRetryLimit - sumRetries;
                end
                
                sumRetries = sumRetries + retryCount(idx);
                % Last minstrel rate: If the total retries is less than
                % the maximum retries specified by the configuration,
                % include the remaining retries for the last minstrel
                % rate
                if (idx == obj.NumMinstrelRates) && (sumRetries < maxMACRetryLimit)
                    retryCount(idx) = retryCount(idx) + (maxMACRetryLimit - sumRetries);
                end
            end
        end
        
        function discard = handleRTSTxStatus(obj, rxStationIdx, isFail, rtsRate)
            %handleRTSTxStatus Handle the transmission status for RTS frame
            
            discard = false;
            assert((rtsRate >= 0) && (rtsRate <= 7), 'minstrel: Argument-4 (rate index) must be a value in the range [0, 7]');
            rtsRate = rtsRate + 1; % +1 for MATLAB indexing
            
            % Increment the number of transmission attempts in this interval
            obj.NumAttempts(rxStationIdx, rtsRate) = obj.NumAttempts(rxStationIdx, rtsRate) + 1;
            obj.TotalNumTx(rxStationIdx, rtsRate) = obj.TotalNumTx(rxStationIdx, rtsRate) + 1;
            
            % Decrement the remaining retry count when a retry attempt is
            % completed
            if obj.RTSRetransmit(rxStationIdx) && (obj.RemainingRetryCountRTS(rxStationIdx) > 0)
                obj.RemainingRetryCountRTS(rxStationIdx) = obj.RemainingRetryCountRTS(rxStationIdx) - 1;
            end
            
            if isFail               
                if obj.RemainingRetryCountRTS(rxStationIdx) == 0
                    % Retries have exhausted for all the rates in the rate
                    % chain. Indicate the caller to discard this frame
                    
                    % Reset retransmit flag
                    obj.RTSRetransmit(rxStationIdx) = false;
                    obj.FrameSequenceCompleted(rxStationIdx) = true;
                    discard = true;
                    
                    % If the frame rate chain contained a probe rate
                    if obj.PrevProbe(rxStationIdx)
                        % Probe rate has not been tried
                        obj.NumProbesDeferred(rxStationIdx) = obj.NumProbesDeferred(rxStationIdx) + 1;
                    end
                else
                    % Retries are left for at least one rate in the rate
                    % chain
                    
                    obj.RTSRetransmit(rxStationIdx) = true;
                end
            else % Success
                
                obj.RTSRetransmit(rxStationIdx) = false;
                obj.NumSuccess(rxStationIdx, rtsRate) = obj.NumSuccess(rxStationIdx, rtsRate) + 1;
                obj.TotalSuccess(rxStationIdx, rtsRate) = obj.TotalSuccess(rxStationIdx, rtsRate) + 1;
            end
        end
        
        function discard = handleDataTxStatus(obj, rxStationIdx, isFail)
        %handleDataTxStatus Handle the transmission status for Data frame
            
            discard = false;
            % Rate chain index for the current rate in the rate chain
            rateChainIdx = obj.CurrentRateChainIdx(rxStationIdx);
            % Get the rate index from the rate chain. Adding 1 for MATLAB indexing
            rate = obj.RateChain(rxStationIdx, rateChainIdx) + 1;
            % Increment the number of transmission attempts in this interval
            obj.NumAttempts(rxStationIdx, rate) = obj.NumAttempts(rxStationIdx, rate) + 1;
            obj.TotalNumTx(rxStationIdx, rate) = obj.TotalNumTx(rxStationIdx, rate) + 1;
            
            % Decrement the remaining retry count when a retry attempt is
            % completed
            if obj.Retransmit(rxStationIdx) && (obj.RemainingRetryCount(rxStationIdx, rateChainIdx) > 0)
                obj.RemainingRetryCount(rxStationIdx, rateChainIdx) = obj.RemainingRetryCount(rxStationIdx, rateChainIdx) - 1;
            end
            retriesLeft = obj.RemainingRetryCount(rxStationIdx, :);
            
            if isFail
                if ~any(retriesLeft > 0)
                    % Retries have exhausted for all the rates in the rate
                    % chain. Indicate the caller to discard this frame
                    
                    % Reset retransmit flag
                    obj.Retransmit(rxStationIdx) = false;
                    obj.FrameSequenceCompleted(rxStationIdx) = true;
                    discard = true;
                    
                    % If the rate chain used contained a probe rate
                    if obj.PrevProbe(rxStationIdx)
                        % Probe rate is definitely tried since the entire
                        % rate chain is used
                        obj.NumProbesCompleted(rxStationIdx) = obj.NumProbesCompleted(rxStationIdx) + 1;
                        if obj.NumProbesDeferred(rxStationIdx) > 0
                            obj.NumProbesDeferred(rxStationIdx) = obj.NumProbesDeferred(rxStationIdx) - 1;
                        end
                    end
                else
                    % Retries are left for at least one rate in the rate
                    % chain
                    
                    obj.Retransmit(rxStationIdx) = true;
                end
                
            else % Success
                % Transmission succeeded. No need to retransmit
                obj.Retransmit(rxStationIdx) = false;
                obj.FrameSequenceCompleted(rxStationIdx) = true;
                
                % Update rate stats
                obj.NumSuccess(rxStationIdx, rate) = obj.NumSuccess(rxStationIdx, rate) + 1;
                obj.TotalSuccess(rxStationIdx, rate) = obj.TotalSuccess(rxStationIdx, rate) + 1;
                discard = true;
                
                % Update the probe rate stats
                if obj.PrevProbe(rxStationIdx)
                    % If probe rate is used
                    if obj.ProbeRateUsed(rxStationIdx)
                        obj.NumProbesCompleted(rxStationIdx) = obj.NumProbesCompleted(rxStationIdx) + 1;
                    else % if the probe rate isn't used
                        obj.NumProbesDeferred(rxStationIdx) = obj.NumProbesDeferred(rxStationIdx) + 1;
                    end
                end
            end
        end
        
        function initMinstrel(obj)
        %initMinstrel Initialize the minstrel algorithm
        
            for idx = 1:obj.NumRates
                avgMSDUSize = 1400; % average size selected for estimation purpose
                headerPlusFCSSize = 30;
                ackSize = 14;
                % 1400 byte PSDU transmission time for each rate
                addSIFS = false;
                obj.PerfectTxTime(idx) = obj.calculateTxTime(idx, (avgMSDUSize+headerPlusFCSSize), addSIFS);
                % Ack frame transmission time for each rate
                addSIFS = true;
                obj.AckTxTime(idx) = obj.calculateTxTime(idx, ackSize, addSIFS);
            end
            
            txTimeSingle = zeros(1,obj.NumRates);
            perfectTxTime = obj.PerfectTxTime;
            ACKTxTime =  obj.AckTxTime;
            spACKDuration = obj.AckTxTime(1); % Ack duration with basic rate
            % Initial frame transmission time
            txTime = (perfectTxTime + spACKDuration);
            txTimeRTSCTS = zeros(size(txTime));
            
            retryCount = zeros(1, obj.NumRates);
            retryCountRTSCTS = zeros(1, obj.NumRates);
            maxRetryLimit = max(obj.LongRetryLimit, obj.ShortRetryLimit);
            
            % Calculate the number of attempts that can be made with a
            % specific rate in a given time (segment size)
            for idx = 1:obj.NumRates
                CW = obj.CWmin;
                perRateMaxRetryReached = false;
                
                while ~perRateMaxRetryReached
                    % Add retransmission time
                    txTimeSingle(idx) = perfectTxTime(idx) + ACKTxTime(idx);
                    % Add average contention time
                    txTimeSingle(idx) = txTimeSingle(idx) + (obj.SlotTime * CW)/2;
                    % Double the contention window for next retransmission
                    CW = min((2*CW + 1),obj.CWmax);
                    
                    % Transmission time for a frame with RTS and CTS
                    % exchange
                    txTimeRTSCTS(idx) = txTimeRTSCTS(idx) + txTimeSingle(idx) + 2*spACKDuration;
                    % If the total transmission time exceeds segment size
                    % (6 ms), or if the sum of retries exceed MAC retry
                    % limit, stop incrementing the retry count
                    if ((txTimeRTSCTS(idx) + txTimeSingle(idx)) < obj.SegmentSize) && ...
                                (retryCountRTSCTS(idx) < maxRetryLimit)
                        retryCountRTSCTS(idx) = retryCountRTSCTS(idx) + 1; 
                    end
                    
                    % If the total transmission time exceeds segment size
                    % (6 ms), or if the sum of retries exceed MAC retry
                    % limit, stop incrementing the retry count and break
                    % out of the loop
                    if ((txTime(idx) + txTimeSingle(idx)) > obj.SegmentSize) || ...
                                (retryCount(idx) >= maxRetryLimit)
                        perRateMaxRetryReached = true;
                        continue;
                    end
                    % Update total transmission time for the rate (without RTS/CTS)
                    txTime(idx) = txTime(idx) + txTimeSingle(idx);
                    % Increment the retry count
                    retryCount(idx) = retryCount(idx) + 1;
                end
            end
            
            for rxSTAIdx = 1:obj.NumStations
                obj.RetryCount(rxSTAIdx, :) = retryCount;
                obj.RetryCountRTSCTS(rxSTAIdx, :) = retryCountRTSCTS;
            end
        end
    end
    
    methods (Hidden)
        function rateChain = getRateChain(obj, rxStationIdx)
        %getRateChain Construct and return a rate chain
        
            % Rate with max throughput
            tp = obj.ThroughputEstimate(rxStationIdx, :);
            [~, maxTPIndices] = find(tp == max(tp));
            maxTP = max(maxTPIndices);
            
            % Rate with 2nd highest max throughput
            tp2Array = [tp(1:maxTP-1) -1 tp(maxTP+1:end)];
            [~, maxTPIndices2] = find(tp2Array == max(tp2Array));
            maxTP2 = max(maxTPIndices2);
            
            % Rate with max probability
            pR = obj.SuccessProbability(rxStationIdx, :);
            sortedProb = sortrows([pR; (1:obj.NumRates)]', 1);
            for idx = obj.NumRates:-1:1
                maxP = sortedProb(idx, 2);
                if (maxP ~= maxTP) && (maxP ~= maxTP2)
                    break;
                end
            end
            
            % Adjust for MCS values 0-8 from MATLAB indexing values
            maxTP = maxTP-1;
            maxTP2 = maxTP2-1;
            maxP = maxP-1;
            lowest = 0;
            
            % Construct a rate-chain
            rateChain = [maxTP, maxTP2, maxP, lowest];
            
            % Check if a probe rate needs to be used and update the rate
            % chain if needed
            rateChain = checkIfProbeFrame(obj, rxStationIdx, rateChain);
        end
    end
    
    methods
        
        function updateRateTable(obj)
        %updateRateTable Update the minstrel rate table
        %   updateRateTable(OBJ) updates the minstrel rate table using the
        %   transmission status updates given through the 'updateStatus'
        %   method. This method should be called periodically, preferably
        %   every 100ms, to reflect the latest changes in the performance
        %   of each rate with each station.
            
            w = obj.RecentIntervalWeight;
            
            % Update the success probability and throughput estimates for
            % each rate, for all the stations
            for rxSTAIdx = 1:obj.NumStations
                for rateidx = 1:obj.NumRates
                    pS = obj.SuccessProbability(rxSTAIdx, rateidx);
                    nA = obj.NumAttempts(rxSTAIdx, rateidx);
                    nS = obj.NumSuccess(rxSTAIdx, rateidx);
                    
                    if nA > 0
                        if obj.ThisIntervalProbability(rxSTAIdx, rateidx) < 0
                            % Initial calculation of success probability
                            pS = (nS/nA)*18000; % Scale to interval [0, 18000] for better resolution
                            obj.ThisIntervalProbability(rxSTAIdx, rateidx) = pS;
                        else
                            currProb = (nS/nA)*18000; % Scale to interval [0, 18000] for better resolution
                            pS = (1 - w)*pS + w*currProb;
                            obj.ThisIntervalProbability(rxSTAIdx, rateidx) = currProb;
                        end
                    end
                    
                    % Update success probability
                    obj.SuccessProbability(rxSTAIdx, rateidx) = pS;
                    % Update throughput estimate
                    obj.ThroughputEstimate(rxSTAIdx, rateidx) = obj.throughputEstimate(rxSTAIdx, rateidx);
                    
                    % Reset the counters for next interval
                    obj.NumAttempts(rxSTAIdx, rateidx) = 0;
                    obj.NumSuccess(rxSTAIdx, rateidx) = 0;
                end
            end
            
        end
        
        function dataRateIdx = getRate(obj, rxStationIdx, isShortFrame)
        %getRate Get a data rate index for current frame transmission
        %   DATARATEIDX = getRate(OBJ, RXSTATIONIDX, ISSHORTFRAME) returns
        %   a data rate index, DATARATEIDX, that can be used for the
        %   current frame transmission for the destination station with
        %   index, RXSTATIONIDX. ISSHORTFRAME flag indicates if the frame
        %   being transmitted is less than RTS threshold. Based on the
        %   transmission status updates indicated through the
        %   'updateStatus' method, this method calculates the number of
        %   retransmissions and returns a data rate index from the minstrel
        %   rate chain.
        
            % Very first packet transmission
            if all(obj.TotalNumTx == 0)
                % Initialize the minstrel algorithm
                obj.initMinstrel;
            end
           
            if ~isShortFrame && obj.RTSRetransmit(rxStationIdx)
                % RTS transmission has not succeeded yet. Return same rate
                % index for the data frame.
                rateChainIdx = obj.CurrentRateChainIdx(rxStationIdx);
                dataRateIdx = obj.RateChain(rxStationIdx, rateChainIdx);
                return;
            end
            
            % Retransmission
            if obj.Retransmit(rxStationIdx)
                rateChainIdx = obj.CurrentRateChainIdx(rxStationIdx);
                
                retriesLeft = obj.RemainingRetryCount(rxStationIdx, :);
                
                if retriesLeft(rateChainIdx) > 0
                    % If the current rate still has retries left, use the same
                    % rate
                    dataRateIdx = obj.RateChain(rxStationIdx, rateChainIdx);
                else
                    % If the current rate retries have exhausted, get the
                    % next rate in the rate chain
                    
                    % Find the first non-zero count in the retry counter
                    nonZeroRetryCountIdx = find(retriesLeft, 1);
                    % 'Retransmit' flag is set only when retry count is
                    % remaining for at least one rate in the rate chain
                    assert(~isempty(nonZeroRetryCountIdx), 'minstrel: Retransmit flag is set which is not expected when the number of retries exceeded the maximum limit');
                    % Use the next rate in the retry chain with non-zero
                    % attempt count
                    dataRateIdx = obj.RateChain(rxStationIdx, nonZeroRetryCountIdx);
                    obj.CurrentRateChainIdx(rxStationIdx) = nonZeroRetryCountIdx;
                end
                
            else % Initial transmission (new packet transmission)
                if obj.FrameSequenceCompleted(rxStationIdx)
                    % Get a new rate chain
                    obj.RateChain(rxStationIdx, :) = obj.getRateChain(rxStationIdx);
                    obj.FrameSequenceCompleted(rxStationIdx) = false;
                end
                
                % Reset the flag for the new packet transmission
                obj.ProbeRateUsed(rxStationIdx) = false;
                
                % Rates (Non-HT MCS indices) in the rate chain. Adding 1 for MATLAB indexing
                rates = obj.RateChain(rxStationIdx, :) + 1;
                                
                % Set the retry counts for minstrel rate chain
                retryCountShort = getRetryCounts(obj, rxStationIdx, rates, obj.ShortRetryLimit, obj.RetryCount);
                retryCountLong = getRetryCounts(obj, rxStationIdx, rates, obj.LongRetryLimit, obj.RetryCountRTSCTS);
                if isShortFrame
                    obj.RemainingRetryCount(rxStationIdx, :) = retryCountShort;
                else
                    obj.RemainingRetryCount(rxStationIdx, :) = retryCountLong;
                end
                obj.RemainingRetryCountRTS(rxStationIdx) = obj.ShortRetryLimit;
                
                % Current rate to be used for transmission from the rate
                % chain
                dataRateIdx = obj.RateChain(rxStationIdx, 1);
                obj.CurrentRateChainIdx(rxStationIdx) = 1;
            end
            
            if ~obj.ProbeRateUsed(rxStationIdx)
                % If the current rate is a probe rate, set the flag to indicate
                % that the probe rate is used
                obj.ProbeRateUsed(rxStationIdx) = obj.IsProbeRate(rxStationIdx, obj.CurrentRateChainIdx(rxStationIdx));
            end
        end
        
        function discard = updateStatus(obj, rxStationIdx, isFail, varargin)
        %updateStatus Update the status of the frame transmission
        %   DISCARD = updateStatus(OBJ, RXSTATIONIDX, ISFAIL, ISRTS,
        %   RTSRATE) updates the status, ISFAIL, of the last frame
        %   transmission to the destination station with the index,
        %   RXSTATIONIDX. ISRTS flag indicates if the transmission status
        %   is for the RTS transmission. RTSRATE is the data rate used for
        %   RTS frame, used only when ISRTS is set to true. DISCARD flag
        %   indicates that the frame needs to be dropped, either due to a
        %   successful frame transmission or due to the exhaustion of
        %   retries.
            
            isRTS = false;
            rtsRate = 0;
            if nargin > 3
                isRTS = varargin{1};
                if isRTS
                    if numel(varargin) < 2
                        error('minstrel: RTS rate index is not provided which is expected as an input when isRTS input argument is set to true');
                    end
                    rtsRate = varargin{2};
                end
            end

            % Handle RTS transmission status
            if isRTS
                discard = handleRTSTxStatus(obj, rxStationIdx, isFail, rtsRate);
            else % handle data transmission status
                discard = handleDataTxStatus(obj, rxStationIdx, isFail);
            end
        end
        
        function statistics = minstrelStatistics(obj)
        %minstrelStatistics Output the minstrel statistics
        %   STATISTICS = minstrelStatistics(OBJ) returns a structure,
        %   STATISTICS, that contains the following statistics for each
        %   receiving station:
        %
        %       RateChain               - Current rate chain (4 rates)
        %       NumAttemptsLastInterval - Number of transmission attempts 
        %                                 in the recent interval for all
        %                                 the 8 rates
        %       NumSuccessLastInterval  - Number of succesful transmissions
        %                                 in the recent interval for all
        %                                 the 8 rates
        %       TotalAttempts           - Total transmission attempts made
        %                                 by this node for all the 8 rates
        %       TotalSuccess            - Total successful transmissions
        %                                 made by this node for all the 8
        %                                 rates
        %       Throughput              - Throughput estimates for all the
        %                                 8 rates
        %       SuccessProbability      - Success probability for all the 8
        %                                 rates
        
            statistics = struct( ...
                'RateChain', obj.RateChain, ...
                'NumAttemptsLastInterval', obj.NumAttempts, ...
                'NumSuccessLastInterval', obj.NumSuccess, ...
                'TotalAttempts', obj.TotalNumTx, ...
                'TotalSuccess', obj.TotalSuccess, ...
                'Throughput', obj.ThroughputEstimate, ...
                'SuccessProbability', obj.SuccessProbability);
        end
    end
end