#    Copyright 2018 SAS Project Authors. All Rights Reserved.
#
#    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.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from concurrent.futures import ThreadPoolExecutor
import json
import numbers
import time
import os
from functools import partial
import logging

import six
from six import string_types as basestring
from six.moves import zip

from reference_models.common import mpool
import sas
import sas_testcase
import test_harness_objects
from full_activity_dump import FullActivityDump
from full_activity_dump_helper import getFullActivityDumpSasTestHarness, getFullActivityDumpSasUut
import common_strings
from util import winnforum_testcase, configurable_testcase, getCertificateFingerprint, writeConfig, \
        loadConfig, makePpaAndPalRecordsConsistent, getFqdnLocalhost, \
        getUnusedPort, getCertFilename, json_load
from sas_test_harness import SasTestHarnessServer, generateCbsdRecords, \
        generatePpaRecords, generateCbsdReferenceId
from reference_models.dpa import dpa_mgr
from reference_models.common import data
from reference_models.pre_iap_filtering import pre_iap_filtering
from reference_models.iap import iap
from reference_models.interference import aggregate_interference, interference
from reference_models.geo import utils as geoutils
from reference_models.geo import drive


DELTA_IAP = 1 # Threshold value in dBm
ONE_MHZ = 1000000
LOW_FREQUENCY_LIMIT_HZ = 3550000000

