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

import time

from acts import base_test
from acts import asserts
from acts.controllers.rohdeschwarz_lib import contest
from acts_contrib.test_utils.tel import tel_test_utils
from acts.metrics.loggers import blackbox

import json


class AGNSSPerformanceTest(base_test.BaseTestClass):

    # User parameters defined in the ACTS config file

    TESTPLAN_KEY = '{}_testplan'
    CONTEST_IP_KEY = 'contest_ip'
    REMOTE_SERVER_PORT_KEY = 'remote_server_port'
    AUTOMATION_PORT_KEY = 'automation_port'
    CUSTOM_FILES_KEY = 'custom_files'
    AUTOMATION_LISTEN_IP = 'automation_listen_ip'
    FTP_USER_KEY = 'ftp_user'
    FTP_PASSWORD_KEY = 'ftp_password'

    def __init__(self, controllers):
        """ Initializes class attributes. """

        super().__init__(controllers)

        self.dut = None
        self.contest = None
        self.testplan = None
        self.thresholds_file = None

        self.ttf_metric = blackbox.BlackboxMetricLogger.for_test_case(
            metric_name='ttf')
        self.pos_error_metric = blackbox.BlackboxMetricLogger.for_test_case(
            metric_name='pos_error')
        self.sensitivity_metric = blackbox.BlackboxMetricLogger.for_test_case(
            metric_name='sensitivity')

    def setup_class(self):
        """ Executed before any test case is started. Initializes the Contest
        controller and prepares the DUT for testing. """

        req_params = [
            self.CONTEST_IP_KEY, self.REMOTE_SERVER_PORT_KEY,
            self.AUTOMATION_PORT_KEY, self.AUTOMATION_LISTEN_IP,
            self.FTP_USER_KEY, self.FTP_PASSWORD_KEY
        ]

        for param in req_params:
            if param not in self.user_params:
                self.log.error('Required parameter {} is missing in config '
                               'file.'.format(param))
                return False

        contest_ip = self.user_params[self.CONTEST_IP_KEY]
        remote_port = self.user_params[self.REMOTE_SERVER_PORT_KEY]
        automation_port = self.user_params[self.AUTOMATION_PORT_KEY]
        listen_ip = self.user_params[self.AUTOMATION_LISTEN_IP]
        ftp_user = self.user_params[self.FTP_USER_KEY]
        ftp_password = self.user_params[self.FTP_PASSWORD_KEY]
        custom_files = self.user_params.get(self.CUSTOM_FILES_KEY, [])

        self.dut = self.android_devices[0]

        self.contest = contest.Contest(logger=self.log,
                                       remote_ip=contest_ip,
                                       remote_port=remote_port,
                                       automation_listen_ip=listen_ip,
                                       automation_port=automation_port,
                                       dut_on_func=self.set_apm_off,
                                       dut_off_func=self.set_apm_on,
                                       ftp_usr=ftp_user,
                                       ftp_pwd=ftp_password)

        # Look for the threshold files
        for file in custom_files:
            if 'pass_fail_threshold_' + self.dut.model in file:
                self.thresholds_file = file
                self.log.debug('Threshold file loaded: ' + file)
                break
        else:
            self.log.warning('No threshold files found in custom files.')

    def teardown_class(self):
        """ Executed after completing all selected test cases."""
        if self.contest:
            self.contest.destroy()

    def setup_test(self):
        """ Executed before every test case.

        Returns:
            False if the setup failed.
        """

        testplan_formatted_key = self.TESTPLAN_KEY.format(self.test_name)

        if testplan_formatted_key not in self.user_params:
            self.log.error('Test plan not indicated in the config file. Use '
                           'the {} key to set the testplan filename.'.format(
                               testplan_formatted_key))
            return False

        self.testplan = self.user_params[testplan_formatted_key]

    def agnss_performance_test(self):
        """ Executes the aGNSS performance test and verifies that the results
        are within the expected values if a thresholds file is available.

        The thresholds file is in json format and contains the metrics keys
        defined in the Contest object with 'min' and 'max' values. """

        results = self.contest.execute_testplan(self.testplan)

        asserts.assert_true(
            results, 'No results were obtained from the test execution.')

        if not self.thresholds_file:
            self.log.info('Skipping pass / fail check because no thresholds '
                          'file was provided.')
            return

        passed = True

        with open(self.thresholds_file, 'r') as file:

            thresholds = json.load(file)

            for key, val in results.items():

                asserts.assert_true(
                    key in thresholds, 'Key {} is missing in '
                    'the thresholds file.'.format(key))

                # If the result is provided as a dictionary, obtain the value
                # from the 'avg' key.
                if isinstance(val, dict):
                    metric = val['avg']
                else:
                    metric = val

                if thresholds[key]['min'] < metric < thresholds[key]['max']:
                    self.log.info('Metric {} = {} is within the expected '
                                  'values.'.format(key, metric))
                else:
                    self.log.error('Metric {} = {} is not within ({}, '
                                   '{}).'.format(key, metric,
                                                 thresholds[key]['min'],
                                                 thresholds[key]['max']))
                    passed = False

                # Save metric to Blackbox logger
                if key == contest.Contest.TTFF_KEY:
                    self.ttf_metric.metric_value = metric
                elif key == contest.Contest.POS_ERROR_KEY:
                    self.pos_error_metric.metric_value = metric
                elif key == contest.Contest.SENSITIVITY_KEY:
                    self.sensitivity_metric.metric_value = metric

        asserts.assert_true(
            passed, 'At least one of the metrics was not '
            'within the expected values.')

    def set_apm_on(self):
        """ Wrapper method to turn airplane mode on.

        This is passed to the Contest object so it can be executed when the
        automation system requires the DUT to be set to 'off' state.
        """

        tel_test_utils.toggle_airplane_mode(self.log, self.dut, True)

    def set_apm_off(self):
        """ Wrapper method to turn airplane mode off.

        This is passed to the Contest object so it can be executed when the
        automation system requires the DUT to be set to 'on' state.
        """
        # Wait for the Contest system to initialize the base stations before
        # actually setting APM off.
        time.sleep(5)

        tel_test_utils.toggle_airplane_mode(self.log, self.dut, False)

    def test_agnss_performance(self):
        self.agnss_performance_test()
