/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.wifi;

import static com.android.server.wifi.util.InformationElementUtil.BssLoad.INVALID;
import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MAX_CHANNEL_UTILIZATION;
import static com.android.server.wifi.util.InformationElementUtil.BssLoad.MIN_CHANNEL_UTILIZATION;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiAnnotations.WifiStandard;
import android.net.wifi.WifiInfo;
import android.net.wifi.nl80211.DeviceWiphyCapabilities;
import android.util.Log;

import com.android.wifi.resources.R;

/**
 * A class that predicts network throughput based on RSSI, channel utilization, channel width,
 * WiFi standard (PHY/MAC mode), Nss and other radio information.
 */
public class ThroughputPredictor {
    private static final String TAG = "WifiThroughputPredictor";
    private boolean mVerboseLoggingEnabled = false;

    // Default value of channel utilization at 2G when channel utilization is not available from
    // BssLoad IE or from link layer stats
    public static final int CHANNEL_UTILIZATION_DEFAULT_2G = MAX_CHANNEL_UTILIZATION * 6 / 16;
    // Default value of channel utilization at 5G when channel utilization is not available from
    // BssLoad IE or from link layer stats
    public static final int CHANNEL_UTILIZATION_DEFAULT_ABOVE_2G = MAX_CHANNEL_UTILIZATION / 16;
    // Channel utilization boost when bluetooth is in the connected mode
    public static final int CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G = MAX_CHANNEL_UTILIZATION / 4;
    //TODO: b/145133625 Need to consider 6GHz

    // Number of data tones per OFDM symbol
    private static final int NUM_TONE_PER_SYM_LEGACY = 48;
    private static final int NUM_TONE_PER_SYM_11N_20MHZ = 52;
    private static final int NUM_TONE_PER_SYM_11N_40MHZ = 108;
    private static final int NUM_TONE_PER_SYM_11AC_20MHZ = 52;
    private static final int NUM_TONE_PER_SYM_11AC_40MHZ = 108;
    private static final int NUM_TONE_PER_SYM_11AC_80MHZ = 234;
    private static final int NUM_TONE_PER_SYM_11AC_160MHZ = 468;
    private static final int NUM_TONE_PER_SYM_11AX_BE_20MHZ = 234;
    private static final int NUM_TONE_PER_SYM_11AX_BE_40MHZ = 468;
    private static final int NUM_TONE_PER_SYM_11AX_BE_80MHZ = 980;
    private static final int NUM_TONE_PER_SYM_11AX_BE_160MHZ = 1960;
    private static final int NUM_TONE_PER_SYM_11BE_320MHZ = 1960 * 2;

    // 11ag OFDM symbol duration in ns
    private static final int SYM_DURATION_LEGACY_NS = 4000;
    // 11n OFDM symbol duration in ns with 0.4us guard interval
    private static final int SYM_DURATION_11N_NS = 3600;
    // 11ac OFDM symbol duration in ns with 0.4us guard interval
    private static final int SYM_DURATION_11AC_NS = 3600;
    // 11ax/be OFDM symbol duration in ns with 0.8us guard interval
    private static final int SYM_DURATION_11AX_BE_NS = 13600;
    private static final int MICRO_TO_NANO_RATIO = 1000;