class McpXprCommonTestcase(sas_testcase.SasTestCase):

  def getEmptyCbsdRequestsWithDomainProxies(self, number_of_elements):
    empty_cbsd_records_domain_proxy = {
        'registrationRequests': [],
        'grantRequests': [],
        'conditionalRegistrationData': []
    }
    return [empty_cbsd_records_domain_proxy] * number_of_elements

  def checkMcpConfig(self, config, test_type):
    self.assertIn(test_type, ('MCP', 'xPR1', 'xPR2'))

    def checkFadConfiguration(fad_config):
      self.assertValidConfig(fad_config, {
          'cbsdRecords': list
      })

    self.assertValidConfig(
        config, {
            'initialCbsdRequestsWithDomainProxies': list,
            'initialCbsdRecords': list,
            'iterationData': list,
            'sasTestHarnessConfigs': list,
            'domainProxyConfigs': list
        }, {'dpas': dict})

    # Check each iteration's data.
    for iteration_data in config['iterationData']:
      self.assertValidConfig(
          iteration_data, {
              'cbsdRequestsWithDomainProxies': list,
              'cbsdRecords': list,
              'protectedEntities': dict,
              'dpaActivationList': list,
              'dpaDeactivationList': list,
              'sasTestHarnessData': list
          })

      self.assertEqual(
          len(iteration_data['cbsdRequestsWithDomainProxies']),
          len(config['domainProxyConfigs']),
          'Mismatch in the number of domain proxies and the configuration for this iteration: %s.'
          % str(iteration_data))
      self.assertEqual(
          len(config['initialCbsdRequestsWithDomainProxies']),
          len(config['domainProxyConfigs']),
          'Mismatch in the number of domain proxies and the configuration of initial CbsdRequests')
      for domain_proxy in iteration_data['cbsdRequestsWithDomainProxies']:
        self.assertValidConfig(
            domain_proxy, {
                'registrationRequests': list,
                'grantRequests': list,
                'conditionalRegistrationData': list
            })

      for cbsd_record in iteration_data['cbsdRecords']:
        self.assertValidConfig(
            cbsd_record, {
                'registrationRequest': dict,
                'grantRequest': dict,
                'clientCert': basestring,
                'clientKey': basestring
            }, {'conditionalRegistrationData': dict})
        if 'conditionalRegistrationData' in cbsd_record:
          self.assertIn('fccId', cbsd_record['conditionalRegistrationData'])
          self.assertIn('cbsdSerialNumber',
                        cbsd_record['conditionalRegistrationData'])

      self.assertValidConfig(
          iteration_data['protectedEntities'],
          {},
          {
              # All fields are optional.
              'palRecords': list,
              'ppaRecords': list,
              'escRecords': list,
              'gwpzRecords': list,
              'gwblRecords': list,
              'fssRecords': list,
          })
      if 'dpas' in config:
        for dpa in iteration_data['dpaActivationList'] + iteration_data['dpaDeactivationList']:
          self.assertValidConfig(dpa, {
              'dpaId': basestring,
              'frequencyRange': dict
          })

      self.assertEqual(
          len(iteration_data['sasTestHarnessData']),
          len(config['sasTestHarnessConfigs']),
          'Mismatch in the number of SAS test harnesses and the configuration for this iteration: %s.'
          % str(iteration_data))
      for sas_test_harness_data in iteration_data['sasTestHarnessData']:
        checkFadConfiguration(sas_test_harness_data)

    # Check SAS test harnesses.
    for sas_test_harness_config in config['sasTestHarnessConfigs']:
      self.assertValidConfig(
          sas_test_harness_config, {
              'sasTestHarnessName': basestring,
              'hostName': basestring,
              'port': int,
              'serverCert': basestring,
              'serverKey': basestring,
              'caCert': basestring
          })

    # Check domain proxies.
    for domain_proxy_config in config['domainProxyConfigs']:
      self.assertValidConfig(domain_proxy_config, {
          'cert': basestring,
          'key': basestring
      })

    # Special xPR-only checks.
    if test_type == 'MCP':
      return

    # Max. 1 iteration.
    self.assertEqual(
        len(config['iterationData']), 1,
        'XPR test cases only have one iteration.')

    # No DPAs.
    if 'dpas' in config:
      self.assertEqual(
          len(config['dpas']), 0,
          'XPR does not permit the use of DPAs.')

    # Max. one protected entity.
    protected_entities = config['iterationData'][0]['protectedEntities']

    def getNumberOfEntities(entity_type):
      if entity_type not in protected_entities:
        return 0
      else:
        return len(protected_entities[entity_type])

    if getNumberOfEntities('ppaRecords') > 0 or getNumberOfEntities(
        'palRecords') > 0:
      self.assertGreaterEqual(
          getNumberOfEntities('ppaRecords'), 1,
          'Must define at least one PPA record.')
      self.assertGreaterEqual(
          getNumberOfEntities('palRecords'), 1,
          'Must define at least one PAL record if a PPA is to be injected.')
      for entity_type in [
          'escRecords', 'gwpzRecords', 'gwblRecords', 'fssRecords'
      ]:
        self.assertEqual(
            getNumberOfEntities(entity_type), 0,
            'May not define any additional protected entities in an xPR test case; found extra entities in "%s".'
            % entity_type)
    else:
      entity_count = 0
      for entity_type in [
          'escRecords', 'gwpzRecords', 'gwblRecords', 'fssRecords'
      ]:
        entity_count += getNumberOfEntities(entity_type)
      self.assertEqual(
          entity_count, 1,
          'Must define exactly one protected entity for an xPR test case.')

    if test_type == 'xPR1':
      # No SAS test harnesses.
      self.assertEqual(
          len(config['sasTestHarnessConfigs']), 0,
          'xPR.1 does not permit the use of SAS Test Harnesses.')

  def executeMcpTestSteps(self, config, test_type):
    """Execute all teststeps for MCP testcase and for xPR testcases till Step22

    Args:
      config: Testcase configuration
      test_type: A string which indicates the type of testcase to be invoked("MCP"/"xPR1"/"xPR2")
    """
    self.checkMcpConfig(config, test_type)

    # Initialize test-wide variables, and state variables.
    self.config = config
    self.active_dpas = []
    self.test_type = test_type
    self.sas_test_harness_objects = []
    self.domain_proxy_objects = []
    self.protected_entity_records = {}
    self.num_peer_sases = len(config['sasTestHarnessConfigs'])
    self.cpas_executor = ThreadPoolExecutor(max_workers=1)
    self.agg_interf_check_executor = ThreadPoolExecutor(max_workers=1)
    logging.info('Running test type "%s" with %d SAS test harnesses.',
                 self.test_type, self.num_peer_sases)
    self.sas_uut_fad = None
    self.test_harness_fads = []
    self.all_dpa_checks_succeeded = True

    logging.info('Creating domain proxies.')
    for domain_proxy in config['domainProxyConfigs']:
      self.domain_proxy_objects.append(test_harness_objects.DomainProxy(
          self,
          domain_proxy['cert'],
          domain_proxy['key']))

    if test_type == 'MCP':
      # Step 1: Load DPAs
      logging.info('Step 1: load DPAs')
      self._sas_admin.TriggerLoadDpas()

      # Step 2: ESC informs SAS about inactive DPAs
      logging.info('Step 2: deactivate all DPAs')
      self._sas_admin.TriggerBulkDpaActivation({'activate': False})
    else:
      logging.info('Skipping steps 1 and 2 because this is an xPR test.')

    # Step 3: creates multiple SAS THs.
    logging.info('Step 3: create %d SAS test harnesses' % self.num_peer_sases)
    if self.num_peer_sases:
      for test_harness in config['sasTestHarnessConfigs']:
        logging.info('Creating SAS TH with config %s', test_harness)

        # Initialize SAS Test Harness Server instance to dump FAD records
        sas_test_harness_object = SasTestHarnessServer(test_harness['sasTestHarnessName'],
                                                             test_harness['hostName'],
                                                             test_harness['port'],
                                                             test_harness['serverCert'],
                                                             test_harness['serverKey'],
                                                             test_harness['caCert'])
        # Start the server
        sas_test_harness_object.start()

        # informing SAS UUT about SAS Test Harnesses
        certificate_hash = getCertificateFingerprint(test_harness['serverCert'])
        self._sas_admin.InjectPeerSas({'certificateHash': certificate_hash,
                                       'url': sas_test_harness_object.getBaseUrl()})

        self.sas_test_harness_objects.append(sas_test_harness_object)

      # Set the cert/key to use when requesting FADs from SAS UUT.
      self.fad_cert = config['sasTestHarnessConfigs'][0]['serverCert']
      self.fad_key = config['sasTestHarnessConfigs'][0]['serverKey']

    # Step 4: Register + Grant.
    logging.info('Step 4: Register + Grant initial CBSDs.')
    self.registerAndGrantCbsds(config['initialCbsdRequestsWithDomainProxies'],
                               config['initialCbsdRecords'])

    # Step 5: Complete each iteration of the testcase.
    for iteration_number, iteration_content in enumerate(config['iterationData']):
      logging.info('Step 5: execute iteration number %d', iteration_number)
      # Execute steps for single iteration
      self.executeSingleMCPIteration(iteration_content)

    # Fail now (at the end) if any DPA check failed anywhere in the test.
    self.assertTrue(
        self.all_dpa_checks_succeeded,
        'At least one DPA check failed; please see logs for details.')

    # Stop test harness servers.
    for test_harness in self.sas_test_harness_objects:
      test_harness.shutdown()
      del test_harness

    self.cpas_executor.shutdown()
    self.agg_interf_check_executor.shutdown()

  def executeSingleMCPIteration(self, iteration_content):
    """Executes the steps from Step 6 to [end] for MCP and XPR testcases

    Args:
      iteration_content: A dictionary with multiple key-value pairs that contain iteration data
    """
    # Step 6: Inject protected entities into UUT
    logging.info('Step 6: inject new protected entities into SAS UUT')
    if 'fssRecords' in iteration_content['protectedEntities']:
      logging.info('Injecting FSS records.')
      for index, fss_record in enumerate(iteration_content['protectedEntities']['fssRecords']):
        try:
          logging.info('Injecting FSS record #%d', index)
          self._sas_admin.InjectFss(fss_record)
        except Exception:
          logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
          raise

    if 'gwblRecords' in iteration_content['protectedEntities']:
      logging.info('Injecting GWBL records.')
      for index, gwbl_record in enumerate(iteration_content['protectedEntities']['gwblRecords']):
        try:
          logging.info('Injecting GWBL record #%d', index)
          self._sas_admin.InjectWisp(gwbl_record)
        except Exception:
          logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
          raise

    if 'gwpzRecords' in iteration_content['protectedEntities']:
      logging.info('Injecting GWPZ records.')
      for index, gwpz_record in enumerate(iteration_content['protectedEntities']['gwpzRecords']):
        try:
          logging.info('Injecting GWPZ record #%d', index)
          self._sas_admin.InjectWisp(gwpz_record)
        except Exception:
          logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
          raise
        grid_points = geoutils.GridPolygon(
            gwpz_record['zone']['features'][0]['geometry'], res_arcsec=1)
        gwpz_record['landCategory'] = drive.nlcd_driver.RegionNlcdVote(
            [(pt[1], pt[0]) for pt in grid_points])
        logging.info('Land category: %s', gwpz_record['landCategory'])

    if 'escRecords' in iteration_content['protectedEntities']:
      logging.info('Injecting ESC records.')
      for index, esc_record in enumerate(iteration_content['protectedEntities']['escRecords']):
        try:
          logging.info('Injecting ESC record #%d', index)
          self._sas_admin.InjectEscSensorDataRecord({'record': esc_record})
        except Exception:
          logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
          raise

    if 'palRecords' in iteration_content['protectedEntities']:
      logging.info('Injecting PAL records.')
      for index, pal_record in enumerate(iteration_content['protectedEntities']['palRecords']):
        try:
          logging.info('Injecting PAL record #%d', index)
          self._sas_admin.InjectPalDatabaseRecord(pal_record)
        except Exception:
          logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
          raise

    if 'ppaRecords' in iteration_content['protectedEntities']:
      logging.info('Injecting PPA records.')
      for index, ppa_record in enumerate(iteration_content['protectedEntities']['ppaRecords']):
        try:
          logging.info('Injecting PPA record #%d', index)
          self._sas_admin.InjectZoneData({'record': ppa_record})
        except Exception:
          logging.error(common_strings.CONFIG_ERROR_SUSPECTED)
          raise

    for key in iteration_content['protectedEntities']:
      if not key in self.protected_entity_records:
        self.protected_entity_records[key] = iteration_content[
            'protectedEntities'][key]
      else:
        self.protected_entity_records[key].extend(
            iteration_content['protectedEntities'][key])

    # Steps 7, 8: Creating FAD Object and Pull FAD records from SAS UUT
    logging.info('Steps 7 + 8: retrieve FAD from SAS UUT.')
    if self.num_peer_sases:
      logging.info('Retrieving SAS UUT FAD.')
      self.sas_uut_fad = getFullActivityDumpSasUut(self._sas,
                                                 self._sas_admin,
                                                 self.fad_cert,
                                                 self.fad_key)

    # Step 9: load SAS test harnesses and have the main test harness retrieve
    # those FADs for use in the reference models.
    if self.num_peer_sases:
      # Configure SAS Test Harnesses with new FAD information.
      logging.info('Step 9: re-configuring SAS test harnesses.')
      for test_harness, test_harness_data in zip(
          self.sas_test_harness_objects,
          iteration_content['sasTestHarnessData']):
        self.InjectTestHarnessFccIds(test_harness_data['cbsdRecords'])
        test_harness.writeFadRecords([test_harness_data['cbsdRecords']])

      # Pull FAD from SAS Test Harnesses; a FAD object is created for each SAS Test Harness.
      logging.info('Collecting SAS TH FADs.')
      self.test_harness_fads = []
      for test_harness in self.sas_test_harness_objects:
        self.test_harness_fads.append(
            getFullActivityDumpSasTestHarness(
                test_harness.getSasTestHarnessInterface()))

    # Step 10: Trigger CPAS and wait until completion.
    logging.info('Step 10: Triggering CPAS.')
    self.cpas = self.cpas_executor.submit(self.TriggerDailyActivitiesImmediatelyAndWaitUntilComplete)

    if self.num_peer_sases:
      # Step 11: Invoke IAP reference model
      logging.info('Step 11: Performing pre-IAP filtering.')
      pre_iap_filtering.preIapReferenceModel(self.protected_entity_records,
                                     self.sas_uut_fad, self.test_harness_fads)
      logging.info('Step 11: Performing IAP.')
      self.performIap()

    # Step 12: Invoke DPA ML reference model for currently-active and
    # will-be-active DPAs.
    logging.info('Step 12: invoke the DPA ML reference model.')
    self.dpa_managers = {}
    self.dpa_margins = {}
    for active_dpa in self.active_dpas + iteration_content['dpaActivationList']:
      dpa_config = self.config['dpas'][active_dpa['dpaId']]
      dpa = dpa_mgr.BuildDpa(active_dpa['dpaId'], dpa_config['points_builder'])
      low_freq_mhz = active_dpa['frequencyRange']['lowFrequency'] // ONE_MHZ
      high_freq_mhz = active_dpa['frequencyRange']['highFrequency'] // ONE_MHZ
      dpa.ResetFreqRange([(low_freq_mhz, high_freq_mhz)])
      if self.num_peer_sases:
        logging.info('Calculating  move list for DPA: %s', active_dpa)
        dpa.SetGrantsFromFad(self.sas_uut_fad, self.test_harness_fads)
        dpa.ComputeMoveLists()

      dpa_combined_id = '%s,%s,%s' % (
          active_dpa['dpaId'], active_dpa['frequencyRange']['lowFrequency'],
          active_dpa['frequencyRange']['highFrequency'])
      self.dpa_managers[dpa_combined_id] = dpa
      self.dpa_margins[dpa_combined_id] = dpa_config['movelistMargin']

    logging.info('Waiting for CPAS to complete (Started in step 10).')
    self.cpas.result()
    logging.info('CPAS started in step 10 complete.')

    # Steps 13, 14, 15, and 16: send heartbeat request for the grants,
    # relinquish the grant, grant request and heartbeat for new grants.
    logging.info('Steps 13 - 16: heartbeat, relinquish, grant, heartbeat.')
    for index, domain_proxy_object in enumerate(self.domain_proxy_objects):
      logging.info('Working on domain proxy %d.', index)
      domain_proxy_object.performHeartbeatAndUpdateGrants()

    # Steps 17, 18, and CHECK
    logging.info('Steps 17, 18, and CHECK: calculating reference DPA move list,'
                 ' performing DPA aggregate interference check and performing'
                 ' aggregate interference check.')
    self.performIapAndDpaChecks()

    # Step 19: DP Test Harnesses register N(2,k) CBSDs with SAS UUT.
    logging.info('Step 19: registering and granting new CBSDs.')
    self.registerAndGrantCbsds(
        iteration_content['cbsdRequestsWithDomainProxies'],
        iteration_content['cbsdRecords'])

    # Step 20: Send heartbeat request for all CBSDs managed by SAS UUT.
    logging.info('Step 20: heartbeating all CBSDs.')
    for index, domain_proxy_object in enumerate(self.domain_proxy_objects):
      logging.info('Heartbeating for domain proxy %d.', index)
      domain_proxy_object.heartbeatForAllActiveGrants()

    # Step 21: ESC Test harness deactivates previously-activated DPAs.
    logging.info('Step 21: activating and deactivating DPAs.')
    for dpa in iteration_content['dpaActivationList']:
      if dpa['frequencyRange']['lowFrequency'] < LOW_FREQUENCY_LIMIT_HZ:
        logging.warning('DPA %s is always-active in frequency, so there is no need to activate.', dpa['dpaId'])
        if dpa not in self.active_dpas:
            self.active_dpas.append(dpa)
        continue
    
      if dpa in self.active_dpas:
        logging.warning('DPA is already active, skipping activation: %s', dpa)
        continue

      logging.info('Activating: %s', dpa)
      self._sas_admin.TriggerDpaActivation(dpa)
      self.active_dpas.append(dpa)
    for dpa in iteration_content['dpaDeactivationList']:
      if dpa['frequencyRange']['lowFrequency'] < LOW_FREQUENCY_LIMIT_HZ:
        logging.warning('DPA %s is always-active in frequency, so there is no need to deactivate.', dpa['dpaId'])
        self.active_dpas.remove(dpa)
        continue
            
      logging.info('Deactivating: %s', dpa)
      self._sas_admin.TriggerDpaDeactivation(dpa)
      self.active_dpas.remove(dpa)

    # Step 22: wait for 240 sec if any DPA is activated in Step 21, else 15 sec.
    logging.info('Step 22: waiting.')
    if iteration_content['dpaActivationList']:
      time.sleep(240)
    else:
      time.sleep(15)

    # Step 23: Send heartbeat request for all CBSDs managed by SAS UUT.
    logging.info('Step 23: heartbeating all CBSDs.')
    for index, domain_proxy_object in enumerate(self.domain_proxy_objects):
      logging.info('Heartbeating for domain proxy %d.', index)
      domain_proxy_object.heartbeatForAllActiveGrants()

    # Steps 24, 25, and CHECK
    logging.info('Steps 24, 25, and CHECK: calculating reference DPA move list,'
                 ' performing DPA aggregate interference check and performing'
                 ' aggregate interference check.')
    self.performIapAndDpaChecks()

  def performIapAndDpaChecks(self):
    """Checks aggregate interference to all protected entities and active DPAs.
    """
    # Check DPA movelist + Check IAP interference (can happen in parallel).
    self.aggregate_interference_check = self.agg_interf_check_executor.submit(self.performAggregateInterferenceCheck)
    # Get list of authorized grants, in the format required from DPA movelist checking.
    grant_info = data.getAuthorizedGrantsFromDomainProxies(self.domain_proxy_objects)
    # Calculate DPA movelist.
    for active_dpa in self.active_dpas:
      # Check SAS UUT authorized grants do not exceed allowed threshold as calculated by the DPA reference model.
      logging.info('CHECK: DPA aggregate interference check for DPA %s.',
                   active_dpa['dpaId'])
      dpa_combined_id = '%s,%s,%s' % (
          active_dpa['dpaId'], active_dpa['frequencyRange']['lowFrequency'],
          active_dpa['frequencyRange']['highFrequency'])
      dpa = self.dpa_managers[dpa_combined_id]
      this_dpa_check_succeeded = dpa.CheckInterference(
          sas_uut_active_grants=grant_info,
          margin_db=self.dpa_margins[dpa_combined_id],
          do_abs_check_single_uut=(self.num_peer_sases == 0))
      if not this_dpa_check_succeeded:
        logging.error('Check for DPA %s FAILED.', active_dpa['dpaId'])
        self.all_dpa_checks_succeeded = False

    logging.info('Waiting for aggregate interference check to complete')
    self.aggregate_interference_check.result()
    logging.info('Aggregate interference check is now COMPLETE.')

  def performIap(self):
    self.ppa_ap_iap_ref_values_list = []
    self.gwpz_ap_iap_ref_values_list = []
    self.fss_blocking_ap_iap_ref_values_list = []
    self.fss_cochannel_ap_iap_ref_values_list = []
    self.esc_ap_iap_ref_values_list = []

    # Calculate the interference value for all PPAs.
    if 'ppaRecords' in self.protected_entity_records:
      for ppa_record in self.protected_entity_records['ppaRecords']:
        pal_records = self.protected_entity_records['palRecords']
        # Call IAP reference model for PPA.
        logging.info('Calling the IAP reference model for PPA (%s) with PAL records (%s)', ppa_record, pal_records)
        ppa_ap_iap_ref_values = iap.performIapForPpa(
            ppa_record,
            self.sas_uut_fad,
            self.test_harness_fads,
            pal_records)
        # Store the IAP results for future comparison.
        logging.debug('IAP reference model results: %s' % str(ppa_ap_iap_ref_values))
        self.ppa_ap_iap_ref_values_list.append(ppa_ap_iap_ref_values)

    # Calculate the interference value for all GWPZs.
    if 'gwpzRecords' in self.protected_entity_records:
      for gwpz_record in self.protected_entity_records['gwpzRecords']:
        # Call IAP reference model for GWPZ.
        logging.info('Calling the IAP reference model for GWPZ (%s).', gwpz_record)
        gwpz_ap_iap_ref_values = iap.performIapForGwpz(
            gwpz_record,
            self.sas_uut_fad,
            self.test_harness_fads)
        # Store the IAP results for future comparison.
        logging.debug('IAP reference model results: %s' % str(gwpz_ap_iap_ref_values))
        self.gwpz_ap_iap_ref_values_list.append(gwpz_ap_iap_ref_values)

    # Calculate the interference value for all FSS sites.
    if 'fssRecords' in self.protected_entity_records:
      for fss_record in self.protected_entity_records['fssRecords']:
        fss_freq_range = fss_record['record']['deploymentParam'][0]\
            ['operationParam']['operationFrequencyRange']
        fss_low_freq = fss_freq_range['lowFrequency']
        fss_high_freq = fss_freq_range['highFrequency']
        fss_ttc_flag = fss_record['ttc']
        if (fss_low_freq >= interference.FSS_LOW_FREQ_HZ and
                fss_low_freq < interference.CBRS_HIGH_FREQ_HZ):
          # Call IAP for FSS blocking and FSS cochannel.
          logging.info('Calling the cochannel IAP reference model for FSS (%s).', fss_record)
          fss_cochannel_ap_iap_ref_values = iap.\
              performIapForFssCochannel(fss_record, self.sas_uut_fad, self.test_harness_fads)
          logging.debug('IAP reference model results: %s' % str(fss_cochannel_ap_iap_ref_values))
          logging.info('Calling the blocking IAP reference model for FSS (%s).', fss_record)
          fss_blocking_ap_iap_ref_values = iap.\
              performIapForFssBlocking(fss_record, self.sas_uut_fad, self.test_harness_fads)
          logging.debug('IAP reference model results: %s' % str(fss_blocking_ap_iap_ref_values))
          # Store the IAP results for future comparison.
          self.fss_cochannel_ap_iap_ref_values_list.append(fss_cochannel_ap_iap_ref_values)
          self.fss_blocking_ap_iap_ref_values_list.append(fss_blocking_ap_iap_ref_values)
        elif (fss_low_freq >= interference.FSS_TTC_LOW_FREQ_HZ and
                 fss_high_freq <= interference.FSS_TTC_HIGH_FREQ_HZ and
                 fss_ttc_flag is True):
          # Call IAP reference model for FSS blocking.
          logging.info('Calling the blocking IAP reference model for FSS (%s).', fss_record)
          fss_blocking_ap_iap_ref_values = iap.\
              performIapForFssBlocking(fss_record, self.sas_uut_fad, self.test_harness_fads)
          logging.debug('IAP reference model results: %s' % str(fss_blocking_ap_iap_ref_values))
          # Store the IAP results for future comparison.
          self.fss_cochannel_ap_iap_ref_values_list.append(None)
          self.fss_blocking_ap_iap_ref_values_list.append(fss_blocking_ap_iap_ref_values)
        else:
          self.fss_cochannel_ap_iap_ref_values_list.append(None)
          self.fss_blocking_ap_iap_ref_values_list.append(None)
    # Calculate the interference value for all ESCs.
    if 'escRecords' in self.protected_entity_records:
      for esc_record in self.protected_entity_records['escRecords']:
        # Call IAP reference model for ESC.
        logging.info('Calling the IAP reference model for ESC (%s).' % str(esc_record))
        esc_ap_iap_ref_values = iap.performIapForEsc(
          esc_record,
          self.sas_uut_fad,
          self.test_harness_fads)
        # Store the IAP results for future comparison.
        logging.debug('IAP reference model results: %s' % str(esc_ap_iap_ref_values))
        self.esc_ap_iap_ref_values_list.append(esc_ap_iap_ref_values)

  def performAggregateInterferenceCheck(self):
    authorized_grants = None
    if any(key in self.protected_entity_records
           for key in ['gwpzRecords', 'fssRecords', 'escRecords']):
      # Get Grant info for all CBSDs with grants from SAS UUT.
      authorized_grants = data.getAuthorizedGrantsFromDomainProxies(
          self.domain_proxy_objects)

    # Calculate and compare the interference value for PPA protected entity
    if 'ppaRecords' in self.protected_entity_records:
      for index, ppa_record in enumerate(self.protected_entity_records['ppaRecords']):
        pal_records = self.protected_entity_records['palRecords']
        # Get Grant info for all CBSDs with grants from SAS UUT that are not
        # part of the current ppa.
        logging.info('Checking aggregate interference for PPA (%s) with PAL records (%s)', ppa_record, pal_records)

        ppa_authorized_grants = data.getAuthorizedGrantsFromDomainProxies(
            self.domain_proxy_objects, ppa_record=ppa_record)
        # Call aggregate interference reference model for ppa
        ppa_aggr_interference = aggregate_interference.calculateAggregateInterferenceForPpa(
            ppa_record, pal_records, ppa_authorized_grants)
        ppa_ap_iap_ref_values = None
        if self.num_peer_sases > 0:
          ppa_ap_iap_ref_values = self.ppa_ap_iap_ref_values_list[index]
        else:
          ppa_ap_iap_ref_values = interference.dbToLinear(iap.THRESH_PPA_DBM_PER_IAPBW)
        # Compare the interference values calculated from both models
        self.compareIapAndAggregateResults(ppa_ap_iap_ref_values, ppa_aggr_interference, 'area')


    # Calculate and compare the interference value for GWPZ protected entity
    if 'gwpzRecords' in self.protected_entity_records:
      for index, gwpz_record in enumerate(self.protected_entity_records['gwpzRecords']):
        logging.info('Checking aggregate interference for GWPZ (%s).', gwpz_record)
        # Call aggregate interference reference model for GWPZ.
        gwpz_aggr_interference = aggregate_interference.calculateAggregateInterferenceForGwpz(
            gwpz_record,
            authorized_grants)
        gwpz_ap_iap_ref_values = None
        if self.num_peer_sases > 0:
          gwpz_ap_iap_ref_values = self.gwpz_ap_iap_ref_values_list[index]
        else:
          gwpz_ap_iap_ref_values = interference.dbToLinear(iap.THRESH_GWPZ_DBM_PER_IAPBW)
        # Compare the interference values calculated from both models
        self.compareIapAndAggregateResults(gwpz_ap_iap_ref_values, gwpz_aggr_interference, 'area')

    # Calculate and compare the interference value for FSS site.
    if 'fssRecords' in self.protected_entity_records:
      for index, fss_record in enumerate(self.protected_entity_records['fssRecords']):
        logging.info('Checking aggregate interference for FSS (%s).', fss_record)
        fss_freq_range = fss_record['record']['deploymentParam'][0]\
            ['operationParam']['operationFrequencyRange']
        fss_low_freq = fss_freq_range['lowFrequency']
        fss_high_freq = fss_freq_range['highFrequency']
        fss_ttc_flag = fss_record['ttc']
        if (fss_low_freq >= interference.FSS_LOW_FREQ_HZ and
                fss_low_freq < interference.CBRS_HIGH_FREQ_HZ):
          # Call aggregate interference for FSS blocking ad FSS cochannel.
          fss_cochannel_aggr_interference = aggregate_interference.\
              calculateAggregateInterferenceForFssCochannel(fss_record, authorized_grants)
          fss_blocking_aggr_interference = aggregate_interference.\
              calculateAggregateInterferenceForFssBlocking(fss_record, authorized_grants)
          fss_cochannel_ap_iap_ref_values = None
          fss_blocking_ap_iap_ref_values = None
          if self.num_peer_sases > 0:
            fss_cochannel_ap_iap_ref_values = self.fss_cochannel_ap_iap_ref_values_list[index]
            fss_blocking_ap_iap_ref_values = self.fss_blocking_ap_iap_ref_values_list[index]
          else:
            fss_cochannel_ap_iap_ref_values = interference.dbToLinear(iap.THRESH_FSS_CO_CHANNEL_DBM_PER_IAPBW)
            fss_blocking_ap_iap_ref_values = interference.dbToLinear(iap.THRESH_FSS_BLOCKING_DBM_PER_RBW)
          # Check and compare interference for FSS entity
          logging.info('Checking cochannel.')
          self.compareIapAndAggregateResults(fss_cochannel_ap_iap_ref_values, fss_cochannel_aggr_interference,'point')
          logging.info('Checking blocking.')
          self.compareIapAndAggregateResults(fss_blocking_ap_iap_ref_values, fss_blocking_aggr_interference,'point')
        elif (fss_low_freq >= interference.FSS_TTC_LOW_FREQ_HZ and
                 fss_high_freq <= interference.FSS_TTC_HIGH_FREQ_HZ and
                 fss_ttc_flag is True):
          # Call aggregate interference model for FSS blocking.
          fss_blocking_aggr_interference = aggregate_interference.\
              calculateAggregateInterferenceForFssBlocking(fss_record, authorized_grants)
          fss_blocking_ap_iap_ref_values = None
          if self.num_peer_sases > 0:
            fss_blocking_ap_iap_ref_values = self.fss_blocking_ap_iap_ref_values_list[index]
          else:
            fss_blocking_ap_iap_ref_values = interference.dbToLinear(iap.THRESH_FSS_BLOCKING_DBM_PER_RBW)
          # Compare the interference values calculated from both models
          logging.info('Checking blocking.')
          self.compareIapAndAggregateResults(fss_blocking_ap_iap_ref_values,\
              fss_blocking_aggr_interference, 'point')

    # Calculate and compare the interference value for ESC.
    if 'escRecords' in self.protected_entity_records:
      for index, esc_record in enumerate(self.protected_entity_records['escRecords']):
        logging.info('Checking aggregate interference for ESC (%s).', esc_record)
        # Call aggregate interference model for ESC.
        esc_aggr_interference = aggregate_interference.\
            calculateAggregateInterferenceForEsc(esc_record, authorized_grants)
        esc_ap_iap_ref_values = None
        if self.num_peer_sases > 0:
          esc_ap_iap_ref_values = self.esc_ap_iap_ref_values_list[index]
        else:
          esc_ap_iap_ref_values = interference.dbToLinear(iap.THRESH_ESC_DBM_PER_IAPBW)
        # Compare the interference values calculated from both models
        self.compareIapAndAggregateResults(esc_ap_iap_ref_values, esc_aggr_interference, 'point')

  def compareIapAndAggregateResults(self, ap_iap_ref_values, aggr_interference, entity_type):
    """Verify aggregate interference is less than or equal to ap_iap_ref value calculated
    by IAP model plus a delta for each FSS and ESC sensor protected point, and for at
    least 95% of the protected points of each PPA and GWPZ.

    Args:
      ap_iap_ref_values : Aggregate interference output calculated from IAP
                             reference model
      aggr_interference : Aggregate interference output calculated from Aggregate
                          Inteference reference model
      entity_type : To identify its a point protected or area protected entity

    Returns: