import logging
import os
import random
import re
import string
import threading
import time
from queue import Empty
from subprocess import call

from acts import asserts
from acts_contrib.test_utils.bt.bt_constants import adv_fail
from acts_contrib.test_utils.bt.bt_constants import adv_succ
from acts_contrib.test_utils.bt.bt_constants import batch_scan_not_supported_list
from acts_contrib.test_utils.bt.bt_constants import batch_scan_result
from acts_contrib.test_utils.bt.bt_constants import bits_per_samples
from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_tx_powers
from acts_contrib.test_utils.bt.bt_constants import bluetooth_a2dp_codec_config_changed
from acts_contrib.test_utils.bt.bt_constants import bluetooth_off
from acts_contrib.test_utils.bt.bt_constants import bluetooth_on
from acts_contrib.test_utils.bt.bt_constants import \
    bluetooth_profile_connection_state_changed
from acts_contrib.test_utils.bt.bt_constants import bluetooth_socket_conn_test_uuid
from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
from acts_contrib.test_utils.bt.bt_constants import bt_profile_constants
from acts_contrib.test_utils.bt.bt_constants import bt_profile_states
from acts_contrib.test_utils.bt.bt_constants import bt_rfcomm_uuids
from acts_contrib.test_utils.bt.bt_constants import bt_scan_mode_types
from acts_contrib.test_utils.bt.bt_constants import btsnoop_last_log_path_on_device
from acts_contrib.test_utils.bt.bt_constants import btsnoop_log_path_on_device
from acts_contrib.test_utils.bt.bt_constants import channel_modes
from acts_contrib.test_utils.bt.bt_constants import codec_types
from acts_contrib.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms
from acts_contrib.test_utils.bt.bt_constants import default_rfcomm_timeout_ms
from acts_contrib.test_utils.bt.bt_constants import hid_id_keyboard
from acts_contrib.test_utils.bt.bt_constants import pairing_variant_passkey_confirmation
from acts_contrib.test_utils.bt.bt_constants import pan_connect_timeout
from acts_contrib.test_utils.bt.bt_constants import sample_rates
from acts_contrib.test_utils.bt.bt_constants import scan_result
from acts_contrib.test_utils.bt.bt_constants import sig_uuid_constants
from acts_contrib.test_utils.bt.bt_constants import small_timeout
from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
from acts.utils import exe_cmd

from acts import utils

log = logging

advertisements_to_devices = {}

class BtTestUtilsError(Exception):
    pass

def _add_android_device_to_dictionary(android_device, profile_list,
                                      selector_dict):
    """Adds the AndroidDevice and supported features to the selector dictionary

    Args:
        android_device: The Android device.
        profile_list: The list of profiles the Android device supports.
    """
    for profile in profile_list:
        if profile in selector_dict and android_device not in selector_dict[
                profile]:
            selector_dict[profile].append(android_device)
        else:
            selector_dict[profile] = [android_device]

def bluetooth_enabled_check(ad, timeout_sec=5):
    """Checks if the Bluetooth state is enabled, if not it will attempt to
    enable it.

    Args:
        ad: The Android device list to enable Bluetooth on.
        timeout_sec: number of seconds to wait for toggle to take effect.

    Returns:
        True if successful, false if unsuccessful.
    """
    if not ad.droid.bluetoothCheckState():
        ad.droid.bluetoothToggleState(True)
        expected_bluetooth_on_event_name = bluetooth_on
        try:
            ad.ed.pop_event(expected_bluetooth_on_event_name,
                            bt_default_timeout)
        except Empty:
            ad.log.info(
                "Failed to toggle Bluetooth on(no broadcast received).")
            # Try one more time to poke at the actual state.
            if ad.droid.bluetoothCheckState():
                ad.log.info(".. actual state is ON")
                return True
            ad.log.error(".. actual state is OFF")
            return False
    end_time = time.time() + timeout_sec
    while not ad.droid.bluetoothCheckState() and time.time() < end_time:
        time.sleep(1)
    return ad.droid.bluetoothCheckState()

def check_device_supported_profiles(droid):
    """Checks for Android device supported profiles.

    Args:
        droid: The droid object to query.

    Returns:
        A dictionary of supported profiles.
    """
    profile_dict = {}
    profile_dict['hid'] = droid.bluetoothHidIsReady()
    profile_dict['hsp'] = droid.bluetoothHspIsReady()
    profile_dict['a2dp'] = droid.bluetoothA2dpIsReady()
    profile_dict['avrcp'] = droid.bluetoothAvrcpIsReady()
    profile_dict['a2dp_sink'] = droid.bluetoothA2dpSinkIsReady()
    profile_dict['hfp_client'] = droid.bluetoothHfpClientIsReady()
    profile_dict['pbap_client'] = droid.bluetoothPbapClientIsReady()
    return profile_dict

def cleanup_scanners_and_advertisers(scn_android_device, scn_callback_list,
                                     adv_android_device, adv_callback_list):
    """Try to gracefully stop all scanning and advertising instances.

    Args:
        scn_android_device: The Android device that is actively scanning.
        scn_callback_list: The scan callback id list that needs to be stopped.
        adv_android_device: The Android device that is actively advertising.
        adv_callback_list: The advertise callback id list that needs to be
            stopped.
    """
    scan_droid, scan_ed = scn_android_device.droid, scn_android_device.ed
    adv_droid = adv_android_device.droid
    try:
        for scan_callback in scn_callback_list:
            scan_droid.bleStopBleScan(scan_callback)
    except Exception as err:
        scn_android_device.log.debug(
            "Failed to stop LE scan... reseting Bluetooth. Error {}".format(
                err))
        reset_bluetooth([scn_android_device])
    try:
        for adv_callback in adv_callback_list:
            adv_droid.bleStopBleAdvertising(adv_callback)
    except Exception as err:
        adv_android_device.log.debug(
            "Failed to stop LE advertisement... reseting Bluetooth. Error {}".
            format(err))
        reset_bluetooth([adv_android_device])

def clear_bonded_devices(ad):
    """Clear bonded devices from the input Android device.

    Args:
        ad: the Android device performing the connection.
    Returns:
        True if clearing bonded devices was successful, false if unsuccessful.
    """
    bonded_device_list = ad.droid.bluetoothGetBondedDevices()
    while bonded_device_list:
        device_address = bonded_device_list[0]['address']
        if not ad.droid.bluetoothUnbond(device_address):
            log.error("Failed to unbond {} from {}".format(
                device_address, ad.serial))
            return False
        log.info("Successfully unbonded {} from {}".format(
            device_address, ad.serial))
        #TODO: wait for BOND_STATE_CHANGED intent instead of waiting
        time.sleep(1)

        # If device was first connected using LE transport, after bonding it is
        # accessible through it's LE address, and through it classic address.
        # Unbonding it will unbond two devices representing different
        # "addresses". Attempt to unbond such already unbonded devices will
        # result in bluetoothUnbond returning false.
        bonded_device_list = ad.droid.bluetoothGetBondedDevices()
    return True

def connect_phone_to_headset(android,
                             headset,
                             timeout=bt_default_timeout,
                             connection_check_period=10):
    """Connects android phone to bluetooth headset.
    Headset object must have methods power_on and enter_pairing_mode,
    and attribute mac_address.

    Args:
        android: AndroidDevice object with SL4A installed.
        headset: Object with attribute mac_address and methods power_on and
            enter_pairing_mode.
        timeout: Seconds to wait for devices to connect.
        connection_check_period: how often to check for connection once the
            SL4A connect RPC has been sent.
    Returns:
        connected (bool): True if devices are paired and connected by end of
        method. False otherwise.
    """
    headset_mac_address = headset.mac_address
    connected = is_a2dp_src_device_connected(android, headset_mac_address)
    log.info('Devices connected before pair attempt: %s' % connected)
    if not connected:
        # Turn on headset and initiate pairing mode.
        headset.enter_pairing_mode()
        android.droid.bluetoothStartPairingHelper()
    start_time = time.time()
    # If already connected, skip pair and connect attempt.
    while not connected and (time.time() - start_time < timeout):
        bonded_info = android.droid.bluetoothGetBondedDevices()
        connected_info = android.droid.bluetoothGetConnectedDevices()
        if headset.mac_address not in [
                info["address"] for info in bonded_info
        ]:
            # Use SL4A to pair and connect with headset.
            headset.enter_pairing_mode()
            android.droid.bluetoothDiscoverAndBond(headset_mac_address)
        elif headset.mac_address not in [
                info["address"] for info in connected_info
        ]:
            #Device is bonded but not connected
            android.droid.bluetoothConnectBonded(headset_mac_address)
        else:
            #Headset is connected, but A2DP profile is not
            android.droid.bluetoothA2dpConnect(headset_mac_address)
        log.info('Waiting for connection...')
        time.sleep(connection_check_period)
        # Check for connection.
        connected = is_a2dp_src_device_connected(android, headset_mac_address)
    log.info('Devices connected after pair attempt: %s' % connected)
    return connected

def connect_pri_to_sec(pri_ad, sec_ad, profiles_set, attempts=2):
    """Connects pri droid to secondary droid.

    Args:
        pri_ad: AndroidDroid initiating connection
        sec_ad: AndroidDroid accepting connection
        profiles_set: Set of profiles to be connected
        attempts: Number of attempts to try until failure.

    Returns:
        Pass if True
        Fail if False
    """
    device_addr = sec_ad.droid.bluetoothGetLocalAddress()
    # Allows extra time for the SDP records to be updated.
    time.sleep(2)
    curr_attempts = 0
    while curr_attempts < attempts:
        log.info("connect_pri_to_sec curr attempt {} total {}".format(
            curr_attempts, attempts))
        if _connect_pri_to_sec(pri_ad, sec_ad, profiles_set):
            return True
        curr_attempts += 1
    log.error("connect_pri_to_sec failed to connect after {} attempts".format(
        attempts))
    return False