    // The scaling factor for integer representation of bitPerTone and MAX_BITS_PER_TONE_XXX
    private static final int BIT_PER_TONE_SCALE = 1000;
    private static final int MAX_BITS_PER_TONE_LEGACY =
            (int) Math.round((6 * 3.0 * BIT_PER_TONE_SCALE) / 4.0);
    private static final int MAX_BITS_PER_TONE_11N =
            (int) Math.round((6 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);
    private static final int MAX_BITS_PER_TONE_11AC =
            (int) Math.round((8 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);
    private static final int MAX_BITS_PER_TONE_11AX =
            (int) Math.round((10 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);
    private static final int MAX_BITS_PER_TONE_11BE =
            (int) Math.round((12 * 5.0 * BIT_PER_TONE_SCALE) / 6.0);

    // snrDb-to-bitPerTone lookup table (LUT) used at low SNR
    // snr = Math.pow(10.0, snrDb / 10.0);
    // bitPerTone = (int) (Math.log10(1 + snr) / Math.log10(2.0) * BIT_PER_TONE_SCALE)
    private static final int TWO_IN_DB = 3;
    private static final int SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE = BIT_PER_TONE_SCALE / TWO_IN_DB;
    private static final int SNR_DB_TO_BIT_PER_TONE_LUT_MIN = -10; // minimum snrDb supported by LUT
    private static final int SNR_DB_TO_BIT_PER_TONE_LUT_MAX = 9; // maximum snrDb supported by LUT
    private static final int[] SNR_DB_TO_BIT_PER_TONE_LUT = {0, 171, 212, 262, 323, 396, 484, 586,
            706, 844, 1000, 1176, 1370, 1583, 1812, 2058, 2317, 2588, 2870, 3161};
    // Thermal noise floor power in dBm integrated over 20MHz with 5.5dB noise figure at 25C
    private static final int NOISE_FLOOR_20MHZ_DBM = -96;
    // A fudge factor to represent HW implementation margin in dB.
    // Predicted throughput matches pretty well with OTA throughput with this fudge factor.
    private static final int SNR_MARGIN_DB = 16;
    private static final int MAX_NUM_SPATIAL_STREAM_11BE = 16;
    private static final int MAX_NUM_SPATIAL_STREAM_11AX = 8;
    private static final int MAX_NUM_SPATIAL_STREAM_11AC = 8;
    private static final int MAX_NUM_SPATIAL_STREAM_11N = 4;
    private static final int MAX_NUM_SPATIAL_STREAM_LEGACY = 1;

    private static final int B_MODE_MAX_MBPS = 11;
    private final Context mContext;

    ThroughputPredictor(Context context) {
        mContext = context;
    }

    /**
     * Enable/Disable verbose logging.
     *
     * @param verbose true to enable and false to disable.
     */
    public void enableVerboseLogging(boolean verbose) {
        mVerboseLoggingEnabled = verbose;
    }

    /**
     * Predict maximum Tx throughput supported by connected network at the highest RSSI
     * with the lowest channel utilization
     * @return predicted maximum Tx throughput in Mbps
     */
    public int predictMaxTxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities) {
        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
                capabilities.channelBandwidth, WifiInfo.MAX_RSSI,
                capabilities.maxNumberTxSpatialStreams, MIN_CHANNEL_UTILIZATION, 0, null);
    }

    /**
     * Predict maximum Rx throughput supported by connected network at the highest RSSI
     * with the lowest channel utilization
     * @return predicted maximum Rx throughput in Mbps
     */
    public int predictMaxRxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities) {
        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
                capabilities.channelBandwidth, WifiInfo.MAX_RSSI,
                capabilities.maxNumberRxSpatialStreams, MIN_CHANNEL_UTILIZATION, 0, null);
    }

    /**
     * Predict Tx throughput with current connection capabilities, RSSI and channel utilization
     * @return predicted Tx throughput in Mbps
     */
    public int predictTxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities,
            int rssiDbm, int frequency, int channelUtilization) {
        int channelUtilizationFinal = getValidChannelUtilization(frequency,
                INVALID, channelUtilization, false);
        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
                capabilities.channelBandwidth, rssiDbm, capabilities.maxNumberTxSpatialStreams,
                channelUtilizationFinal, frequency, null);
    }

    /**
     * Predict Rx throughput with current connection capabilities, RSSI and channel utilization
     * @return predicted Rx throughput in Mbps
     */
    public int predictRxThroughput(@NonNull WifiNative.ConnectionCapabilities capabilities,
            int rssiDbm, int frequency, int channelUtilization) {
        int channelUtilizationFinal = getValidChannelUtilization(frequency,
                INVALID, channelUtilization, false);
        return predictThroughputInternal(capabilities.wifiStandard, capabilities.is11bMode,
                capabilities.channelBandwidth, rssiDbm, capabilities.maxNumberRxSpatialStreams,
                channelUtilizationFinal, frequency, null);
    }

    /**
     * Predict network throughput given by the current channel condition and RSSI
     * @param deviceCapabilities Phy Capabilities of the device
     * @param wifiStandardAp the highest wifi standard supported by AP
     * @param channelWidthAp the channel bandwidth of AP
     * @param rssiDbm the scan RSSI in dBm
     * @param frequency the center frequency of primary 20MHz channel
     * @param maxNumSpatialStreamAp the maximum number of spatial streams supported by AP
     * @param channelUtilizationBssLoad the channel utilization ratio indicated from BssLoad IE
     * @param channelUtilizationLinkLayerStats the channel utilization ratio detected from scan
     * @param isBluetoothConnected whether the bluetooth adaptor is in connected mode
     * @param disabledSubchannelBitmap the disabled Subchannel Bitmap (2 bytes) from EHT
     *                                 Operation IE
     * @return predicted throughput in Mbps
     */
    public int predictThroughput(DeviceWiphyCapabilities deviceCapabilities,
            @WifiStandard int wifiStandardAp,
            int channelWidthAp, int rssiDbm, int frequency, int maxNumSpatialStreamAp,
            int channelUtilizationBssLoad, int channelUtilizationLinkLayerStats,
            boolean isBluetoothConnected, @Nullable byte[] disabledSubchannelBitmap) {

        if (deviceCapabilities == null) {
            Log.e(TAG, "Null device capabilities passed to throughput predictor");
            return 0;
        }

        int maxNumSpatialStreamDevice = Math.min(deviceCapabilities.getMaxNumberTxSpatialStreams(),
                deviceCapabilities.getMaxNumberRxSpatialStreams());

        if (mContext.getResources().getBoolean(
                R.bool.config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideEnable)) {
            maxNumSpatialStreamDevice = mContext.getResources().getInteger(
                    R.integer.config_wifiFrameworkMaxNumSpatialStreamDeviceOverrideValue);
        }

        int maxNumSpatialStream = Math.min(maxNumSpatialStreamDevice, maxNumSpatialStreamAp);

        // Get minimum standard support between device and AP
        int wifiStandard;
        switch (wifiStandardAp) {
            case ScanResult.WIFI_STANDARD_11BE:
                if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11BE)) {
                    wifiStandard = ScanResult.WIFI_STANDARD_11BE;
                    break;
                }
                //FALL THROUGH
            case ScanResult.WIFI_STANDARD_11AX:
                if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AX)) {
                    wifiStandard = ScanResult.WIFI_STANDARD_11AX;
                    break;
                }
                //FALL THROUGH
            case ScanResult.WIFI_STANDARD_11AC:
                if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11AC)) {
                    wifiStandard = ScanResult.WIFI_STANDARD_11AC;
                    break;
                }
                //FALL THROUGH
            case ScanResult.WIFI_STANDARD_11N:
                if (deviceCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11N)) {
                    wifiStandard = ScanResult.WIFI_STANDARD_11N;
                    break;
                }
                //FALL THROUGH
            default:
                wifiStandard = ScanResult.WIFI_STANDARD_LEGACY;
        }

        // Calculate channel width
        int channelWidth;
        switch (channelWidthAp) {
            case ScanResult.CHANNEL_WIDTH_320MHZ:
                if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_320MHZ)) {
                    channelWidth = ScanResult.CHANNEL_WIDTH_320MHZ;
                    break;
                }
                // FALL THROUGH
            case ScanResult.CHANNEL_WIDTH_160MHZ:
                if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ)) {
                    channelWidth = ScanResult.CHANNEL_WIDTH_160MHZ;
                    break;
                }
                // FALL THROUGH
            case ScanResult.CHANNEL_WIDTH_80MHZ:
                if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ)) {
                    channelWidth = ScanResult.CHANNEL_WIDTH_80MHZ;
                    break;
                }
                // FALL THROUGH
            case ScanResult.CHANNEL_WIDTH_40MHZ:
                if (deviceCapabilities.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ)) {
                    channelWidth = ScanResult.CHANNEL_WIDTH_40MHZ;
                    break;
                }
                // FALL THROUGH
            default:
                channelWidth = ScanResult.CHANNEL_WIDTH_20MHZ;
        }

        if (mVerboseLoggingEnabled) {
            StringBuilder sb = new StringBuilder();
            Log.d(TAG, sb.append("AP Nss: ").append(maxNumSpatialStreamAp)
                    .append(", Device Nss: ").append(maxNumSpatialStreamDevice)
                    .append(", freq: ").append(frequency)
                    .toString());
        }

        int channelUtilization = getValidChannelUtilization(frequency,
                channelUtilizationBssLoad,
                channelUtilizationLinkLayerStats,
                isBluetoothConnected);

        return predictThroughputInternal(wifiStandard, false/* is11bMode */, channelWidth,
                rssiDbm, maxNumSpatialStream, channelUtilization, frequency,
                disabledSubchannelBitmap);
    }

    private int predictThroughputInternal(@WifiStandard int wifiStandard, boolean is11bMode,
            int channelWidth, int rssiDbm, int maxNumSpatialStream,  int channelUtilization,
            int frequency, @Nullable byte[] disabledSubchannelBitmap) {

        // channel bandwidth in MHz = 20MHz * (2 ^ channelWidthFactor);
        int channelWidthFactor;
        int numTonePerSym;
        int symDurationNs;
        int maxBitsPerTone;
        if (maxNumSpatialStream < 1) {
            Log.e(TAG, "maxNumSpatialStream < 1 due to wrong implementation. Overridden to 1");
            maxNumSpatialStream = 1;
        }
        if (wifiStandard == ScanResult.WIFI_STANDARD_LEGACY) {
            // For simplicity, use legacy OFDM parameters to predict 11b rate
            numTonePerSym = NUM_TONE_PER_SYM_LEGACY;
            channelWidthFactor = 0;
            maxNumSpatialStream = MAX_NUM_SPATIAL_STREAM_LEGACY;
            maxBitsPerTone = MAX_BITS_PER_TONE_LEGACY;
            symDurationNs = SYM_DURATION_LEGACY_NS;
        } else if (wifiStandard == ScanResult.WIFI_STANDARD_11N) {
            if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11N_20MHZ;
                channelWidthFactor = 0;
            } else {
                numTonePerSym = NUM_TONE_PER_SYM_11N_40MHZ;
                channelWidthFactor = 1;
            }
            maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11N);
            maxBitsPerTone = MAX_BITS_PER_TONE_11N;
            symDurationNs = SYM_DURATION_11N_NS;
        } else if (wifiStandard == ScanResult.WIFI_STANDARD_11AC) {
            if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AC_20MHZ;
                channelWidthFactor = 0;
            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_40MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AC_40MHZ;
                channelWidthFactor = 1;
            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_80MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AC_80MHZ;
                channelWidthFactor = 2;
            } else {
                numTonePerSym = NUM_TONE_PER_SYM_11AC_160MHZ;
                channelWidthFactor = 3;
            }
            maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11AC);
            maxBitsPerTone = MAX_BITS_PER_TONE_11AC;
            symDurationNs = SYM_DURATION_11AC_NS;
        } else if (wifiStandard == ScanResult.WIFI_STANDARD_11AX) {
            if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_20MHZ;
                channelWidthFactor = 0;
            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_40MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_40MHZ;
                channelWidthFactor = 1;
            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_80MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_80MHZ;
                channelWidthFactor = 2;
            } else {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_160MHZ;
                channelWidthFactor = 3;
            }
            maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11AX);
            maxBitsPerTone = MAX_BITS_PER_TONE_11AX;
            symDurationNs = SYM_DURATION_11AX_BE_NS;
        } else if (wifiStandard == ScanResult.WIFI_STANDARD_11BE) {
            if (channelWidth == ScanResult.CHANNEL_WIDTH_20MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_20MHZ;
                channelWidthFactor = 0;
            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_40MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_40MHZ;
                channelWidthFactor = 1;
            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_80MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_80MHZ;
                channelWidthFactor = 2;
            } else if (channelWidth == ScanResult.CHANNEL_WIDTH_160MHZ) {
                numTonePerSym = NUM_TONE_PER_SYM_11AX_BE_160MHZ;
                channelWidthFactor = 3;
            } else {
                numTonePerSym = NUM_TONE_PER_SYM_11BE_320MHZ;
                channelWidthFactor = 4;
            }
            int numPunctured20MhzSubChannel = 0;
            if (disabledSubchannelBitmap != null && disabledSubchannelBitmap.length == 2) {
                numPunctured20MhzSubChannel = Integer.bitCount(
                        (disabledSubchannelBitmap[1] << 8) | disabledSubchannelBitmap[0]);
            }
            if (numPunctured20MhzSubChannel * NUM_TONE_PER_SYM_11AX_BE_20MHZ < numTonePerSym) {
                numTonePerSym -= numPunctured20MhzSubChannel * NUM_TONE_PER_SYM_11AX_BE_20MHZ;
            }
            maxNumSpatialStream = Math.min(maxNumSpatialStream, MAX_NUM_SPATIAL_STREAM_11BE);
            maxBitsPerTone = MAX_BITS_PER_TONE_11BE;
            symDurationNs = SYM_DURATION_11AX_BE_NS;
        } else {
            return WifiInfo.LINK_SPEED_UNKNOWN;
        }

        // 6Ghz RSSI boost
        if (mContext.getResources().getBoolean(R.bool.config_wifiEnable6GhzBeaconRssiBoost)
                && ScanResult.is6GHz(frequency)) {
            switch (channelWidth) {
                case ScanResult.CHANNEL_WIDTH_40MHZ:
                    rssiDbm += 3;
                    break;
                case ScanResult.CHANNEL_WIDTH_80MHZ:
                    rssiDbm += 6;
                    break;
                case ScanResult.CHANNEL_WIDTH_160MHZ:
                    rssiDbm += 9;
                    break;
                case ScanResult.CHANNEL_WIDTH_320MHZ:
                    rssiDbm += 12;
                    break;
                default:
                    // do nothing
            }
        }

        // noiseFloorDbBoost = 10 * log10 * (2 ^ channelWidthFactor)
        int noiseFloorDbBoost = TWO_IN_DB * channelWidthFactor;
        int noiseFloorDbm = NOISE_FLOOR_20MHZ_DBM + noiseFloorDbBoost + SNR_MARGIN_DB;
        int snrDb  = rssiDbm - noiseFloorDbm;

        int bitPerTone = calculateBitPerTone(snrDb);
        bitPerTone = Math.min(bitPerTone, maxBitsPerTone);

        long bitPerToneTotal = bitPerTone * maxNumSpatialStream;
        long numBitPerSym = bitPerToneTotal * numTonePerSym;
        int phyRateMbps =  (int) ((numBitPerSym * MICRO_TO_NANO_RATIO)
                / (symDurationNs * BIT_PER_TONE_SCALE));

        int airTimeFraction = calculateAirTimeFraction(channelUtilization, channelWidthFactor);

        int throughputMbps = (phyRateMbps * airTimeFraction) / MAX_CHANNEL_UTILIZATION;

        if (is11bMode) {
            throughputMbps = Math.min(throughputMbps, B_MODE_MAX_MBPS);
        }
        if (mVerboseLoggingEnabled) {
            StringBuilder sb = new StringBuilder();
            Log.d(TAG, sb.append(" BW: ").append(channelWidth)
                    .append(" RSSI: ").append(rssiDbm)
                    .append(" Nss: ").append(maxNumSpatialStream)
                    .append(" Mode: ").append(wifiStandard)
                    .append(" symDur: ").append(symDurationNs)
                    .append(" snrDb ").append(snrDb)
                    .append(" bitPerTone: ").append(bitPerTone)
                    .append(" rate: ").append(phyRateMbps)
                    .append(" throughput: ").append(throughputMbps)
                    .toString());
        }
        return throughputMbps;
    }

    // Calculate the number of bits per tone based on the input of SNR in dB
    // The output is scaled up by BIT_PER_TONE_SCALE for integer representation
    private static int calculateBitPerTone(int snrDb) {
        int bitPerTone;
        if (snrDb <= SNR_DB_TO_BIT_PER_TONE_LUT_MAX) {
            int lut_in_idx = Math.max(snrDb, SNR_DB_TO_BIT_PER_TONE_LUT_MIN)
                    - SNR_DB_TO_BIT_PER_TONE_LUT_MIN;
            lut_in_idx = Math.min(lut_in_idx, SNR_DB_TO_BIT_PER_TONE_LUT.length - 1);
            bitPerTone = SNR_DB_TO_BIT_PER_TONE_LUT[lut_in_idx];
        } else {
            // bitPerTone = Math.log10(1+snr)/Math.log10(2) can be approximated as
            // Math.log10(snr) / 0.3 = log10(10^(snrDb/10)) / 0.3 = snrDb / 3
            // SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE = BIT_PER_TONE_SCALE / 3
            bitPerTone = snrDb * SNR_DB_TO_BIT_PER_TONE_HIGH_SNR_SCALE;
        }
        return bitPerTone;
    }

    private int getValidChannelUtilization(int frequency, int channelUtilizationBssLoad,
            int channelUtilizationLinkLayerStats, boolean isBluetoothConnected) {
        int channelUtilization;
        boolean is2G = ScanResult.is24GHz(frequency);
        if (isValidUtilizationRatio(channelUtilizationBssLoad)) {
            channelUtilization = channelUtilizationBssLoad;
        } else if (isValidUtilizationRatio(channelUtilizationLinkLayerStats)) {
            channelUtilization = channelUtilizationLinkLayerStats;
        } else {
            channelUtilization = is2G ? CHANNEL_UTILIZATION_DEFAULT_2G :
                    CHANNEL_UTILIZATION_DEFAULT_ABOVE_2G;
        }

        if (is2G && isBluetoothConnected) {
            channelUtilization += CHANNEL_UTILIZATION_BOOST_BT_CONNECTED_2G;
            channelUtilization = Math.min(channelUtilization, MAX_CHANNEL_UTILIZATION);
        }
        if (mVerboseLoggingEnabled) {
            StringBuilder sb = new StringBuilder();
            Log.d(TAG, sb.append(" utilization (BssLoad) ").append(channelUtilizationBssLoad)
                    .append(" utilization (LLStats) ").append(channelUtilizationLinkLayerStats)
                    .append(" isBluetoothConnected: ").append(isBluetoothConnected)
                    .append(" final utilization: ").append(channelUtilization)
                    .toString());
        }
        return channelUtilization;
    }

    /**
     * Check if the channel utilization ratio is valid
     */
    private static boolean isValidUtilizationRatio(int utilizationRatio) {
        return (utilizationRatio <= MAX_CHANNEL_UTILIZATION
                && utilizationRatio >= MIN_CHANNEL_UTILIZATION);
    }

    // Calculate the available airtime fraction value which is multiplied by
    // MAX_CHANNEL_UTILIZATION for integer representation. It is calculated as
    // (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) * MAX_CHANNEL_UTILIZATION
    private int calculateAirTimeFraction(int channelUtilization, int channelWidthFactor) {
        int airTimeFraction20MHz = MAX_CHANNEL_UTILIZATION - channelUtilization;
        int airTimeFraction = airTimeFraction20MHz;
        // For the cases of 40MHz or above, need to take
        // (1 - channelUtilization / MAX_CHANNEL_UTILIZATION) ^ (2 ^ channelWidthFactor)
        // because channelUtilization is defined for primary 20MHz channel
        for (int i = 1; i <= channelWidthFactor; ++i) {
            airTimeFraction *= airTimeFraction;
            airTimeFraction /= MAX_CHANNEL_UTILIZATION;
        }
        if (mVerboseLoggingEnabled) {
            Log.d(TAG, " airTime20: " + airTimeFraction20MHz + " airTime: " + airTimeFraction);
        }
        return airTimeFraction;
    }
}
