#!/usr/bin/env python3
#
#   Copyright 2020 - 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.

import itertools

from acts import asserts
from acts import utils
from acts.controllers.access_point import setup_ap
from acts.controllers.ap_lib.hostapd_security import Security
from acts.controllers.ap_lib import hostapd_constants
from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
from acts.utils import rand_ascii_str

# AC Capabilities
"""
Capabilities Not Supported on Whirlwind:
    - Supported Channel Width ([VHT160], [VHT160-80PLUS80]): 160mhz and 80+80
        unsupported
    - SU Beamformer [SU-BEAMFORMER]
    - SU Beamformee [SU-BEAMFORMEE]
    - MU Beamformer [MU-BEAMFORMER]
    - MU Beamformee [MU-BEAMFORMEE]
    - BF Antenna ([BF-ANTENNA-2], [BF-ANTENNA-3], [BF-ANTENNA-4])
    - Rx STBC 2, 3, & 4 ([RX-STBC-12],[RX-STBC-123],[RX-STBC-124])
    - VHT Link Adaptation ([VHT-LINK-ADAPT2],[VHT-LINK-ADAPT3])
    - VHT TXOP Power Save [VHT-TXOP-PS]
    - HTC-VHT [HTC-VHT]
"""
VHT_MAX_MPDU_LEN = [
    hostapd_constants.AC_CAPABILITY_MAX_MPDU_7991,
    hostapd_constants.AC_CAPABILITY_MAX_MPDU_11454, ''
]
RXLDPC = [hostapd_constants.AC_CAPABILITY_RXLDPC, '']
SHORT_GI_80 = [hostapd_constants.AC_CAPABILITY_SHORT_GI_80, '']
TX_STBC = [hostapd_constants.AC_CAPABILITY_TX_STBC_2BY1, '']
RX_STBC = [hostapd_constants.AC_CAPABILITY_RX_STBC_1, '']
MAX_A_MPDU = [
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP0,
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP1,
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP2,
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP3,
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP4,
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP5,
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP6,
    hostapd_constants.AC_CAPABILITY_MAX_A_MPDU_LEN_EXP7, ''
]
RX_ANTENNA = [hostapd_constants.AC_CAPABILITY_RX_ANTENNA_PATTERN, '']
TX_ANTENNA = [hostapd_constants.AC_CAPABILITY_TX_ANTENNA_PATTERN, '']

# Default 11N Capabilities
N_CAPABS_40MHZ = [
    hostapd_constants.N_CAPABILITY_LDPC, hostapd_constants.N_CAPABILITY_SGI20,
    hostapd_constants.N_CAPABILITY_RX_STBC1,
    hostapd_constants.N_CAPABILITY_SGI20, hostapd_constants.N_CAPABILITY_SGI40,
    hostapd_constants.N_CAPABILITY_MAX_AMSDU_7935,
    hostapd_constants.N_CAPABILITY_HT40_PLUS
]

N_CAPABS_20MHZ = [
    hostapd_constants.N_CAPABILITY_LDPC, hostapd_constants.N_CAPABILITY_SGI20,
    hostapd_constants.N_CAPABILITY_RX_STBC1,
    hostapd_constants.N_CAPABILITY_SGI20,
    hostapd_constants.N_CAPABILITY_MAX_AMSDU_7935,
    hostapd_constants.N_CAPABILITY_HT20
]

# Default wpa2 profile.
WPA2_SECURITY = Security(security_mode=hostapd_constants.WPA2_STRING,
                         password=rand_ascii_str(20),
                         wpa_cipher=hostapd_constants.WPA2_DEFAULT_CIPER,
                         wpa2_cipher=hostapd_constants.WPA2_DEFAULT_CIPER)

SECURITIES = [None, WPA2_SECURITY]


def generate_test_name(settings):
    """Generates a test name string based on the ac_capabilities for
    a test case.

    Args:
        settings: a dict with the test settings (bandwidth, security, ac_capabs)

    Returns:
        A string test case name
    """
    chbw = settings['chbw']
    sec = 'wpa2' if settings['security'] else 'open'
    ret = []
    for cap in hostapd_constants.AC_CAPABILITIES_MAPPING.keys():
        if cap in settings['ac_capabilities']:
            ret.append(hostapd_constants.AC_CAPABILITIES_MAPPING[cap])
    return 'test_11ac_%smhz_%s_%s' % (chbw, sec, ''.join(ret))