def _connect_pri_to_sec(pri_ad, sec_ad, profiles_set):
    """Connects pri droid to secondary droid.

    Args:
        pri_ad: AndroidDroid initiating connection.
        sec_ad: AndroidDroid accepting connection.
        profiles_set: Set of profiles to be connected.

    Returns:
        True of connection is successful, false if unsuccessful.
    """
    # Check if we support all profiles.
    supported_profiles = bt_profile_constants.values()
    for profile in profiles_set:
        if profile not in supported_profiles:
            pri_ad.log.info("Profile {} is not supported list {}".format(
                profile, supported_profiles))
            return False

    # First check that devices are bonded.
    paired = False
    for paired_device in pri_ad.droid.bluetoothGetBondedDevices():
        if paired_device['address'] == \
                sec_ad.droid.bluetoothGetLocalAddress():
            paired = True
            break

    if not paired:
        pri_ad.log.error("Not paired to {}".format(sec_ad.serial))
        return False

    # Now try to connect them, the following call will try to initiate all
    # connections.
    pri_ad.droid.bluetoothConnectBonded(
        sec_ad.droid.bluetoothGetLocalAddress())

    end_time = time.time() + 10
    profile_connected = set()
    sec_addr = sec_ad.droid.bluetoothGetLocalAddress()
    pri_ad.log.info("Profiles to be connected {}".format(profiles_set))
    # First use APIs to check profile connection state
    while (time.time() < end_time
           and not profile_connected.issuperset(profiles_set)):
        if (bt_profile_constants['headset_client'] not in profile_connected
                and bt_profile_constants['headset_client'] in profiles_set):
            if is_hfp_client_device_connected(pri_ad, sec_addr):
                profile_connected.add(bt_profile_constants['headset_client'])
        if (bt_profile_constants['a2dp'] not in profile_connected
                and bt_profile_constants['a2dp'] in profiles_set):
            if is_a2dp_src_device_connected(pri_ad, sec_addr):
                profile_connected.add(bt_profile_constants['a2dp'])
        if (bt_profile_constants['a2dp_sink'] not in profile_connected
                and bt_profile_constants['a2dp_sink'] in profiles_set):
            if is_a2dp_snk_device_connected(pri_ad, sec_addr):
                profile_connected.add(bt_profile_constants['a2dp_sink'])
        if (bt_profile_constants['map_mce'] not in profile_connected
                and bt_profile_constants['map_mce'] in profiles_set):
            if is_map_mce_device_connected(pri_ad, sec_addr):
                profile_connected.add(bt_profile_constants['map_mce'])
        if (bt_profile_constants['map'] not in profile_connected
                and bt_profile_constants['map'] in profiles_set):
            if is_map_mse_device_connected(pri_ad, sec_addr):
                profile_connected.add(bt_profile_constants['map'])
        time.sleep(0.1)
    # If APIs fail, try to find the connection broadcast receiver.
    while not profile_connected.issuperset(profiles_set):
        try:
            profile_event = pri_ad.ed.pop_event(
                bluetooth_profile_connection_state_changed,
                bt_default_timeout + 10)
            pri_ad.log.info("Got event {}".format(profile_event))
        except Exception:
            pri_ad.log.error("Did not get {} profiles left {}".format(
                bluetooth_profile_connection_state_changed, profile_connected))
            return False

        profile = profile_event['data']['profile']
        state = profile_event['data']['state']
        device_addr = profile_event['data']['addr']
        if state == bt_profile_states['connected'] and \
                device_addr == sec_ad.droid.bluetoothGetLocalAddress():
            profile_connected.add(profile)
        pri_ad.log.info(
            "Profiles connected until now {}".format(profile_connected))
    # Failure happens inside the while loop. If we came here then we already
    # connected.
    return True

def determine_max_advertisements(android_device):
    """Determines programatically how many advertisements the Android device
    supports.

    Args:
        android_device: The Android device to determine max advertisements of.

    Returns:
        The maximum advertisement count.
    """
    android_device.log.info(
        "Determining number of maximum concurrent advertisements...")
    advertisement_count = 0
    bt_enabled = False
    expected_bluetooth_on_event_name = bluetooth_on
    if not android_device.droid.bluetoothCheckState():
        android_device.droid.bluetoothToggleState(True)
    try:
        android_device.ed.pop_event(expected_bluetooth_on_event_name,
                                    bt_default_timeout)
    except Exception:
        android_device.log.info(
            "Failed to toggle Bluetooth on(no broadcast received).")
        # Try one more time to poke at the actual state.
        if android_device.droid.bluetoothCheckState() is True:
            android_device.log.info(".. actual state is ON")
        else:
            android_device.log.error(
                "Failed to turn Bluetooth on. Setting default advertisements to 1"
            )
            advertisement_count = -1
            return advertisement_count
    advertise_callback_list = []
    advertise_data = android_device.droid.bleBuildAdvertiseData()
    advertise_settings = android_device.droid.bleBuildAdvertiseSettings()
    while (True):
        advertise_callback = android_device.droid.bleGenBleAdvertiseCallback()
        advertise_callback_list.append(advertise_callback)

        android_device.droid.bleStartBleAdvertising(advertise_callback,
                                                    advertise_data,
                                                    advertise_settings)

        regex = "(" + adv_succ.format(
            advertise_callback) + "|" + adv_fail.format(
                advertise_callback) + ")"
        # wait for either success or failure event
        evt = android_device.ed.pop_events(regex, bt_default_timeout,
                                           small_timeout)
        if evt[0]["name"] == adv_succ.format(advertise_callback):
            advertisement_count += 1
            android_device.log.info(
                "Advertisement {} started.".format(advertisement_count))
        else:
            error = evt[0]["data"]["Error"]
            if error == "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS":
                android_device.log.info(
                    "Advertisement failed to start. Reached max " +
                    "advertisements at {}".format(advertisement_count))
                break
            else:
                raise BtTestUtilsError(
                    "Expected ADVERTISE_FAILED_TOO_MANY_ADVERTISERS," +
                    " but received bad error code {}".format(error))
    try:
        for adv in advertise_callback_list:
            android_device.droid.bleStopBleAdvertising(adv)
    except Exception:
        android_device.log.error(
            "Failed to stop advertisingment, resetting Bluetooth.")
        reset_bluetooth([android_device])
    return advertisement_count

def disable_bluetooth(droid):
    """Disable Bluetooth on input Droid object.

    Args:
        droid: The droid object to disable Bluetooth on.

    Returns:
        True if successful, false if unsuccessful.
    """
    if droid.bluetoothCheckState() is True:
        droid.bluetoothToggleState(False)
        if droid.bluetoothCheckState() is True:
            log.error("Failed to toggle Bluetooth off.")
            return False
    return True

def disconnect_pri_from_sec(pri_ad, sec_ad, profiles_list):
    """
    Disconnect primary from secondary on a specific set of profiles
    Args:
        pri_ad - Primary android_device initiating disconnection
        sec_ad - Secondary android droid (sl4a interface to keep the
          method signature the same connect_pri_to_sec above)
        profiles_list - List of profiles we want to disconnect from

    Returns:
        True on Success
        False on Failure
    """
    # Sanity check to see if all the profiles in the given set is supported
    supported_profiles = bt_profile_constants.values()
    for profile in profiles_list:
        if profile not in supported_profiles:
            pri_ad.log.info("Profile {} is not in supported list {}".format(
                profile, supported_profiles))
            return False

    pri_ad.log.info(pri_ad.droid.bluetoothGetBondedDevices())
    # Disconnecting on a already disconnected profile is a nop,
    # so not checking for the connection state
    try:
        pri_ad.droid.bluetoothDisconnectConnectedProfile(
            sec_ad.droid.bluetoothGetLocalAddress(), profiles_list)
    except Exception as err:
        pri_ad.log.error(
            "Exception while trying to disconnect profile(s) {}: {}".format(
                profiles_list, err))
        return False

    profile_disconnected = set()
    pri_ad.log.info("Disconnecting from profiles: {}".format(profiles_list))

    while not profile_disconnected.issuperset(profiles_list):
        try:
            profile_event = pri_ad.ed.pop_event(
                bluetooth_profile_connection_state_changed, bt_default_timeout)
            pri_ad.log.info("Got event {}".format(profile_event))
        except Exception as e:
            pri_ad.log.error(
                "Did not disconnect from Profiles. Reason {}".format(e))
            return False

        profile = profile_event['data']['profile']
        state = profile_event['data']['state']
        device_addr = profile_event['data']['addr']

        if state == bt_profile_states['disconnected'] and \
                device_addr == sec_ad.droid.bluetoothGetLocalAddress():
            profile_disconnected.add(profile)
        pri_ad.log.info(
            "Profiles disconnected so far {}".format(profile_disconnected))

    return True

def enable_bluetooth(droid, ed):
    if droid.bluetoothCheckState() is True:
        return True

    droid.bluetoothToggleState(True)
    expected_bluetooth_on_event_name = bluetooth_on
    try:
        ed.pop_event(expected_bluetooth_on_event_name, bt_default_timeout)
    except Exception:
        log.info("Failed to toggle Bluetooth on (no broadcast received)")
        if droid.bluetoothCheckState() is True:
            log.info(".. actual state is ON")
            return True
        log.info(".. actual state is OFF")
        return False

    return True

def factory_reset_bluetooth(android_devices):
    """Clears Bluetooth stack of input Android device list.

        Args:
            android_devices: The Android device list to reset Bluetooth

        Returns:
            True if successful, false if unsuccessful.
        """
    for a in android_devices:
        droid, ed = a.droid, a.ed
        a.log.info("Reset state of bluetooth on device.")
        if not bluetooth_enabled_check(a):
            return False
        # TODO: remove device unbond b/79418045
        # Temporary solution to ensure all devices are unbonded
        bonded_devices = droid.bluetoothGetBondedDevices()
        for b in bonded_devices:
            a.log.info("Removing bond for device {}".format(b['address']))
            droid.bluetoothUnbond(b['address'])

        droid.bluetoothFactoryReset()
        wait_for_bluetooth_manager_state(droid)
        if not enable_bluetooth(droid, ed):
            return False
    return True

def generate_ble_advertise_objects(droid):
    """Generate generic LE advertise objects.

    Args:
        droid: The droid object to generate advertise LE objects from.

    Returns:
        advertise_callback: The generated advertise callback id.
        advertise_data: The generated advertise data id.
        advertise_settings: The generated advertise settings id.
    """
    advertise_callback = droid.bleGenBleAdvertiseCallback()
    advertise_data = droid.bleBuildAdvertiseData()
    advertise_settings = droid.bleBuildAdvertiseSettings()
    return advertise_callback, advertise_data, advertise_settings

def generate_ble_scan_objects(droid):
    """Generate generic LE scan objects.

    Args:
        droid: The droid object to generate LE scan objects from.

    Returns:
        filter_list: The generated scan filter list id.
        scan_settings: The generated scan settings id.
        scan_callback: The generated scan callback id.
    """
    filter_list = droid.bleGenFilterList()
    scan_settings = droid.bleBuildScanSetting()
    scan_callback = droid.bleGenScanCallback()
    return filter_list, scan_settings, scan_callback

def generate_id_by_size(size,
                        chars=(string.ascii_lowercase +
                               string.ascii_uppercase + string.digits)):
    """Generate random ascii characters of input size and input char types

    Args:
        size: Input size of string.
        chars: (Optional) Chars to use in generating a random string.

    Returns:
        String of random input chars at the input size.
    """
    return ''.join(random.choice(chars) for _ in range(size))

def get_advanced_droid_list(android_devices):
    """Add max_advertisement and batch_scan_supported attributes to input
    Android devices

    This will programatically determine maximum LE advertisements of each
    input Android device.

    Args:
        android_devices: The Android devices to setup.

    Returns:
        List of Android devices with new attribtues.
    """
    droid_list = []
    for a in android_devices:
        d, e = a.droid, a.ed
        model = d.getBuildModel()
        max_advertisements = 1
        batch_scan_supported = True
        if model in advertisements_to_devices.keys():
            max_advertisements = advertisements_to_devices[model]
        else:
            max_advertisements = determine_max_advertisements(a)
            max_tries = 3
            # Retry to calculate max advertisements
            while max_advertisements == -1 and max_tries > 0:
                a.log.info(
                    "Attempts left to determine max advertisements: {}".format(
                        max_tries))
                max_advertisements = determine_max_advertisements(a)
                max_tries -= 1
            advertisements_to_devices[model] = max_advertisements
        if model in batch_scan_not_supported_list:
            batch_scan_supported = False
        role = {
            'droid': d,
            'ed': e,
            'max_advertisements': max_advertisements,
            'batch_scan_supported': batch_scan_supported
        }
        droid_list.append(role)
    return droid_list

