/**
   * Checks if the specified piece of evidence is relevant for the supplied trustor. More
   * specifically, a piece of evidence is relevant for indirect trust evaluation if:
   *
   * <ol>
   *   <li>type == {@link TrustEvidenceType#DIRECTLY_TRUSTED DIRECTLY_TRUSTED}
   *   <li>trustorId != evidence.subjectId, i.e. ignore trust opinions originating <i>from</i> the
   *       trustor
   *   <li>trustorId != evidence.objectId, i.e. ignore trust opinions <i>about</i> the trustor
   *   <li>evidence.subjectId != evidence.objectId, i.e. ignore self-referencing trust opinions
   * </ol>
   *
   * @param trustorId
   * @param evidence
   * @return <code>true</code> if the specified piece of evidence is relevant for the supplied
   *     trustor; <code>false</code> otherwise.
   */
  private boolean areRelevant(final TrustedEntityId trustorId, final ITrustEvidence evidence) {

    boolean result = false;

    if (TrustEvidenceType.DIRECTLY_TRUSTED == evidence.getType()
        && !trustorId.equals(evidence.getSubjectId())
        && !trustorId.equals(evidence.getObjectId())
        && !evidence.getSubjectId().equals(evidence.getObjectId())) {
      result = true;
    }

    LOG.debug(
        "areRelevant: trustorId={}, evidence={}, result={}",
        new Object[] {trustorId, evidence, result});
    return result;
  }
  /*
   * @see org.societies.integration.performance.test.upper_tester.trust.indirect.ITestIndirectTrustPerformance#testEvaluateIndirectTrust(org.societies.integration.performance.test.lower_tester.PerformanceTestMgmtInfo, org.societies.integration.performance.test.upper_tester.trust.TrustEvidenceParams)
   */
  @Override
  public void testEvaluateIndirectTrust(
      PerformanceTestMgmtInfo performanceTestMgmtInfo, TrustEvidenceParams trustEvidenceParams) {

    // The following 2 lines are mandatory for the beginning of each test
    this.performanceLowerTester = new PerformanceLowerTester(performanceTestMgmtInfo);
    this.performanceLowerTester.testStart(this.getClass().getName(), this.getCommManager());

    final Date testStart = new Date();

    // Extract the Map<cssId, Set<TrustEvidence> contained in the test params.
    Map<String, Set<TrustEvidence>> trustEvidenceMap = null;
    try {
      trustEvidenceMap = TrustEvidenceParamParser.toTrustEvidence(trustEvidenceParams);
    } catch (Exception e) {

      this.fail("Could not parse TrustEvidenceParams JSON String: " + e.getLocalizedMessage());
      return;
    }

    try {
      // Retrieve the IIdentity of my CSS.
      final String myCssId = this.commManager.getIdManager().getCloudNode().getBareJid();
      final TrustedEntityId myTeid = new TrustedEntityId(TrustedEntityType.CSS, myCssId);

      // Extract TrustSimilarity Map based on the test params.
      final Map<TrustedEntityId, TrustSimilarity> trustSimilarityMap =
          this.extractTrustSimilarity(myTeid, trustEvidenceMap);

      // The set of trust evidence to evaluate
      final Set<TrustEvidence> trustEvidenceSet =
          (trustEvidenceMap.get(myCssId) != null)
              ? trustEvidenceMap.get(myCssId)
              : Collections.<TrustEvidence>emptySet();

      // Start test
      LOG.info(
          "START testEvaluateDirectTrust: trustEvidenceSet={}, trustSimilarityMap={}",
          trustEvidenceSet,
          trustSimilarityMap);
      final Set<TrustRelationship> result = new LinkedHashSet<TrustRelationship>();
      for (final TrustEvidence trustEvidence : trustEvidenceSet) {

        LOG.info("Evaluating evidence '{}'", trustEvidence);
        // Create a DIRECT trust query for the entity specified in this piece of evidence
        final TrustQuery trustQuery =
            new TrustQuery(myTeid)
                .setTrusteeId(trustEvidence.getObjectId())
                .setTrustValueType(TrustValueType.DIRECT);
        // Initialise count-down latch to use for verifying trust update event.
        this.cdLatch = new CountDownLatch(1);
        final TrustUpdateEventListener trustListener = new TrustUpdateEventListener();
        // Register for trust value updates of this entity
        this.internalTrustBroker.registerTrustUpdateListener(trustListener, trustQuery);
        // Add evidence
        this.internalTrustEvidenceCollector.addDirectEvidence(
            trustEvidence.getSubjectId(),
            trustEvidence.getObjectId(),
            trustEvidence.getType(),
            trustEvidence.getTimestamp(),
            trustEvidence.getInfo());
        final boolean isTrustUpdated =
            this.cdLatch.await(DIRECT_EVAL_TIMEOUT, TimeUnit.MILLISECONDS);
        // Unregister from trust value updates of this entity
        this.internalTrustBroker.unregisterTrustUpdateListener(trustListener, trustQuery);
        if (!isTrustUpdated) {
          LOG.warn(
              "Did not receive TrustUpdateEvent for evidence '"
                  + trustEvidence
                  + "' in the specified timeout: "
                  + DIRECT_EVAL_TIMEOUT
                  + "ms");
        } else {
          final TrustRelationship updatedTrustRelationship =
              trustListener.getUpdatedTrustRelationship();
          LOG.info("Updated trust relationship '{}'", updatedTrustRelationship);
          // Verify Trust Relationships properties
          if (!myTeid.equals(updatedTrustRelationship.getTrustorId())) {
            this.fail(
                "Expected trustorId '"
                    + myTeid
                    + "' but was '"
                    + updatedTrustRelationship.getTrustorId()
                    + "'");
            return;
          }
          if (!trustEvidence.getObjectId().equals(updatedTrustRelationship.getTrusteeId())) {
            this.fail(
                "Expected trusteeId '"
                    + myTeid
                    + "' but was '"
                    + updatedTrustRelationship.getTrusteeId()
                    + "'");
            return;
          }
          if (TrustValueType.DIRECT != updatedTrustRelationship.getTrustValueType()) {
            this.fail(
                "Expected trustValueType '"
                    + TrustValueType.DIRECT
                    + "' but was '"
                    + updatedTrustRelationship.getTrustValueType()
                    + "'");
            return;
          }
        }
      }

      final Set<TrustedEntityId> connectionTeids = trustSimilarityMap.keySet();
      for (final TrustedEntityId connectionTeid : connectionTeids) {
        LOG.info("Establishing DIRECT trust relationship with connection '{}'", connectionTeid);
        // Create a DIRECT trust query for the new connection
        final TrustQuery trustQuery =
            new TrustQuery(myTeid)
                .setTrusteeId(connectionTeid)
                .setTrustValueType(TrustValueType.DIRECT);
        // Initialise count-down latch to use for verifying trust update event.
        this.cdLatch = new CountDownLatch(1);
        final TrustUpdateEventListener trustListener = new TrustUpdateEventListener();
        // Register for trust value updates of this entity
        this.internalTrustBroker.registerTrustUpdateListener(trustListener, trustQuery);
        // Add fake evidence to establish DIRECT trust connection with connection
        this.internalTrustEvidenceCollector.addDirectEvidence(
            myTeid, connectionTeid, TrustEvidenceType.FRIENDED_USER, new Date(), null);
        final boolean isTrustUpdated =
            this.cdLatch.await(DIRECT_EVAL_TIMEOUT, TimeUnit.MILLISECONDS);
        // Unregister from trust value updates of this entity
        this.internalTrustBroker.unregisterTrustUpdateListener(trustListener, trustQuery);
        if (!isTrustUpdated) {
          LOG.warn(
              "Did not receive TrustUpdateEvent for connection '"
                  + connectionTeid
                  + "' in the specified timeout: "
                  + DIRECT_EVAL_TIMEOUT
                  + "ms");
        } else {
          final TrustRelationship updatedTrustRelationship =
              trustListener.getUpdatedTrustRelationship();
          LOG.info("Updated trust relationship '{}'", updatedTrustRelationship);
          // Verify Trust Relationships properties
          if (!myTeid.equals(updatedTrustRelationship.getTrustorId())) {
            this.fail(
                "Expected trustorId '"
                    + myTeid
                    + "' but was '"
                    + updatedTrustRelationship.getTrustorId()
                    + "'");
            return;
          }
          if (!connectionTeid.equals(updatedTrustRelationship.getTrusteeId())) {
            this.fail(
                "Expected trusteeId '"
                    + connectionTeid
                    + "' but was '"
                    + updatedTrustRelationship.getTrusteeId()
                    + "'");
            return;
          }
          if (TrustValueType.DIRECT != updatedTrustRelationship.getTrustValueType()) {
            this.fail(
                "Expected trustValueType '"
                    + TrustValueType.DIRECT
                    + "' but was '"
                    + updatedTrustRelationship.getTrustValueType()
                    + "'");
            return;
          }
        }
      }

      LOG.info("Sleeping for {}ms to allow INDIRECT trust evaluation", INDIRECT_EVAL_TIMEOUT);
      Thread.sleep(INDIRECT_EVAL_TIMEOUT);

      for (final Map.Entry<TrustedEntityId, TrustSimilarity> trustSimilarity :
          trustSimilarityMap.entrySet()) {
        for (final TrustedEntityId trusteeId : trustSimilarity.getValue().getUnsharedTrustees()) {
          final ExtTrustRelationship extTrustRelationship =
              this.internalTrustBroker
                  .retrieveExtTrustRelationship(
                      new TrustQuery(myTeid)
                          .setTrusteeId(trusteeId)
                          .setTrustValueType(TrustValueType.INDIRECT))
                  .get();
          LOG.info("INDIRECT trust relationship '{}'", extTrustRelationship);
          if (extTrustRelationship == null) {
            this.fail("INDIRECT trust relationship with '" + trusteeId + "' not found");
            return;
          }
          if (extTrustRelationship.getTimestamp().compareTo(testStart) < 0) {
            this.fail("INDIRECT trust relationship with '" + trusteeId + "' is out-of-date");
            return;
          }
          if (extTrustRelationship.getTrustEvidence().isEmpty()) {
            this.fail("INDIRECT trust relationship with '" + trusteeId + "' contains no evidence");
            return;
          }
          boolean foundConnection = false;
          for (final TrustEvidence evidence : extTrustRelationship.getTrustEvidence()) {
            if (TrustEvidenceType.DIRECTLY_TRUSTED == evidence.getType()
                && trustSimilarity.getKey().equals(evidence.getSubjectId())) {
              foundConnection = true;
              break;
            }
          }
          if (!foundConnection) {
            this.fail(
                "INDIRECT trust relationship with '"
                    + trusteeId
                    + "' not associated with connection '"
                    + trustSimilarity.getKey()
                    + "'");
            return;
          }
          result.add(extTrustRelationship);
        }
      }

      // End test
      LOG.info("END testEvaluateDirectTrust for params: {}", trustEvidenceSet);
      this.performanceTestResult =
          new PerformanceTestResult(
              this.getClass().getName(),
              "Updated trust relationships: " + result,
              PerformanceTestResult.SUCCESS_STATUS);
      this.performanceLowerTester.testFinish(this.performanceTestResult);

    } catch (TrustException te) {

      this.fail(te.getLocalizedMessage());

    } catch (InterruptedException ie) {

      this.fail("Interrupted while executing test: " + ie.getLocalizedMessage());

    } catch (Exception e) {

      this.fail(e.getLocalizedMessage());
    }
  }