# 6912 test cases
class WlanPhyCompliance11ACTest(WifiBaseTest):
    """Tests for validating 11ac PHYS.

    Test Bed Requirement:
    * One Android device or Fuchsia device
    * One Access Point
    """

    def __init__(self, controllers):
        super().__init__(controllers)

    def setup_generated_tests(self):
        test_args = self._generate_20mhz_test_args() + \
            self._generate_40mhz_test_args() + \
            self._generate_80mhz_test_args()
        self.generate_tests(test_logic=self.setup_and_connect,
                            name_func=generate_test_name,
                            arg_sets=test_args)

    def setup_class(self):
        super().setup_class()
        if 'dut' in self.user_params:
            if self.user_params['dut'] == 'fuchsia_devices':
                self.dut = create_wlan_device(self.fuchsia_devices[0])
            elif self.user_params['dut'] == 'android_devices':
                self.dut = create_wlan_device(self.android_devices[0])
            else:
                raise ValueError('Invalid DUT specified in config. (%s)' %
                                 self.user_params['dut'])
        else:
            self.dut = create_wlan_device(self.android_devices[0])

        self.access_point = self.access_points[0]
        self.android_devices = getattr(self, 'android_devices', [])
        self.access_point.stop_all_aps()

    def setup_test(self):
        for ad in self.android_devices:
            ad.droid.wakeLockAcquireBright()
            ad.droid.wakeUpNow()
        self.dut.wifi_toggle_state(True)

    def teardown_test(self):
        for ad in self.android_devices:
            ad.droid.wakeLockRelease()
            ad.droid.goToSleepNow()
        self.dut.turn_location_off_and_scan_toggle_off()
        self.dut.disconnect()
        self.dut.reset_wifi()
        self.download_ap_logs()
        self.access_point.stop_all_aps()

    def on_fail(self, test_name, begin_time):
        super().on_fail(test_name, begin_time)
        self.access_point.stop_all_aps()

    def setup_and_connect(self, ap_settings):
        """Uses ap_settings to set up ap and then attempts to associate a DUT.

        Args:
            ap_settings: a dict containing test case settings, including
                bandwidth, security, n_capabilities, and ac_capabilities

        """
        ssid = rand_ascii_str(20)
        security = ap_settings['security']
        chbw = ap_settings['chbw']
        password = None
        target_security = None
        if security:
            password = security.password
            target_security = security.security_mode_string
        n_capabilities = ap_settings['n_capabilities']
        ac_capabilities = ap_settings['ac_capabilities']

        setup_ap(access_point=self.access_point,
                 profile_name='whirlwind',
                 mode=hostapd_constants.MODE_11AC_MIXED,
                 channel=36,
                 n_capabilities=n_capabilities,
                 ac_capabilities=ac_capabilities,
                 force_wmm=True,
                 ssid=ssid,
                 security=security,
                 vht_bandwidth=chbw,
                 password=password)
        asserts.assert_true(
            self.dut.associate(ssid,
                               target_pwd=password,
                               target_security=target_security),
            'Failed to associate.')

    # 1728 tests
    def _generate_20mhz_test_args(self):
        test_args = []

        # 864 test cases for open security
        # 864 test cases for wpa2 security
        for combination in itertools.product(SECURITIES, VHT_MAX_MPDU_LEN,
                                             RXLDPC, RX_STBC, TX_STBC,
                                             MAX_A_MPDU, RX_ANTENNA,
                                             TX_ANTENNA):
            security = combination[0]
            ac_capabilities = combination[1:]
            test_args.append(({
                'chbw': 20,
                'security': security,
                'n_capabilities': N_CAPABS_20MHZ,
                'ac_capabilities': ac_capabilities
            }, ))

        return test_args

    # 1728 tests
    def _generate_40mhz_test_args(self):
        test_args = []

        # 864 test cases for open security
        # 864 test cases for wpa2 security
        for combination in itertools.product(SECURITIES, VHT_MAX_MPDU_LEN,
                                             RXLDPC, RX_STBC, TX_STBC,
                                             MAX_A_MPDU, RX_ANTENNA,
                                             TX_ANTENNA):
            security = combination[0]
            ac_capabilities = combination[1:]
            test_args.append(({
                'chbw': 40,
                'security': security,
                'n_capabilities': N_CAPABS_40MHZ,
                'ac_capabilities': ac_capabilities
            }, ))

        return test_args

    # 3456 tests
    def _generate_80mhz_test_args(self):
        test_args = []

        # 1728 test cases for open security
        # 1728 test cases for wpa2 security
        for combination in itertools.product(SECURITIES, VHT_MAX_MPDU_LEN,
                                             RXLDPC, SHORT_GI_80, RX_STBC,
                                             TX_STBC, MAX_A_MPDU, RX_ANTENNA,
                                             TX_ANTENNA):
            security = combination[0]
            ac_capabilities = combination[1:]
            test_args.append(({
                'chbw': 80,
                'security': security,
                'n_capabilities': N_CAPABS_40MHZ,
                'ac_capabilities': ac_capabilities
            }, ))
        return test_args