def get_bluetooth_crash_count(android_device):
    out = android_device.adb.shell("dumpsys bluetooth_manager")
    return int(re.search("crashed(.*\d)", out).group(1))

def read_otp(ad):
    """Reads and parses the OTP output to return TX power backoff

    Reads the OTP registers from the phone, parses them to return a
    dict of TX power backoffs for different power levels

    Args:
        ad : android device object

    Returns :
        otp_dict : power backoff dict
    """

    ad.adb.shell('svc bluetooth disable')
    time.sleep(2)
    otp_output = ad.adb.shell('bluetooth_sar_test -r')
    ad.adb.shell('svc bluetooth enable')
    time.sleep(2)
    otp_dict = {
        "BR": {
            "10": 0,
            "9": 0,
            "8": 0
        },
        "EDR": {
            "10": 0,
            "9": 0,
            "8": 0
        },
        "BLE": {
            "10": 0,
            "9": 0,
            "8": 0
        }
    }

    otp_regex = '\s+\[\s+PL10:\s+(\d+)\s+PL9:\s+(\d+)*\s+PL8:\s+(\d+)\s+\]'

    for key in otp_dict:
        bank_list = re.findall("{}{}".format(key, otp_regex), otp_output)
        for bank_tuple in bank_list:
            if ('0', '0', '0') != bank_tuple:
                [otp_dict[key]["10"], otp_dict[key]["9"],
                 otp_dict[key]["8"]] = bank_tuple
    return otp_dict

def get_bt_metric(ad_list, duration=1, tag="bt_metric", processed=True):
    """ Function to get the bt metric from logcat.

    Captures logcat for the specified duration and returns the bqr results.
    Takes list of android objects as input. If a single android object is given,
    converts it into a list.

    Args:
        ad_list: list of android_device objects
        duration: time duration (seconds) for which the logcat is parsed.
        tag: tag to be appended to the logcat dump.
        processed: flag to process bqr output.

    Returns:
        metrics_dict: dict of metrics for each android device.
    """

    # Defining bqr quantitites and their regex to extract
    regex_dict = {
        "vsp_txpl": "VSP_TxPL:\s(\S+)",
        "pwlv": "PwLv:\s(\S+)",
        "rssi": "RSSI:\s[-](\d+)"
    }
    metrics_dict = {"rssi": {}, "pwlv": {}, "vsp_txpl": {}}

    # Converting a single android device object to list
    if not isinstance(ad_list, list):
        ad_list = [ad_list]

    #Time sync with the test machine
    for ad in ad_list:
        ad.droid.setTime(int(round(time.time() * 1000)))
        time.sleep(0.5)

    begin_time = utils.get_current_epoch_time()
    time.sleep(duration)
    end_time = utils.get_current_epoch_time()

    for ad in ad_list:
        bt_rssi_log = ad.cat_adb_log(tag, begin_time, end_time)
        bqr_tag = "Handle:"

        # Extracting supporting bqr quantities
        for metric, regex in regex_dict.items():
            bqr_metric = []
            file_bt_log = open(bt_rssi_log, "r")
            for line in file_bt_log:
                if bqr_tag in line:
                    if re.findall(regex, line):
                        m = re.findall(regex, line)[0].strip(",")
                        bqr_metric.append(m)
            metrics_dict[metric][ad.serial] = bqr_metric

        # Ensures back-compatibility for vsp_txpl enabled DUTs
        if metrics_dict["vsp_txpl"][ad.serial]:
            metrics_dict["pwlv"][ad.serial] = metrics_dict["vsp_txpl"][
                ad.serial]

        # Formatting the raw data
        metrics_dict["rssi"][ad.serial] = [
            (-1) * int(x) for x in metrics_dict["rssi"][ad.serial]
        ]
        metrics_dict["pwlv"][ad.serial] = [
            int(x, 16) for x in metrics_dict["pwlv"][ad.serial]
        ]

        # Processing formatted data if processing is required
        if processed:
            # Computes the average RSSI
            metrics_dict["rssi"][ad.serial] = round(
                sum(metrics_dict["rssi"][ad.serial]) /
                len(metrics_dict["rssi"][ad.serial]), 2)
            # Returns last noted value for power level
            metrics_dict["pwlv"][ad.serial] = float(
                sum(metrics_dict["pwlv"][ad.serial]) /
                len(metrics_dict["pwlv"][ad.serial]))

    return metrics_dict

def get_bt_rssi(ad, duration=1, processed=True):
    """Function to get average bt rssi from logcat.

    This function returns the average RSSI for the given duration. RSSI values are
    extracted from BQR.

    Args:
        ad: (list of) android_device object.
        duration: time duration(seconds) for which logcat is parsed.

    Returns:
        avg_rssi: average RSSI on each android device for the given duration.
    """
    function_tag = "get_bt_rssi"
    bqr_results = get_bt_metric(ad,
                                duration,
                                tag=function_tag,
                                processed=processed)
    return bqr_results["rssi"]

def enable_bqr(
    ad_list,
    bqr_interval=10,
    bqr_event_mask=15,
):
    """Sets up BQR reporting.

       Sets up BQR to report BT metrics at the requested frequency and toggles
       airplane mode for the bqr settings to take effect.

    Args:
        ad_list: an android_device or list of android devices.
    """
    # Converting a single android device object to list
    if not isinstance(ad_list, list):
        ad_list = [ad_list]

    for ad in ad_list:
        #Setting BQR parameters
        ad.adb.shell("setprop persist.bluetooth.bqr.event_mask {}".format(
            bqr_event_mask))
        ad.adb.shell("setprop persist.bluetooth.bqr.min_interval_ms {}".format(
            bqr_interval))

        ## Toggle airplane mode
        ad.droid.connectivityToggleAirplaneMode(True)
        ad.droid.connectivityToggleAirplaneMode(False)

def disable_bqr(ad_list):
    """Disables BQR reporting.

    Args:
        ad_list: an android_device or list of android devices.
    """
    # Converting a single android device object to list
    if not isinstance(ad_list, list):
        ad_list = [ad_list]

    DISABLE_BQR_MASK = 0

    for ad in ad_list:
        #Disabling BQR
        ad.adb.shell("setprop persist.bluetooth.bqr.event_mask {}".format(
            DISABLE_BQR_MASK))

        ## Toggle airplane mode
        ad.droid.connectivityToggleAirplaneMode(True)
        ad.droid.connectivityToggleAirplaneMode(False)

def get_device_selector_dictionary(android_device_list):
    """Create a dictionary of Bluetooth features vs Android devices.

    Args:
        android_device_list: The list of Android devices.
    Returns:
        A dictionary of profiles/features to Android devices.
    """
    selector_dict = {}
    for ad in android_device_list:
        uuids = ad.droid.bluetoothGetLocalUuids()

        for profile, uuid_const in sig_uuid_constants.items():
            uuid_check = sig_uuid_constants['BASE_UUID'].format(
                uuid_const).lower()
            if uuids and uuid_check in uuids:
                if profile in selector_dict:
                    selector_dict[profile].append(ad)
                else:
                    selector_dict[profile] = [ad]

        # Various services may not be active during BT startup.
        # If the device can be identified through adb shell pm list features
        # then try to add them to the appropriate profiles / features.

        # Android TV.
        if "feature:android.hardware.type.television" in ad.features:
            ad.log.info("Android TV device found.")
            supported_profiles = ['AudioSink']
            _add_android_device_to_dictionary(ad, supported_profiles,
                                              selector_dict)

        # Android Auto
        elif "feature:android.hardware.type.automotive" in ad.features:
            ad.log.info("Android Auto device found.")
            # Add: AudioSink , A/V_RemoteControl,
            supported_profiles = [
                'AudioSink', 'A/V_RemoteControl', 'Message Notification Server'
            ]
            _add_android_device_to_dictionary(ad, supported_profiles,
                                              selector_dict)
        # Android Wear
        elif "feature:android.hardware.type.watch" in ad.features:
            ad.log.info("Android Wear device found.")
            supported_profiles = []
            _add_android_device_to_dictionary(ad, supported_profiles,
                                              selector_dict)
        # Android Phone
        elif "feature:android.hardware.telephony" in ad.features:
            ad.log.info("Android Phone device found.")
            # Add: AudioSink
            supported_profiles = [
                'AudioSource', 'A/V_RemoteControlTarget',
                'Message Access Server'
            ]
            _add_android_device_to_dictionary(ad, supported_profiles,
                                              selector_dict)
    return selector_dict

def get_mac_address_of_generic_advertisement(scan_ad, adv_ad):
    """Start generic advertisement and get it's mac address by LE scanning.

    Args:
        scan_ad: The Android device to use as the scanner.
        adv_ad: The Android device to use as the advertiser.

    Returns:
        mac_address: The mac address of the advertisement.
        advertise_callback: The advertise callback id of the active
            advertisement.
    """
    adv_ad.droid.bleSetAdvertiseDataIncludeDeviceName(True)
    adv_ad.droid.bleSetAdvertiseSettingsAdvertiseMode(
        ble_advertise_settings_modes['low_latency'])
    adv_ad.droid.bleSetAdvertiseSettingsIsConnectable(True)
    adv_ad.droid.bleSetAdvertiseSettingsTxPowerLevel(
        ble_advertise_settings_tx_powers['high'])
    advertise_callback, advertise_data, advertise_settings = (
        generate_ble_advertise_objects(adv_ad.droid))
    adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data,
                                        advertise_settings)
    try:
        adv_ad.ed.pop_event(adv_succ.format(advertise_callback),
                            bt_default_timeout)
    except Empty as err:
        raise BtTestUtilsError(
            "Advertiser did not start successfully {}".format(err))
    filter_list = scan_ad.droid.bleGenFilterList()
    scan_settings = scan_ad.droid.bleBuildScanSetting()
    scan_callback = scan_ad.droid.bleGenScanCallback()
    scan_ad.droid.bleSetScanFilterDeviceName(
        adv_ad.droid.bluetoothGetLocalName())
    scan_ad.droid.bleBuildScanFilter(filter_list)
    scan_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback)
    try:
        event = scan_ad.ed.pop_event(
            "BleScan{}onScanResults".format(scan_callback), bt_default_timeout)
    except Empty as err:
        raise BtTestUtilsError(
            "Scanner did not find advertisement {}".format(err))
    mac_address = event['data']['Result']['deviceInfo']['address']
    return mac_address, advertise_callback, scan_callback

def hid_device_send_key_data_report(host_id, device_ad, key, interval=1):
    """Send a HID report simulating a 1-second keyboard press from host_ad to
    device_ad

    Args:
        host_id: the Bluetooth MAC address or name of the HID host
        device_ad: HID device
        key: the key we want to send
        interval: the interval between key press and key release
    """
    device_ad.droid.bluetoothHidDeviceSendReport(host_id, hid_id_keyboard,
                                                 hid_keyboard_report(key))
    time.sleep(interval)
    device_ad.droid.bluetoothHidDeviceSendReport(host_id, hid_id_keyboard,
                                                 hid_keyboard_report("00"))

def hid_keyboard_report(key, modifier="00"):
    """Get the HID keyboard report for the given key

    Args:
        key: the key we want
        modifier: HID keyboard modifier bytes
    Returns:
        The byte array for the HID report.
    """
    return str(
        bytearray.fromhex(" ".join(
            [modifier, "00", key, "00", "00", "00", "00", "00"])), "utf-8")

def is_a2dp_connected(sink, source):
    """
    Convenience Function to see if the 2 devices are connected on
    A2dp.
    Args:
        sink:       Audio Sink
        source:     Audio Source
    Returns:
        True if Connected
        False if Not connected
    """

    devices = sink.droid.bluetoothA2dpSinkGetConnectedDevices()
    for device in devices:
        sink.log.info("A2dp Connected device {}".format(device["name"]))
        if (device["address"] == source.droid.bluetoothGetLocalAddress()):
            return True
    return False

def is_a2dp_snk_device_connected(ad, addr):
    """Determines if an AndroidDevice has A2DP snk connectivity to input address

    Args:
        ad: the Android device
        addr: the address that's expected
    Returns:
        True if connection was successful, false if unsuccessful.
    """
    devices = ad.droid.bluetoothA2dpSinkGetConnectedDevices()
    ad.log.info("Connected A2DP Sink devices: {}".format(devices))
    if addr in {d['address'] for d in devices}:
        return True
    return False

def is_a2dp_src_device_connected(ad, addr):
    """Determines if an AndroidDevice has A2DP connectivity to input address

    Args:
        ad: the Android device
        addr: the address that's expected
    Returns:
        True if connection was successful, false if unsuccessful.
    """
    devices = ad.droid.bluetoothA2dpGetConnectedDevices()
    ad.log.info("Connected A2DP Source devices: {}".format(devices))
    if addr in {d['address'] for d in devices}:
        return True
    return False

def is_hfp_client_device_connected(ad, addr):
    """Determines if an AndroidDevice has HFP connectivity to input address

    Args:
        ad: the Android device
        addr: the address that's expected
    Returns:
        True if connection was successful, false if unsuccessful.
    """
    devices = ad.droid.bluetoothHfpClientGetConnectedDevices()
    ad.log.info("Connected HFP Client devices: {}".format(devices))
    if addr in {d['address'] for d in devices}:
        return True
    return False

def is_map_mce_device_connected(ad, addr):
    """Determines if an AndroidDevice has MAP MCE connectivity to input address

    Args:
        ad: the Android device
        addr: the address that's expected
    Returns:
        True if connection was successful, false if unsuccessful.
    """
    devices = ad.droid.bluetoothMapClientGetConnectedDevices()
    ad.log.info("Connected MAP MCE devices: {}".format(devices))
    if addr in {d['address'] for d in devices}:
        return True
    return False

def is_map_mse_device_connected(ad, addr):
    """Determines if an AndroidDevice has MAP MSE connectivity to input address

    Args:
        ad: the Android device
        addr: the address that's expected
    Returns:
        True if connection was successful, false if unsuccessful.
    """
    devices = ad.droid.bluetoothMapGetConnectedDevices()
    ad.log.info("Connected MAP MSE devices: {}".format(devices))
    if addr in {d['address'] for d in devices}:
        return True
    return False

def kill_bluetooth_process(ad):
    """Kill Bluetooth process on Android device.

    Args:
        ad: Android device to kill BT process on.
    """
    ad.log.info("Killing Bluetooth process.")
    pid = ad.adb.shell(
        "ps | grep com.android.bluetooth | awk '{print $2}'").decode('ascii')
    call(["adb -s " + ad.serial + " shell kill " + pid], shell=True)

def log_energy_info(android_devices, state):
    """Logs energy info of input Android devices.

    Args:
        android_devices: input Android device list to log energy info from.
        state: the input state to log. Usually 'Start' or 'Stop' for logging.

    Returns:
        A logging string of the Bluetooth energy info reported.
    """
    return_string = "{} Energy info collection:\n".format(state)
    # Bug: b/31966929
    return return_string

def orchestrate_and_verify_pan_connection(pan_dut, panu_dut):
    """Setups up a PAN conenction between two android devices.

    Args:
        pan_dut: the Android device providing tethering services
        panu_dut: the Android device using the internet connection from the
            pan_dut
    Returns:
        True if PAN connection and verification is successful,
        false if unsuccessful.
    """
    if not toggle_airplane_mode_by_adb(log, panu_dut, True):
        panu_dut.log.error("Failed to toggle airplane mode on")
        return False
    if not toggle_airplane_mode_by_adb(log, panu_dut, False):
        pan_dut.log.error("Failed to toggle airplane mode off")
        return False
    pan_dut.droid.bluetoothStartConnectionStateChangeMonitor("")
    panu_dut.droid.bluetoothStartConnectionStateChangeMonitor("")
    if not bluetooth_enabled_check(panu_dut):
        return False
    if not bluetooth_enabled_check(pan_dut):
        return False
    pan_dut.droid.bluetoothPanSetBluetoothTethering(True)
    if not (pair_pri_to_sec(pan_dut, panu_dut)):
        return False
    if not pan_dut.droid.bluetoothPanIsTetheringOn():
        pan_dut.log.error("Failed to enable Bluetooth tethering.")
        return False
    # Magic sleep needed to give the stack time in between bonding and
    # connecting the PAN profile.
    time.sleep(pan_connect_timeout)
    panu_dut.droid.bluetoothConnectBonded(
        pan_dut.droid.bluetoothGetLocalAddress())
    if not verify_http_connection(log, panu_dut):
        panu_dut.log.error("Can't verify http connection on PANU device.")
        if not verify_http_connection(log, pan_dut):
            pan_dut.log.info(
                "Can't verify http connection on PAN service device")
        return False
    return True

def orchestrate_bluetooth_socket_connection(
        client_ad,
        server_ad,
        accept_timeout_ms=default_bluetooth_socket_timeout_ms,
        uuid=None):
    """Sets up the Bluetooth Socket connection between two Android devices.

    Args:
        client_ad: the Android device performing the connection.
        server_ad: the Android device accepting the connection.
    Returns:
        True if connection was successful, false if unsuccessful.
    """
    server_ad.droid.bluetoothStartPairingHelper()
    client_ad.droid.bluetoothStartPairingHelper()

    server_ad.droid.bluetoothSocketConnBeginAcceptThreadUuid(
        (bluetooth_socket_conn_test_uuid if uuid is None else uuid),
        accept_timeout_ms)
    client_ad.droid.bluetoothSocketConnBeginConnectThreadUuid(
        server_ad.droid.bluetoothGetLocalAddress(),
        (bluetooth_socket_conn_test_uuid if uuid is None else uuid))

    end_time = time.time() + bt_default_timeout
    result = False
    test_result = True
    while time.time() < end_time:
        if len(client_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
            test_result = True
            client_ad.log.info("Bluetooth socket Client Connection Active")
            break
        else:
            test_result = False
        time.sleep(1)
    if not test_result:
        client_ad.log.error(
            "Failed to establish a Bluetooth socket connection")
        return False
    return True

def orchestrate_rfcomm_connection(client_ad,
                                  server_ad,
                                  accept_timeout_ms=default_rfcomm_timeout_ms,
                                  uuid=None):
    """Sets up the RFCOMM connection between two Android devices.

    Args:
        client_ad: the Android device performing the connection.
        server_ad: the Android device accepting the connection.
    Returns:
        True if connection was successful, false if unsuccessful.
    """
    result = orchestrate_bluetooth_socket_connection(
        client_ad, server_ad, accept_timeout_ms,
        (bt_rfcomm_uuids['default_uuid'] if uuid is None else uuid))

    return result

def pair_pri_to_sec(pri_ad, sec_ad, attempts=2, auto_confirm=True):
    """Pairs pri droid to secondary droid.

    Args:
        pri_ad: Android device initiating connection
        sec_ad: Android device accepting connection
        attempts: Number of attempts to try until failure.
        auto_confirm: Auto confirm passkey match for both devices

    Returns:
        Pass if True
        Fail if False
    """
    pri_ad.droid.bluetoothStartConnectionStateChangeMonitor(
        sec_ad.droid.bluetoothGetLocalAddress())
    curr_attempts = 0
    while curr_attempts < attempts:
        if _pair_pri_to_sec(pri_ad, sec_ad, auto_confirm):
            return True
        # Wait 2 seconds before unbound
        time.sleep(2)
        if not clear_bonded_devices(pri_ad):
            log.error(
                "Failed to clear bond for primary device at attempt {}".format(
                    str(curr_attempts)))
            return False
        if not clear_bonded_devices(sec_ad):
            log.error(
                "Failed to clear bond for secondary device at attempt {}".
                format(str(curr_attempts)))
            return False
        # Wait 2 seconds after unbound
        time.sleep(2)
        curr_attempts += 1
    log.error("pair_pri_to_sec failed to connect after {} attempts".format(
        str(attempts)))
    return False

def _pair_pri_to_sec(pri_ad, sec_ad, auto_confirm):
    # Enable discovery on sec_ad so that pri_ad can find it.
    # The timeout here is based on how much time it would take for two devices
    # to pair with each other once pri_ad starts seeing devices.
    pri_droid = pri_ad.droid
    sec_droid = sec_ad.droid
    pri_ad.ed.clear_all_events()
    sec_ad.ed.clear_all_events()
    log.info("Bonding device {} to {}".format(
        pri_droid.bluetoothGetLocalAddress(),
        sec_droid.bluetoothGetLocalAddress()))
    sec_droid.bluetoothMakeDiscoverable(bt_default_timeout)
    target_address = sec_droid.bluetoothGetLocalAddress()
    log.debug("Starting paring helper on each device")
    pri_droid.bluetoothStartPairingHelper(auto_confirm)
    sec_droid.bluetoothStartPairingHelper(auto_confirm)
    pri_ad.log.info("Primary device starting discovery and executing bond")
    result = pri_droid.bluetoothDiscoverAndBond(target_address)
    if not auto_confirm:
        if not _wait_for_passkey_match(pri_ad, sec_ad):
            return False
    # Loop until we have bonded successfully or timeout.
    end_time = time.time() + bt_default_timeout
    pri_ad.log.info("Verifying devices are bonded")
    while time.time() < end_time:
        bonded_devices = pri_droid.bluetoothGetBondedDevices()
        bonded = False
        for d in bonded_devices:
            if d['address'] == target_address:
                pri_ad.log.info("Successfully bonded to device")
                return True
        time.sleep(0.1)
    # Timed out trying to bond.
    pri_ad.log.info("Failed to bond devices.")
    return False

def reset_bluetooth(android_devices):
    """Resets Bluetooth state of input Android device list.

    Args:
        android_devices: The Android device list to reset Bluetooth state on.

    Returns:
        True if successful, false if unsuccessful.
    """
    for a in android_devices:
        droid, ed = a.droid, a.ed
        a.log.info("Reset state of bluetooth on device.")
        if droid.bluetoothCheckState() is True:
            droid.bluetoothToggleState(False)
            expected_bluetooth_off_event_name = bluetooth_off
            try:
                ed.pop_event(expected_bluetooth_off_event_name,
                             bt_default_timeout)
            except Exception:
                a.log.error("Failed to toggle Bluetooth off.")
                return False
        # temp sleep for b/17723234
        time.sleep(3)
        if not bluetooth_enabled_check(a):
            return False
    return True

def scan_and_verify_n_advertisements(scn_ad, max_advertisements):
    """Verify that input number of advertisements can be found from the scanning
    Android device.

    Args:
        scn_ad: The Android device to start LE scanning on.
        max_advertisements: The number of advertisements the scanner expects to
        find.

    Returns:
        True if successful, false if unsuccessful.
    """
    test_result = False
    address_list = []
    filter_list = scn_ad.droid.bleGenFilterList()
    scn_ad.droid.bleBuildScanFilter(filter_list)
    scan_settings = scn_ad.droid.bleBuildScanSetting()
    scan_callback = scn_ad.droid.bleGenScanCallback()
    scn_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback)
    start_time = time.time()
    while (start_time + bt_default_timeout) > time.time():
        event = None
        try:
            event = scn_ad.ed.pop_event(scan_result.format(scan_callback),
                                        bt_default_timeout)
        except Empty as error:
            raise BtTestUtilsError(
                "Failed to find scan event: {}".format(error))
        address = event['data']['Result']['deviceInfo']['address']
        if address not in address_list:
            address_list.append(address)
        if len(address_list) == max_advertisements:
            test_result = True
            break
    scn_ad.droid.bleStopBleScan(scan_callback)
    return test_result

def set_bluetooth_codec(android_device,
                        codec_type,
                        sample_rate,
                        bits_per_sample,
                        channel_mode,
                        codec_specific_1=0):
    """Sets the A2DP codec configuration on the AndroidDevice.

    Args:
        android_device (acts.controllers.android_device.AndroidDevice): the
            android device for which to switch the codec.
        codec_type (str): the desired codec type. Must be a key in
            bt_constants.codec_types.
        sample_rate (str): the desired sample rate. Must be a key in
            bt_constants.sample_rates.
        bits_per_sample (str): the desired bits per sample. Must be a key in
            bt_constants.bits_per_samples.
        channel_mode (str): the desired channel mode. Must be a key in
            bt_constants.channel_modes.
        codec_specific_1 (int): the desired bit rate (quality) for LDAC codec.
    Returns:
        bool: True if the codec config was successfully changed to the desired
            values. Else False.
    """
    message = ("Set Android Device A2DP Bluetooth codec configuration:\n"
               "\tCodec: {codec_type}\n"
               "\tSample Rate: {sample_rate}\n"
               "\tBits per Sample: {bits_per_sample}\n"
               "\tChannel Mode: {channel_mode}".format(
                   codec_type=codec_type,
                   sample_rate=sample_rate,
                   bits_per_sample=bits_per_sample,
                   channel_mode=channel_mode))
    android_device.log.info(message)

    # Send SL4A command
    droid, ed = android_device.droid, android_device.ed
    if not droid.bluetoothA2dpSetCodecConfigPreference(
            codec_types[codec_type], sample_rates[str(sample_rate)],
            bits_per_samples[str(bits_per_sample)],
            channel_modes[channel_mode], codec_specific_1):
        android_device.log.warning(
            "SL4A command returned False. Codec was not "
            "changed.")
    else:
        try:
            ed.pop_event(bluetooth_a2dp_codec_config_changed,
                         bt_default_timeout)
        except Exception:
            android_device.log.warning("SL4A event not registered. Codec "
                                       "may not have been changed.")

    # Validate codec value through ADB
    # TODO (aidanhb): validate codec more robustly using SL4A
    command = "dumpsys bluetooth_manager | grep -i 'current codec'"
    out = android_device.adb.shell(command)
    split_out = out.split(": ")
    if len(split_out) != 2:
        android_device.log.warning("Could not verify codec config change "
                                   "through ADB.")
    elif split_out[1].strip().upper() != codec_type:
        android_device.log.error("Codec config was not changed.\n"
                                 "\tExpected codec: {exp}\n"
                                 "\tActual codec: {act}".format(
                                     exp=codec_type, act=split_out[1].strip()))
        return False
    android_device.log.info("Bluetooth codec successfully changed.")
    return True

def set_bt_scan_mode(ad, scan_mode_value):
    """Set Android device's Bluetooth scan mode.

    Args:
        ad: The Android device to set the scan mode on.
        scan_mode_value: The value to set the scan mode to.

    Returns:
        True if successful, false if unsuccessful.
    """
    droid, ed = ad.droid, ad.ed
    if scan_mode_value == bt_scan_mode_types['state_off']:
        disable_bluetooth(droid)
        scan_mode = droid.bluetoothGetScanMode()
        reset_bluetooth([ad])
        if scan_mode != scan_mode_value:
            return False
    elif scan_mode_value == bt_scan_mode_types['none']:
        droid.bluetoothMakeUndiscoverable()
        scan_mode = droid.bluetoothGetScanMode()
        if scan_mode != scan_mode_value:
            return False
    elif scan_mode_value == bt_scan_mode_types['connectable']:
        droid.bluetoothMakeUndiscoverable()
        droid.bluetoothMakeConnectable()
        scan_mode = droid.bluetoothGetScanMode()
        if scan_mode != scan_mode_value:
            return False
    elif (scan_mode_value == bt_scan_mode_types['connectable_discoverable']):
        droid.bluetoothMakeDiscoverable()
        scan_mode = droid.bluetoothGetScanMode()
        if scan_mode != scan_mode_value:
            return False
    else:
        # invalid scan mode
        return False
    return True

def set_device_name(droid, name):
    """Set and check Bluetooth local name on input droid object.

    Args:
        droid: Droid object to set local name on.
        name: the Bluetooth local name to set.

    Returns:
        True if successful, false if unsuccessful.
    """
    droid.bluetoothSetLocalName(name)
    time.sleep(2)
    droid_name = droid.bluetoothGetLocalName()
    if droid_name != name:
        return False
    return True

def set_profile_priority(host_ad, client_ad, profiles, priority):
    """Sets the priority of said profile(s) on host_ad for client_ad"""
    for profile in profiles:
        host_ad.log.info("Profile {} on {} for {} set to priority {}".format(
            profile, host_ad.droid.bluetoothGetLocalName(),
            client_ad.droid.bluetoothGetLocalAddress(), priority.value))
        if bt_profile_constants['a2dp_sink'] == profile:
            host_ad.droid.bluetoothA2dpSinkSetPriority(
                client_ad.droid.bluetoothGetLocalAddress(), priority.value)
        elif bt_profile_constants['headset_client'] == profile:
            host_ad.droid.bluetoothHfpClientSetPriority(
                client_ad.droid.bluetoothGetLocalAddress(), priority.value)
        elif bt_profile_constants['pbap_client'] == profile:
            host_ad.droid.bluetoothPbapClientSetPriority(
                client_ad.droid.bluetoothGetLocalAddress(), priority.value)
        else:
            host_ad.log.error(
                "Profile {} not yet supported for priority settings".format(
                    profile))

def setup_multiple_devices_for_bt_test(android_devices):
    """A common setup routine for Bluetooth on input Android device list.

    Things this function sets up:
    1. Resets Bluetooth
    2. Set Bluetooth local name to random string of size 4
    3. Disable BLE background scanning.
    4. Enable Bluetooth snoop logging.

    Args:
        android_devices: Android device list to setup Bluetooth on.

    Returns:
        True if successful, false if unsuccessful.
    """
    log.info("Setting up Android Devices")
    # TODO: Temp fix for an selinux error.
    for ad in android_devices:
        ad.adb.shell("setenforce 0")
    threads = []
    try:
        for a in android_devices:
            thread = threading.Thread(target=factory_reset_bluetooth,
                                      args=([[a]]))
            threads.append(thread)
            thread.start()
        for t in threads:
            t.join()

        for a in android_devices:
            d = a.droid
            # TODO: Create specific RPC command to instantiate
            # BluetoothConnectionFacade. This is just a workaround.
            d.bluetoothStartConnectionStateChangeMonitor("")
            setup_result = d.bluetoothSetLocalName(generate_id_by_size(4))
            if not setup_result:
                a.log.error("Failed to set device name.")
                return setup_result
            d.bluetoothDisableBLE()
            utils.set_location_service(a, True)
            bonded_devices = d.bluetoothGetBondedDevices()
            for b in bonded_devices:
                a.log.info("Removing bond for device {}".format(b['address']))
                d.bluetoothUnbond(b['address'])
        for a in android_devices:
            a.adb.shell("setprop persist.bluetooth.btsnooplogmode full")
            getprop_result = a.adb.shell(
                "getprop persist.bluetooth.btsnooplogmode") == "full"
            if not getprop_result:
                a.log.warning("Failed to enable Bluetooth Hci Snoop Logging.")
    except Exception as err:
        log.error("Something went wrong in multi device setup: {}".format(err))
        return False
    return setup_result

def setup_n_advertisements(adv_ad, num_advertisements):
    """Setup input number of advertisements on input Android device.

    Args:
        adv_ad: The Android device to start LE advertisements on.
        num_advertisements: The number of advertisements to start.

    Returns:
        advertise_callback_list: List of advertisement callback ids.
    """
    adv_ad.droid.bleSetAdvertiseSettingsAdvertiseMode(
        ble_advertise_settings_modes['low_latency'])
    advertise_data = adv_ad.droid.bleBuildAdvertiseData()
    advertise_settings = adv_ad.droid.bleBuildAdvertiseSettings()
    advertise_callback_list = []
    for i in range(num_advertisements):
        advertise_callback = adv_ad.droid.bleGenBleAdvertiseCallback()
        advertise_callback_list.append(advertise_callback)
        adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data,
                                            advertise_settings)
        try:
            adv_ad.ed.pop_event(adv_succ.format(advertise_callback),
                                bt_default_timeout)
            adv_ad.log.info("Advertisement {} started.".format(i + 1))
        except Empty as error:
            adv_ad.log.error("Advertisement {} failed to start.".format(i + 1))
            raise BtTestUtilsError(
                "Test failed with Empty error: {}".format(error))
    return advertise_callback_list

def take_btsnoop_log(ad, testcase, testname):
    """Grabs the btsnoop_hci log on a device and stores it in the log directory
    of the test class.

    If you want grab the btsnoop_hci log, call this function with android_device
    objects in on_fail. Bug report takes a relative long time to take, so use
    this cautiously.

    Args:
        ad: The android_device instance to take bugreport on.
        testcase: Name of the test calss that triggered this snoop log.
        testname: Name of the test case that triggered this bug report.
    """
    testname = "".join(x for x in testname if x.isalnum())
    serial = ad.serial
    device_model = ad.droid.getBuildModel()
    device_model = device_model.replace(" ", "")
    out_name = ','.join((testname, device_model, serial))
    snoop_path = os.path.join(ad.device_log_path, 'BluetoothSnoopLogs')
    os.makedirs(snoop_path, exist_ok=True)
    cmd = ''.join(("adb -s ", serial, " pull ", btsnoop_log_path_on_device,
                   " ", snoop_path + '/' + out_name, ".btsnoop_hci.log"))
    exe_cmd(cmd)
    try:
        cmd = ''.join(
            ("adb -s ", serial, " pull ", btsnoop_last_log_path_on_device, " ",
             snoop_path + '/' + out_name, ".btsnoop_hci.log.last"))
        exe_cmd(cmd)
    except Exception as err:
        testcase.log.info(
            "File does not exist {}".format(btsnoop_last_log_path_on_device))

def take_btsnoop_logs(android_devices, testcase, testname):
    """Pull btsnoop logs from an input list of android devices.

    Args:
        android_devices: the list of Android devices to pull btsnoop logs from.
        testcase: Name of the test calss that triggered this snoop log.
        testname: Name of the test case that triggered this bug report.
    """
    for a in android_devices:
        take_btsnoop_log(a, testcase, testname)

def teardown_n_advertisements(adv_ad, num_advertisements,
                              advertise_callback_list):
    """Stop input number of advertisements on input Android device.

    Args:
        adv_ad: The Android device to stop LE advertisements on.
        num_advertisements: The number of advertisements to stop.
        advertise_callback_list: The list of advertisement callbacks to stop.

    Returns:
        True if successful, false if unsuccessful.
    """
    for n in range(num_advertisements):
        adv_ad.droid.bleStopBleAdvertising(advertise_callback_list[n])
    return True

def verify_server_and_client_connected(client_ad, server_ad, log=True):
    """Verify that input server and client Android devices are connected.

    This code is under the assumption that there will only be
    a single connection.

    Args:
        client_ad: the Android device to check number of active connections.
        server_ad: the Android device to check number of active connections.

    Returns:
        True both server and client have at least 1 active connection,
        false if unsuccessful.
    """
    test_result = True
    if len(server_ad.droid.bluetoothSocketConnActiveConnections()) == 0:
        if log:
            server_ad.log.error("No socket connections found on server.")
        test_result = False
    if len(client_ad.droid.bluetoothSocketConnActiveConnections()) == 0:
        if log:
            client_ad.log.error("No socket connections found on client.")
        test_result = False
    return test_result

def wait_for_bluetooth_manager_state(droid,
                                     state=None,
                                     timeout=10,
                                     threshold=5):
    """ Waits for BlueTooth normalized state or normalized explicit state
    args:
        droid: droid device object
        state: expected BlueTooth state
        timeout: max timeout threshold
        threshold: list len of bt state
    Returns:
        True if successful, false if unsuccessful.
    """
    all_states = []
    get_state = lambda: droid.bluetoothGetLeState()
    start_time = time.time()
    while time.time() < start_time + timeout:
        all_states.append(get_state())
        if len(all_states) >= threshold:
            # for any normalized state
            if state is None:
                if len(set(all_states[-threshold:])) == 1:
                    log.info("State normalized {}".format(
                        set(all_states[-threshold:])))
                    return True
            else:
                # explicit check against normalized state
                if set([state]).issubset(all_states[-threshold:]):
                    return True
        time.sleep(0.5)
    log.error(
        "Bluetooth state fails to normalize" if state is None else
        "Failed to match bluetooth state, current state {} expected state {}".
        format(get_state(), state))
    return False

def _wait_for_passkey_match(pri_ad, sec_ad):
    pri_pin, sec_pin = -1, 1
    pri_variant, sec_variant = -1, 1
    pri_pairing_req, sec_pairing_req = None, None
    try:
        pri_pairing_req = pri_ad.ed.pop_event(
            event_name="BluetoothActionPairingRequest",
            timeout=bt_default_timeout)
        pri_variant = pri_pairing_req["data"]["PairingVariant"]
        pri_pin = pri_pairing_req["data"]["Pin"]
        pri_ad.log.info("Primary device received Pin: {}, Variant: {}".format(
            pri_pin, pri_variant))
        sec_pairing_req = sec_ad.ed.pop_event(
            event_name="BluetoothActionPairingRequest",
            timeout=bt_default_timeout)
        sec_variant = sec_pairing_req["data"]["PairingVariant"]
        sec_pin = sec_pairing_req["data"]["Pin"]
        sec_ad.log.info(
            "Secondary device received Pin: {}, Variant: {}".format(
                sec_pin, sec_variant))
    except Empty as err:
        log.error("Wait for pin error: {}".format(err))
        log.error("Pairing request state, Primary: {}, Secondary: {}".format(
            pri_pairing_req, sec_pairing_req))
        return False
    if pri_variant == sec_variant == pairing_variant_passkey_confirmation:
        confirmation = pri_pin == sec_pin
        if confirmation:
            log.info("Pairing code matched, accepting connection")
        else:
            log.info("Pairing code mismatched, rejecting connection")
        pri_ad.droid.eventPost("BluetoothActionPairingRequestUserConfirm",
                               str(confirmation))
        sec_ad.droid.eventPost("BluetoothActionPairingRequestUserConfirm",
                               str(confirmation))
        if not confirmation:
            return False
    elif pri_variant != sec_variant:
        log.error("Pairing variant mismatched, abort connection")
        return False
    return True

def write_read_verify_data(client_ad, server_ad, msg, binary=False):
    """Verify that the client wrote data to the server Android device correctly.

    Args:
        client_ad: the Android device to perform the write.
        server_ad: the Android device to read the data written.
        msg: the message to write.
        binary: if the msg arg is binary or not.

    Returns:
        True if the data written matches the data read, false if not.
    """
    client_ad.log.info("Write message.")
    try:
        if binary:
            client_ad.droid.bluetoothSocketConnWriteBinary(msg)
        else:
            client_ad.droid.bluetoothSocketConnWrite(msg)
    except Exception as err:
        client_ad.log.error("Failed to write data: {}".format(err))
        return False
    server_ad.log.info("Read message.")
    try:
        if binary:
            read_msg = server_ad.droid.bluetoothSocketConnReadBinary().rstrip(
                "\r\n")
        else:
            read_msg = server_ad.droid.bluetoothSocketConnRead()
    except Exception as err:
        server_ad.log.error("Failed to read data: {}".format(err))
        return False
    log.info("Verify message.")
    if msg != read_msg:
        log.error("Mismatch! Read: {}, Expected: {}".format(read_msg, msg))
        return False
    return True

class MediaControlOverSl4a(object):
    """Media control using sl4a facade for general purpose.

    """
    def __init__(self, android_device, music_file):
        """Initialize the media_control class.

        Args:
            android_dut: android_device object
            music_file: location of the music file
        """
        self.android_device = android_device
        self.music_file = music_file

    def play(self):
        """Play media.

        """
        self.android_device.droid.mediaPlayOpen('file://%s' % self.music_file,
                                                'default', True)
        playing = self.android_device.droid.mediaIsPlaying()
        asserts.assert_true(playing,
                            'Failed to play music %s' % self.music_file)

    def pause(self):
        """Pause media.

        """
        self.android_device.droid.mediaPlayPause('default')
        paused = not self.android_device.droid.mediaIsPlaying()
        asserts.assert_true(paused,
                            'Failed to pause music %s' % self.music_file)

    def resume(self):
        """Resume media.

        """
        self.android_device.droid.mediaPlayStart('default')
        playing = self.android_device.droid.mediaIsPlaying()
        asserts.assert_true(playing,
                            'Failed to play music %s' % self.music_file)

    def stop(self):
        """Stop media.

        """
        self.android_device.droid.mediaPlayStop('default')
        stopped = not self.android_device.droid.mediaIsPlaying()
        asserts.assert_true(stopped,
                            'Failed to stop music %s' % self.music_file)


from pathlib import Path
import os

def find_max_cals(input):

    cals_elf = 0
    max_cals_elf = 0

    for i in range(len(input)):
        if input[i] == '':
            max_cals_elf = max(max_cals_elf, cals_elf)
            cals_elf = 0

        else:
            cals_elf += int(input[i])

    print(f'The elf carrying the most has {max_cals_elf} calories \n')

def top_three_elfs(input):
    cals_elf = 0
    cals_list = []

    for i in range(len(input)):
        if input[i] == '':
            cals_list.append(cals_elf)
            cals_elf = 0

        else:
            cals_elf += int(input[i])

    cals_list.sort(reverse=True)

    print(f'The top three elfs have {sum(cals_list[:3])} calorie \n')

def main():
    #print(Path(__file__).parents[1])
    #print(os.path.dirname(__file__))
    #os.chdir('..')
    #print(os.path.join(os.getcwd(), "/Data/input_p1.txt"))

    data_path = '/Users/luisrodri/Documents/GitHub/Advent-of-code-2022/Data/day_1_input.txt'

    file = open(data_path)
    input = file.readlines()
    input = [i.rstrip() for i in input]

    print('*PROBLEM 1*')
    find_max_cals(input)

    print('*PROBLEM 2*')
    top_three_elfs(input)

if __name__ == "__main__":
    main()


from exploits.bombs.git import git_bombs
from test.exploits.dummy_output import DummyOutput

def test_git_bombs():
    depths = range(10, 51, 10)

    for d in depths:
        output = DummyOutput()
        git_bombs.options['depth'] = d
        git_bombs.run(output)
        assert len(output) == 1


# -*- coding: utf-8 -*-
"""Provides tools for UPNP searches"""

from MythTV.exceptions import MythError
from MythTV.logging import MythLog

from time import time
import socket

class MSearch( object ):
    """
    Opens a socket for performing UPNP searches.
    """
    def __init__(self):
        self.log = MythLog('Python M-Search')
        port = 1900
        addr = '239.255.255.250'
        self.dest = (addr, port)
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
                             socket.IPPROTO_UDP)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        listening = False
        while listening == False:
            try:
                self.sock.bind(('', port))
                self.addr = (addr, port)
                listening = True
            except socket.error as e:
                if port < 1910:
                    port += 1
                else:
                    raise MythError(MythError.SOCKET, e)
        self.log(MythLog.UPNP|MythLog.SOCKET, MythLog.DEBUG,
                    'Port %d opened for UPnP search.' % port)
        self.sock.setblocking(0)

    def __del__(self):
        self.sock.close()

    def search(self, timeout=5.0, filter=None):
        """
        obj.search(timeout=5.0, filter=None) -> response dicts

          IN:
            timeout  -- in seconds
            filter   -- optional list of ST strings to search for
          OUT:
            response -- a generator returning dicts containing the fields
                content-length,   request,   date,   usn,    location,
                cache-control,    server,    ext,    st
        """
        self.log(MythLog.UPNP, MythLog.DEBUG, 'running UPnP search')

        sock = self.sock
        sreq = '\r\n'.join(['M-SEARCH * HTTP/1.1',
                            'HOST: %s:%s' % self.addr,
                            'MAN: "ssdp:discover"',
                            'MX: %d' % timeout,
                            'ST: ssdp:all',''])
        self._runsearch = True
        # spam the request a couple times
        [sock.sendto(sreq.encode('utf-8'), self.dest) for i in range(3)]

        atime = time()+timeout
        while (time()<atime) and self._runsearch:
            try:
                sdata, saddr = sock.recvfrom(2048)
            except socket.error:
                continue #no data, continue
            sdata = sdata.decode('utf-8')

            lines = sdata.split('\n')
            sdict = {'request':lines[0].strip()}
            for line in lines[1:]:
                fields = line.split(':',1)
                if len(fields) == 2:
                    sdict[fields[0].strip().lower()] = fields[1].strip()

            if ('st' not in sdict) or ('location' not in sdict):
                continue

            if filter:
                if sdict['st'] not in filter:
                    continue

            self.log(MythLog.UPNP, MythLog.DEBUG, sdict['st'], sdict['location'])
            yield sdict

    def searchMythBE(self, timeout=5.0):
        """
        obj.searchMythBE(timeout=5.0) -> response dicts

            Filters responses for those from `mythbackend`.

          IN:
            timeout  -- in seconds
          OUT:
            response -- a generator returning dicts containing the fields
                content-length,   request,   date,   usn,    location,
                cache-control,    server,    ext,    st
        """
        location = []
        for res in self.search(timeout, (\
                'urn:schemas-mythtv-org:device:MasterMediaServer:1',
                'urn:schemas-mythtv-org:device:SlaveMediaServer:1')):
            if res['location'] not in location:
                location.append(res['location'])
                yield res

    def searchMythFE(self, timeout=5.0):
        """
        obj.searchMythFE(timeout=5.0) -> response dicts

            Filters responses for those from `mythfrontend`.

          IN:
            timeout  -- in seconds
          OUT:
            response -- a generator returning dicts containing the fields
                content-length,   request,   date,   usn,    location,
                cache-control,    server,    ext,    st
        """
        location = []
        for res in self.search(timeout, \
                'urn:schemas-upnp-org:device:MediaRenderer:1'):
            if 'MythTV' not in res['server']:
                continue
            if res['location'] not in location:
                location.append(res['location'])
                yield res

    def terminateSearch(self):
        """
        Prematurely terminate an running search prior
            to the specified timeout.
        """
        self._runsearch = False


from waflib.TaskGen import feature

@feature('copy_portaudio')
def feature_copy_portaudio(self):
    platform = self.bld.env['PLATFORM']
    configuration = self.bld.env['CONFIGURATION']

    #for the windows platform
    if platform == 'win_x64' or platform == 'win_x64_vs2015' or platform == 'win_x64_vs2013':
        output_folder = self.bld.get_output_folders(platform, configuration)[0]

        #copy libsamplerate
        sample_source_file = self.bld.CreateRootRelativePath('Gems/' + self.name + '/3rdParty/libsamplerate/bin/libsamplerate-0.dll')
        self.create_task('copy_outputs', self.bld.root.make_node(sample_source_file), output_folder.make_node('libsamplerate-0.dll'))

        #depending on the configuration, copy portaudio
        if configuration == 'debug' or configuration == 'profile':
            port_source_file = self.bld.CreateRootRelativePath('Gems/' + self.name + '/3rdParty/portaudio/bin/Debug/portaudio_x64.dll')
            self.create_task('copy_outputs', self.bld.root.make_node(port_source_file), output_folder.make_node('portaudio_x64.dll'))
        else:
            port_source_file = self.bld.CreateRootRelativePath('Gems/' + self.name + '/3rdParty/portaudio/bin/Release/portaudio_x64.dll')
            self.create_task('copy_outputs', self.bld.root.make_node(port_source_file), output_folder.make_node('portaudio_x64.dll'))

def build(bld):
    bld.DefineGem(
        # Add custom build options here
        features = [ 'copy_portaudio' ],

        includes    = [
            bld.Path('Code/CryEngine/CryAction'),
            bld.path.abspath() + '/../3rdParty/portaudio/include',
            bld.path.abspath() + '/../3rdParty/libsamplerate/include'
        ],

        lib = [
            'portaudio_x64',
            'libsamplerate-0'
        ],

        libpath = [
            bld.path.abspath() + '/../3rdParty/libsamplerate/lib'
        ],

        debug_libpath = [
            bld.path.abspath() + '/../3rdParty/portaudio/lib/Debug'
        ],

        profile_libpath = [
            bld.path.abspath() + '/../3rdParty/portaudio/lib/Debug'
        ],

        performance_libpath = [
            bld.path.abspath() + '/../3rdParty/portaudio/lib/Release'
        ],

        release_libpath = [
            bld.path.abspath() + '/../3rdParty/portaudio/lib/Release'
        ],
        win_features = ['crcfix']
    )


from typing import *
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
import numpy as np
import keras
import tensorflow as tf
import tf_agents
from tf_agents.agents.dqn import dqn_agent
from tf_agents.networks import network
from tf_agents.networks import sequential
from tf_agents.specs.array_spec import BoundedArraySpec
from collections import namedtuple
from model.model_config import ModelConfig
from json import dump

class ResidualModelConfig(ModelConfig):
    def __init__(self,
                 num_residual_layers : int,
                 num_residual_filters : int,
                 residual_kernel_size : int,
                 residual_strides : int,
                 num_filters : int,
                 kernel_size : int,
                 strides : int,
                 input_dims : Tuple[int]):
        super().__init__()
        self.num_residual_layers = num_residual_layers
        self.num_residual_filters = num_residual_filters
        self.num_filters = num_filters
        self.kernel_size = kernel_size
        self.strides = strides
        self.residual_kernel_size = residual_kernel_size
        self.residual_strides = residual_strides
        self.input_dims = input_dims

    def __str__(self):
        s = f'ResidualModelConfig'
        s += f'\n\tFilters: {self.num_filters}'
        s += f'\n\tKernel:  {self.kernel_size}'
        s += f'\n\tStrides: {self.strides}'
        s += f'\n\tRes layers: {self.num_residual_layers}'
        s += f'\n\tResidaul Kernel Sizes: {self.residual_kernel_size}'
        s += f'\n\tResidual strides: {self.residual_strides}'
        s += f'\n\t{self.input_shape}'

        return s

    def save_as_json(self, path : str):
        with open(path, 'w') as f:
            dump({
                "type": self.__class__.__name__,
                'parameters': {
                    "num_residual_layers" : self.num_residual_layers,
                    "num_residual_filters" : self.num_residual_filters,
                    "num_filters" : self.num_filters,
                    "kernel_size" : self.kernel_size,
                    "strides" : self.strides,
                    "residual_kernel_size" : self.residual_kernel_size,
                    "residual_strides" : self.residual_strides,
                    "input_dims" : self.input_dims,
                }
            }, f)

class StureResidualLayer(keras.layers.Layer):
    def __init__(self, num_filters : int, kernel_size : int = 3, strides : int = 1):
        super().__init__()
        self._conv2d1 = tf.keras.layers.Conv2D(
            filters=num_filters,
            kernel_size=kernel_size,
            strides=strides,
            padding='same'
        )
        self._bn1 = tf.keras.layers.BatchNormalization()
        self._relu1 = tf.keras.layers.ReLU()

        self._conv2d2 = tf.keras.layers.Conv2D(
            filters=num_filters,
            kernel_size=kernel_size,
            strides=strides,
            padding='same'
        )
        self._bn2 = tf.keras.layers.BatchNormalization()
        self._relu2 = tf.keras.layers.ReLU()

    def call(self, x):
        x1 = self._conv2d1(x)
        x1 = self._bn1(x1)
        x1 = self._relu1(x1)

        x1 = self._conv2d2(x1)
        x1 = self._bn2(x1)

        x1 = x1 + x
        x1 = self._relu2(x1)
        return x1

class StureValueHead(keras.layers.Layer):
    def __init__(self):
        super().__init__()

        self._conv2d1 = tf.keras.layers.Conv2D(
            filters=1,
            kernel_size=(1, 1),
            strides=(1, 1),
            padding='same'
        )
        self._bn1 = tf.keras.layers.BatchNormalization()
        self._relu1 = tf.keras.layers.ReLU()
        self._flatten = tf.keras.layers.Flatten()

        self._dense1 = tf.keras.layers.Dense(32, activation='relu')
        self._dense2 = tf.keras.layers.Dense(1, activation='tanh')

    def call(self, x):
        x = self._conv2d1(x)
        x = self._bn1(x)
        x = self._relu1(x)
        x = self._flatten(x)

        x = self._dense1(x)
        x = self._dense2(x)
        return x

class SturePolicyHead(tf.keras.layers.Layer):
    def __init__(self):
        super().__init__()
        self._conv2d1 = tf.keras.layers.Conv2D(
            filters=2,
            kernel_size=(1, 1),
            strides=(1, 1),
            padding='valid'
        )
        self._bn1 = tf.keras.layers.BatchNormalization()
        self._relu1 = tf.keras.layers.ReLU()
        self._flatten = tf.keras.layers.Flatten()
        self._dense = tf.keras.layers.Dense(
            4,
            activation=None
        )

    def call(self, x):
        x = self._conv2d1(x)
        x = self._bn1(x)
        x = self._relu1(x)
        x = self._flatten(x)
        x = self._dense(x)
        return x

class StureModel(network.Network):
    def __init__(self,
                 num_residual_layers : int,
                 num_residual_filters : int,
                 num_init_filters : int,
                 input_tensor_spec : tf_agents.specs.ArraySpec,
                 init_kernel_size : int = 3,
                 init_strides : int = 1,
                 residual_kernel_size : int = 5,
                 residual_strides = 1,
                 name : str = 'SigmaSture'):
        super().__init__(name=name,
                         input_tensor_spec=input_tensor_spec)
        self._conv1 = tf.keras.layers.Conv2D(
            filters=num_init_filters,
            kernel_size=init_kernel_size,
            strides=init_strides,
            padding='same'
        )
        self._bn1 = tf.keras.layers.BatchNormalization()
        self._relu1 = tf.keras.layers.ReLU()
        self._residual_layers = [
            StureResidualLayer(num_residual_filters, residual_kernel_size, residual_strides) for _ in range(num_residual_layers)
        ]
        # self._vh = StureValueHead()
        self._ph = SturePolicyHead()

    def call(self, observations, step_type=(), network_state=()):

        x = self._conv1(observations)
        x = self._bn1(x)
        x = self._relu1(x)

        for res_layer in self._residual_layers:
            x = res_layer(x)

        # value = self._vh(x)
        policy = self._ph(x)
        return policy, network_state

if __name__ == '__main__':
    input_tensor_spec = BoundedArraySpec(
        shape=(32, 32, 1), dtype=np.float32,
        minimum=-1, maximum=1
    )

    q_model = StureModel(
        num_residual_layers=3,
        num_filters=64,
        input_tensor_spec=input_tensor_spec
    )
    q_model.create_variables()
    q_model.summary()

    np.random.seed(0)
    a = np.random.uniform(size=(1, 32, 32, 1), low=-1, high=1)
    print(f'Input shape: {a.shape}')
    print(f'Input std:   {np.std(a)}')

    policy, _ = q_model(a)
    print(f'Policy: {policy}')


#===============================================================================
# This file is part of PyManageMC.
#
#    PyManageMC is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 2 of the License, or
#    (at your option) any later version.
#
#    PyManageMC is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with PyManageMC.  If not, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#===============================================================================
'''
Created on Aug 06, 2014

@author: Paulson McIntyre (GpMidi) <paul@gpmidi.net>
'''
# Logging
import logging
log = logging.getLogger("mcprofile.profile")

# Built-in
import json
import hashlib
import urllib, urllib2

# External
import requests
from django.core.cache import get_cache

# Ours

class MinecraftUser(object):
    """
    """
    USER_SESSION_LOOKUP = 'https://sessionserver.mojang.com/session/minecraft/profile/'
    USERNAMES_TO_UUID = 'https://api.mojang.com/profiles/minecraft'
    USERNAMES_TO_UUID_HEADERS = {"Content-Type": "application/json"}
    SESSION_CACHE_TIME_SECONDS = 60

    def __init__(self, uuid=None, username=None):
        assert uuid or username, "Expected either uuid or username to be defined"
        self.cache = get_cache('default')
        self._uuid = uuid
        self._session = None
        if username:
            self._uuid = self.usernameToUUID(username)
        self._username = username

    @property
    def uuid(self):
        assert self._uuid, "Expected a valid UUID for %r" % self
        return self._uuid

    @property
    def username(self):
        if not self._username:
            self._username = self.session['name']
        return self._username

    @property
    def session(self):
        if not self._session:
            self._session = self.getSession(self.uuid)
        return self._session

    def getSession(self, uuid):
        k = self.makeKey("uuid->session", uuid)
        ses = self.cache.get(k)
        if ses:
            return ses
        ses = self.uuidToSession(uuid)
        self.cache.set(k, ses, self.SESSION_CACHE_TIME_SECONDS)
        if ses is None:
            raise ValueError("Couldn't find a session/profile for %r" % uuid)
        return ses

    @classmethod
    def uuidToSession(cls, uuid):
        req = requests.get(cls.USER_SESSION_LOOKUP + urllib.quote(uuid))
        if req.text == '':
            return None
        return req.json()

    @classmethod
    def makeKey(cls, typ, *data):
        h = hashlib.new('SHA1')
        h.update(cls.__class__.__name__)
        h.update(typ)
        for i in data:
            h.update(str(i))
        return h.hexdigest()

    def usernameToUUID(self, username):
        k = self.makeKey("username->uuid", username)
        uuid = self.cache.get(k)
        if uuid:
            return uuid
        for name, uuid in self.usernamesToUUID([username, ]).items():
            if name == username:
                self.cache.set(k, uuid, None)
                return uuid
        raise ValueError("Username %r wasn't found" % username)

    @classmethod
    def usernamesToUUID(cls, usernames):
        req = requests.post(
                            cls.USERNAMES_TO_UUID,
                            headers=cls.USERNAMES_TO_UUID_HEADERS,
                            data=json.JSONEncoder().encode(usernames),
                            )

        ret = {}
        for info in req.json():
            ret[info['name']] = info['id']

        return ret


def solution(arr):
    square = 1
    high = max(arr)
    l = len(arr)
    position = arr.index(high)
    m = min(l, high)
    i = m + 1
    while i >= 2:
        j = 0
        while j < l:
            sub = arr[j:j + i]
            if min(sub) >= len(sub) > square:
                square = len(sub)

            j += 1
        i -= 1

    return square


import gym
import numpy as np
from drlfads.GMM.gmm import GMM
from drlfads.Soft_Actor_Critic.sac_agent import SAC_Agent

class SAC_GMM_Agent(SAC_Agent):
    def __init__(self, model, window_size=32, *args, **kwargs):
        self.initial_model = model      # Initial model provided
        self.model = GMM()
        self.model.copy_model(self.initial_model)    # Model used for training
        self.window_size = window_size
        self.burn_in_steps = 1000
        super(SAC_GMM_Agent, self).__init__(*args, **kwargs)

    def get_action_space(self):
        if not hasattr(self, 'action_space'):
            priors_high = np.ones(self.model.priors.size)
            mu_high = np.ones(self.model.mu.size)
            action_high = np.concatenate((priors_high, mu_high), axis=-1)
            action_low = - action_high
            self.action_space = gym.spaces.Box(action_low, action_high)
        return self.action_space

    def update_gaussians(self, gmm_change):
        # change of priors range: [-0.1, 0.1]
        priors = gmm_change[:self.model.priors.size]
        priors = priors.reshape(self.model.priors.shape) * 0.1
        # change of mus range: [-0.01, 0.01]
        mu = gmm_change[self.model.priors.size:]
        mu = mu.reshape(self.model.mu.shape) * 0.01
        change_dict = {"mu":mu, "prior":priors}
        self.model.update_gaussians(change_dict)

    def evaluate(self, num_episodes = 5, render=False):
        succesful_episodes, episodes_returns, episodes_lengths = 0, [], []
        for episode in range(1, num_episodes + 1):
            observation = self.env.reset()
            episode_return, episode_length, left_steps = 0, 0, self.env.max_episode_steps
            while left_steps > 0:
                self.model.copy_model(self.initial_model)
                gmm_change = self.get_action_from_observation(observation, deterministic = True)
                self.update_gaussians(gmm_change)
                model_reward = 0
                for step in range(self.window_size):
                    vel = self.model.predict_velocity_from_observation(observation)
                    observation, reward, done, info = self.env.step(vel)
                    model_reward += reward
                    episode_length += 1
                    left_steps -= 1
                    if render:
                        self.env.render()
                    if done or left_steps <= 0:
                        break
                episode_return += model_reward
                if done:
                    break
                if render:
                    self.env.render()
            if ("success" in info) and info['success']:
                succesful_episodes += 1
            episodes_returns.append(episode_return)
            episodes_lengths.append(episode_length)
        accuracy = succesful_episodes/num_episodes
        return accuracy, np.mean(episodes_returns), np.mean(episodes_lengths)

    def train_episode(self, episode, exploration_episodes, log, render):
        sac_steps = 0
        episode_return, episode_length, left_steps = 0, 0, self.env.max_episode_steps
        ep_critic_loss, ep_actor_loss, ep_alpha_loss = 0, 0, 0
        observation = self.env.reset()
        while left_steps > 0:
            self.model.copy_model(self.initial_model)
            if self.training_step < self.burn_in_steps:
                gmm_change = np.zeros(self.action_space.shape)
            else:
                gmm_change = self.get_action_from_observation(observation, deterministic = False)
            self.update_gaussians(gmm_change)
            model_reward = 0
            curr_observation = observation
            for step in range(self.window_size):
                vel = self.model.predict_velocity_from_observation(curr_observation)
                curr_observation, reward, done, info = self.env.step(vel)
                model_reward += reward
                episode_length += 1
                left_steps -= 1
                if render:
                    self.env.render()
                if done or left_steps <= 0:
                    break
            critic_loss, actor_loss, alpha_loss = self.update(observation, gmm_change, curr_observation,
                                                                model_reward, done, log)
            observation = curr_observation
            episode_return += model_reward
            ep_critic_loss += critic_loss
            ep_actor_loss += actor_loss
            ep_alpha_loss += alpha_loss
            self.training_step += 1 # SAC_Steps in total
            sac_steps += 1 # SAC_Steps in this episode

            if render:
                self.env.render()
            if done:
                break

        if log:
            self.log_scalar('Train/Episode/critic_loss', ep_critic_loss/sac_steps, episode)
            self.log_scalar('Train/Episode/actor_loss', ep_actor_loss/sac_steps, episode)
            self.log_scalar('Train/Episode/alpha_loss', ep_alpha_loss/sac_steps, episode)
            self.log_episode_information(episode_return, episode_length, episode, "Train")

        return episode_return, episode_length


class Resource:
    def __init__(self, value, expired):
        self.value = value
        self.expired = expired

INT_MAX = 0x7fffffff

class Memcache:

    def __init__(self):
        # initialize your data structure here.
        self.client = {}

    # @param {int} curtTime an integer
    # @param {int} key an integer
    # @return an integer
    def get(self, curtTime, key):
        # Write your code here
        if key not in self.client:
            return INT_MAX
        res = self.client.get(key)
        if res.expired >= curtTime or res.expired == -1:
            return res.value
        return INT_MAX

    # @param {int} curtTime an integer
    # @param {int} key an integer
    # @param {int} integer
    # @param {int} ttl an integer
    # @return nothing
    def set(self, curtTime, key, value, ttl):
        # Write your code here
        if ttl:
            res = Resource(value, curtTime + ttl - 1)
        else:
            res = Resource(value, -1)
        self.client[key] = res

    # @param {int} curtTime an integer
    # @param {int} key an integer
    # @return nothing
    def delete(self, curtTime, key):
        # Write your code here
        if key not in self.client:
            return
        del self.client[key]

    # @param {int} curtTime an integer
    # @param {int} key an integer
    # @param {int} delta an integer
    # @return an integer
    def incr(self, curtTime, key, delta):
        # Write your code here
        if key not in self.client:
            return INT_MAX

        self.client[key].value += delta
        return self.get(curtTime, key)

    # @param {int} curtTime an integer
    # @param {int} key an integer
    # @param {int} delta an integer
    # @return an integer
    def decr(self, curtTime, key, delta):
        # Write your code here
        if key not in self.client:
            return INT_MAX
        self.client[key] -= delta
        return self.get(curtTime, key)


from django.urls import path
from . import views

# this like app.use() in express
urlpatterns = [
    path('', views.home, name='home'),
    path('products/', views.Products_Index.as_view(), name='products_index'),
    path('products/womens', views.Products_Womens.as_view(), name='products_womens'),
    path('products/mens', views.Products_Mens.as_view(), name='products_mens'),
    path('products/accessories', views.Products_Accessories.as_view(), name='products_accessories'),
    path('products/create', views.product_create, name="product_create"),
    # path('products/cart/', views.cart, name='cart'),
    path('products/cart/', views.Cart.as_view(), name='cart'),
    # path('products/<slug>', views.product_show, name='product_show'),
    path('products/<slug>', views.Product_Show.as_view(), name='product_show'),
    path('products/<slug>/update', views.Product_Update.as_view(), name='product_update'),
    path('products/<slug>/delete', views.Product_Delete.as_view(), name='product_delete'),

    path('login/', views.login_view, name='login'),
    path('logout/', views.logout_view, name='logout'),
    path('signup/', views.signup_view, name='signup'),
    path('user/<username>', views.profile, name="profile"),
    path('user/<int:pk>/update', views.Profile_Update.as_view(), name="profile_update"),

    path('add_to_cart/<slug>/', views.add_to_cart, name='add_to_cart'),
    path('remove_all_cart/<slug>/', views.remove_all_cart, name='remove_all_cart'),
    path('remove_item_from_cart/<slug>/', views.remove_item_from_cart, name='remove_item_from_cart'),

]